From 1cf307daec565e9d1105b1b12c00c2b208cd81c5 Mon Sep 17 00:00:00 2001 From: Fluid <90795031+fluiderson@users.noreply.github.com> Date: Wed, 27 Mar 2024 09:46:04 +0200 Subject: [PATCH 01/76] New API progress --- Cargo.lock | 907 +++++++------ Cargo.toml | 32 +- kalatori-ah/Cargo.toml | 42 - kalatori-ah/src/database.rs | 380 ------ kalatori-ah/src/lib.rs | 322 ----- kalatori-ah/src/main.rs | 5 - kalatori-ah/src/rpc.rs | 1279 ------------------- kalatori-ah/src/server.rs | 208 --- kalatori-test.toml | 31 + kalatori.toml | 36 + rustfmt.toml | 1 - src/callback.rs | 2 + src/database.rs | 642 +++++----- src/lib.rs | 266 ---- src/main.rs | 451 ++++++- src/rpc.rs | 2398 +++++++++++++++++++---------------- src/server.rs | 509 +++++--- 17 files changed, 3000 insertions(+), 4511 deletions(-) delete mode 100644 kalatori-ah/Cargo.toml delete mode 100644 kalatori-ah/src/database.rs delete mode 100644 kalatori-ah/src/lib.rs delete mode 100644 kalatori-ah/src/main.rs delete mode 100644 kalatori-ah/src/rpc.rs delete mode 100644 kalatori-ah/src/server.rs create mode 100644 kalatori-test.toml create mode 100644 kalatori.toml create mode 100644 src/callback.rs delete mode 100644 src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 894ed83..3483005 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -72,9 +72,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] @@ -159,9 +159,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.80" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1" +checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" [[package]] name = "ark-bls12-377" @@ -359,9 +359,9 @@ dependencies = [ [[package]] name = "async-io" -version = "2.3.1" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f97ab0c5b00a7cdbe5a371b9a782ee7be1316095885c8a4ea1daf490eb0ef65" +checksum = "dcccb0f599cfa2f8ace422d3555572f47424da5648a4382a9dd0310ff8210884" dependencies = [ "async-lock 3.3.0", "cfg-if", @@ -370,7 +370,7 @@ dependencies = [ "futures-lite", "parking", "polling", - "rustix 0.38.31", + "rustix 0.38.32", "slab", "tracing", "windows-sys 0.52.0", @@ -421,7 +421,7 @@ dependencies = [ "cfg-if", "event-listener 5.2.0", "futures-lite", - "rustix 0.38.31", + "rustix 0.38.32", "windows-sys 0.52.0", ] @@ -437,7 +437,7 @@ dependencies = [ "cfg-if", "futures-core", "futures-io", - "rustix 0.38.31", + "rustix 0.38.32", "signal-hook-registry", "slab", "windows-sys 0.48.0", @@ -451,13 +451,13 @@ checksum = "fbb36e985947064623dbd357f727af08ffd077f93d696782f3c56365fa2e2799" [[package]] name = "async-trait" -version = "0.1.77" +version = "0.1.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" +checksum = "a507401cad91ec6a857ed5513a2073c82a9b9048762b885bb98655b306964681" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] @@ -474,15 +474,15 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" [[package]] name = "axum" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1236b4b292f6c4d6dc34604bb5120d85c3fe1d1aa596bd5cc52ca054d13e7b9e" +checksum = "3a6c9af12842a67734c9a2e355436e5d03b22383ed60cf13cd0c18fbfe3dcbcf" dependencies = [ "async-trait", "axum-core", @@ -504,12 +504,11 @@ dependencies = [ "serde_json", "serde_path_to_error", "serde_urlencoded", - "sync_wrapper", + "sync_wrapper 1.0.0", "tokio", "tower", "tower-layer", "tower-service", - "tracing", ] [[package]] @@ -527,17 +526,16 @@ dependencies = [ "mime", "pin-project-lite", "rustversion", - "sync_wrapper", + "sync_wrapper 0.1.2", "tower-layer", "tower-service", - "tracing", ] [[package]] name = "backtrace" -version = "0.3.69" +version = "0.3.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" dependencies = [ "addr2line 0.21.0", "cc", @@ -548,6 +546,12 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + [[package]] name = "base58" version = "0.2.0" @@ -596,19 +600,31 @@ 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", + "bitcoin_hashes 0.11.0", ] +[[package]] +name = "bitcoin-internals" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9425c3bf7089c983facbae04de54513cce73b41c7f9ff8c845b54e7bc64ebbfb" + [[package]] name = "bitcoin_hashes" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90064b8dee6815a6470d60bad07bbbaee885c0e12d04177138fa3291a01b7bc4" +[[package]] +name = "bitcoin_hashes" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1930a4dabfebb8d7d9992db18ebe3ae2876f0a305fab206fd168df931ede293b" +dependencies = [ + "bitcoin-internals", + "hex-conservative", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -617,9 +633,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.2" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" [[package]] name = "bitvec" @@ -699,9 +715,9 @@ dependencies = [ [[package]] name = "bounded-collections" -version = "0.1.9" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca548b6163b872067dc5eb82fd130c56881435e30367d2073594a3d9744120dd" +checksum = "d32385ecb91a31bddaf908e8dcf4a15aef1bcd3913cc03ebfad02ff6d568abc1" dependencies = [ "log", "parity-scale-codec", @@ -711,18 +727,18 @@ dependencies = [ [[package]] name = "bs58" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5353f36341f7451062466f0b755b96ac3a9547e4d7f6b70d603fc721a7d7896" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" dependencies = [ "tinyvec", ] [[package]] name = "bumpalo" -version = "3.15.3" +version = "3.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ea184aa71bb362a1157c896979544cc23974e08fd265f29ea96b59f0b4a555b" +checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa" [[package]] name = "byte-slice-cast" @@ -738,9 +754,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" [[package]] name = "cc" @@ -912,31 +928,33 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" [[package]] -name = "crypto-common" -version = "0.1.6" +name = "crypto-bigint" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" dependencies = [ "generic-array", "rand_core 0.6.4", - "typenum", + "subtle", + "zeroize", ] [[package]] -name = "crypto-mac" -version = "0.8.0" +name = "crypto-common" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", - "subtle", + "rand_core 0.6.4", + "typenum", ] [[package]] name = "crypto-mac" -version = "0.11.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25fab6889090c8133f3deb8f73ba3c65a7f456f66436fc012a1b1e272b1e103e" +checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" dependencies = [ "generic-array", "subtle", @@ -980,7 +998,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] @@ -1028,7 +1046,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] @@ -1050,7 +1068,7 @@ checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f" dependencies = [ "darling_core 0.20.8", "quote", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] @@ -1114,6 +1132,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer 0.10.4", + "const-oid", "crypto-common", "subtle", ] @@ -1139,7 +1158,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.52", + "syn 2.0.55", "termcolor", "toml", "walkdir", @@ -1178,6 +1197,21 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest 0.10.7", + "elliptic-curve", + "rfc6979", + "serdect", + "signature", + "spki", +] + [[package]] name = "ed25519" version = "2.2.3" @@ -1238,26 +1272,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" [[package]] -name = "env_filter" -version = "0.1.0" +name = "elliptic-curve" +version = "0.13.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" dependencies = [ - "log", - "regex", + "base16ct", + "crypto-bigint", + "digest 0.10.7", + "ff", + "generic-array", + "group", + "pkcs8", + "rand_core 0.6.4", + "sec1", + "serdect", + "subtle", + "zeroize", ] [[package]] -name = "env_logger" -version = "0.10.2" +name = "env_filter" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" +checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea" dependencies = [ - "humantime", - "is-terminal", "log", - "regex", - "termcolor", ] [[package]] @@ -1354,7 +1394,7 @@ dependencies = [ "prettier-please", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] @@ -1365,15 +1405,25 @@ checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" [[package]] name = "fastrand" -version = "2.0.1" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +checksum = "658bd65b1cf4c852a3cc96f18a8ce7b5640f6b703f905c7d74532294c2a63984" + +[[package]] +name = "ff" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] [[package]] name = "fiat-crypto" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1676f435fc1dadde4d03e43f5d62b259e1ce5f40bd4ffb21db2b42ebe59c1382" +checksum = "c007b1ae3abe1cb6f85a16305acd418b7ca6343b953633fee2b76d8f108b830f" [[package]] name = "fixed-hash" @@ -1491,9 +1541,9 @@ checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" [[package]] name = "futures-lite" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445ba825b27408685aaecefd65178908c36c6e96aaf6d8599419d46e624192ba" +checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" dependencies = [ "fastrand", "futures-core", @@ -1510,7 +1560,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] @@ -1557,6 +1607,7 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] @@ -1598,37 +1649,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" [[package]] -name = "h2" -version = "0.3.24" +name = "group" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http 0.2.12", - "indexmap 2.2.5", - "slab", - "tokio", - "tokio-util", - "tracing", + "ff", + "rand_core 0.6.4", + "subtle", ] [[package]] name = "h2" -version = "0.4.2" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31d030e59af851932b72ceebadf4a2b5986dba4c3b99dd2493f8273a0f151943" +checksum = "4fbd2820c5e49886948654ab546d0688ff24530286bdcf8fca3cefb16d4618eb" dependencies = [ "bytes", "fnv", "futures-core", "futures-sink", "futures-util", - "http 1.1.0", - "indexmap 2.2.5", + "http 0.2.12", + "indexmap 2.2.6", "slab", "tokio", "tokio-util", @@ -1698,22 +1741,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] -name = "hmac" -version = "0.8.1" +name = "hex-conservative" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "126888268dcc288495a26bf004b38c5fdbb31682f992c84ceb046a1f0fe38840" +checksum = "30ed443af458ccb6d81c1e7e661545f94d3176752fb1df2f543b902a1e0f51e2" + +[[package]] +name = "hex-simd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f7685beb53fc20efc2605f32f5d51e9ba18b8ef237961d1760169d2290d3bee" dependencies = [ - "crypto-mac 0.8.0", - "digest 0.9.0", + "outref", + "vsimd", ] [[package]] name = "hmac" -version = "0.11.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" +checksum = "126888268dcc288495a26bf004b38c5fdbb31682f992c84ceb046a1f0fe38840" dependencies = [ - "crypto-mac 0.11.0", + "crypto-mac", "digest 0.9.0", ] @@ -1782,12 +1831,12 @@ dependencies = [ [[package]] name = "http-body-util" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41cb79eb393015dadd30fc252023adb0b2400a0caee0fa2a077e6e21a551e840" +checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d" dependencies = [ "bytes", - "futures-util", + "futures-core", "http 1.1.0", "http-body 1.0.0", "pin-project-lite", @@ -1821,7 +1870,7 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "h2 0.3.24", + "h2", "http 0.2.12", "http-body 0.4.6", "httparse", @@ -1844,7 +1893,6 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "h2 0.4.2", "http 1.1.0", "http-body 1.0.0", "httparse", @@ -1968,9 +2016,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.2.5" +version = "2.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", "hashbrown 0.14.3", @@ -2020,17 +2068,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "is-terminal" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" -dependencies = [ - "hermit-abi", - "libc", - "windows-sys 0.52.0", -] - [[package]] name = "itertools" version = "0.10.5" @@ -2051,9 +2088,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "js-sys" @@ -2066,57 +2103,26 @@ dependencies = [ [[package]] name = "jsonrpsee" -version = "0.21.0" +version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9579d0ca9fb30da026bac2f0f7d9576ec93489aeb7cd4971dd5b4617d82c79b2" +checksum = "3cdbb7cb6f3ba28f5b212dd250ab4483105efc3e381f5c8bb90340f14f0a2cc3" dependencies = [ - "jsonrpsee-client-transport 0.21.0", - "jsonrpsee-core 0.21.0", + "jsonrpsee-client-transport", + "jsonrpsee-core", "jsonrpsee-http-client", - "jsonrpsee-types 0.21.0", -] - -[[package]] -name = "jsonrpsee" -version = "0.22.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f3ae45a64cfc0882934f963be9431b2a165d667f53140358181f262aca0702" -dependencies = [ - "jsonrpsee-core 0.22.2", - "jsonrpsee-types 0.22.2", + "jsonrpsee-types", "jsonrpsee-ws-client", ] [[package]] name = "jsonrpsee-client-transport" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9f9ed46590a8d5681975f126e22531698211b926129a40a2db47cbca429220" -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", -] - -[[package]] -name = "jsonrpsee-client-transport" -version = "0.22.2" +version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "455fc882e56f58228df2aee36b88a1340eafd707c76af2fa68cf94b37d461131" +checksum = "9ab2e14e727d2faf388c99d9ca5210566ed3b044f07d92c29c3611718d178380" dependencies = [ "futures-util", "http 0.2.12", - "jsonrpsee-core 0.22.2", + "jsonrpsee-core", "pin-project", "rustls-native-certs 0.7.0", "rustls-pki-types", @@ -2131,9 +2137,9 @@ dependencies = [ [[package]] name = "jsonrpsee-core" -version = "0.21.0" +version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "776d009e2f591b78c038e0d053a796f94575d66ca4e77dd84bfc5e81419e436c" +checksum = "71962a1c49af43adf81d337e4ebc93f3c915faf6eccaa14d74e255107dfd7723" dependencies = [ "anyhow", "async-lock 3.3.0", @@ -2142,30 +2148,7 @@ dependencies = [ "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.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b75568f4f9696e3a47426e1985b548e1a9fcb13372a5e320372acaf04aca30d1" -dependencies = [ - "anyhow", - "async-lock 3.3.0", - "async-trait", - "beef", - "futures-timer", - "futures-util", - "jsonrpsee-types 0.22.2", + "jsonrpsee-types", "pin-project", "rustc-hash", "serde", @@ -2178,15 +2161,15 @@ dependencies = [ [[package]] name = "jsonrpsee-http-client" -version = "0.21.0" +version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b7de9f3219d95985eb77fd03194d7c1b56c19bce1abfcc9d07462574b15572" +checksum = "8c13987da51270bda2c1c9b40c19be0fe9b225c7a0553963d8f17e683a50ce84" dependencies = [ "async-trait", "hyper 0.14.28", "hyper-rustls", - "jsonrpsee-core 0.21.0", - "jsonrpsee-types 0.21.0", + "jsonrpsee-core", + "jsonrpsee-types", "serde", "serde_json", "thiserror", @@ -2198,22 +2181,9 @@ dependencies = [ [[package]] name = "jsonrpsee-types" -version = "0.21.0" +version = "0.22.3" 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.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3467fd35feeee179f71ab294516bdf3a81139e7aeebdd860e46897c12e1a3368" +checksum = "1e53c72de6cd2ad6ac1aa6e848206ef8b736f92ed02354959130373dfa5b3cbd" dependencies = [ "anyhow", "beef", @@ -2224,51 +2194,49 @@ dependencies = [ [[package]] name = "jsonrpsee-ws-client" -version = "0.22.2" +version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68ca71e74983f624c0cb67828e480a981586074da8ad3a2f214c6a3f884edab9" +checksum = "c8a07ab8da9a283b906f6735ddd17d3680158bb72259e853441d1dd0167079ec" dependencies = [ "http 0.2.12", - "jsonrpsee-client-transport 0.22.2", - "jsonrpsee-core 0.22.2", - "jsonrpsee-types 0.22.2", + "jsonrpsee-client-transport", + "jsonrpsee-core", + "jsonrpsee-types", "url", ] [[package]] -name = "kalatori" -version = "0.1.2" +name = "k256" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "956ff9b67e26e1a6a866cb758f12c6f8746208489e3e4a4b5580802f2f0a587b" dependencies = [ - "anyhow", - "axum", - "env_logger 0.11.3", - "hex", - "log", - "reconnecting-jsonrpsee-ws-client", - "redb", - "serde", - "serde_json", - "subxt", - "tokio", - "tokio-util", + "cfg-if", + "ecdsa", + "elliptic-curve", + "once_cell", + "serdect", + "sha2 0.10.8", ] [[package]] -name = "kalatori-ah" -version = "0.1.2" +name = "kalatori" +version = "0.2.0" dependencies = [ "anyhow", "axum", - "env_logger 0.10.2", - "hex", + "env_logger", + "hex-simd", + "humantime", "log", - "reconnecting-jsonrpsee-ws-client", + "names", "redb", "serde", - "serde_json", "subxt", "tokio", "tokio-util", + "toml_edit 0.22.9", + "ureq", ] [[package]] @@ -2419,7 +2387,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2cffa4ad52c6f791f4f8b15f0c05f9824b2ced1160e88cc393d64fff9a8ac64" dependencies = [ - "rustix 0.38.31", + "rustix 0.38.32", ] [[package]] @@ -2484,6 +2452,15 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "names" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bddcd3bf5144b6392de80e04c347cd7fab2508f6df16a85fc496ecd5cec39bc" +dependencies = [ + "rand", +] + [[package]] name = "no-std-net" version = "0.6.0" @@ -2612,6 +2589,25 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +[[package]] +name = "outref" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4030760ffd992bef45b0ae3f10ce1aba99e33464c90d14dd7c039884963ddc7a" + +[[package]] +name = "parity-bip39" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e69bf016dc406eff7d53a7d3f7cf1c2e72c82b9088aac1118591e36dd2cd3e9" +dependencies = [ + "bitcoin_hashes 0.13.0", + "rand", + "rand_core 0.6.4", + "serde", + "unicode-normalization", +] + [[package]] name = "parity-scale-codec" version = "3.6.9" @@ -2669,19 +2665,21 @@ dependencies = [ ] [[package]] -name = "paste" -version = "1.0.14" +name = "password-hash" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" +checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" +dependencies = [ + "base64ct", + "rand_core 0.6.4", + "subtle", +] [[package]] -name = "pbkdf2" -version = "0.8.0" +name = "paste" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d95f5254224e617595d2cc3cc73ff0a5eaf2637519e25f03388154e9378b6ffa" -dependencies = [ - "crypto-mac 0.11.0", -] +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" [[package]] name = "pbkdf2" @@ -2690,6 +2688,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" dependencies = [ "digest 0.10.7", + "password-hash", ] [[package]] @@ -2715,7 +2714,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] @@ -2753,20 +2752,95 @@ dependencies = [ [[package]] name = "platforms" -version = "3.3.0" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db23d408679286588f4d4644f965003d056e3dd5abcaaa938116871d7ce2fee7" + +[[package]] +name = "polkavm-common" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92c99f7eee94e7be43ba37eef65ad0ee8cbaf89b7c00001c3f6d2be985cb1817" + +[[package]] +name = "polkavm-common" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d9428a5cfcc85c5d7b9fc4b6a18c4b802d0173d768182a51cc7751640f08b92" + +[[package]] +name = "polkavm-derive" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "626dec3cac7cc0e1577a2ec3fc496277ec2baa084bebad95bb6fdbfae235f84c" +checksum = "79fa916f7962348bd1bb1a65a83401675e6fc86c51a0fdbcf92a3108e58e6125" +dependencies = [ + "polkavm-derive-impl-macro 0.8.0", +] + +[[package]] +name = "polkavm-derive" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae8c4bea6f3e11cd89bb18bcdddac10bd9a24015399bd1c485ad68a985a19606" +dependencies = [ + "polkavm-derive-impl-macro 0.9.0", +] + +[[package]] +name = "polkavm-derive-impl" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c10b2654a8a10a83c260bfb93e97b262cf0017494ab94a65d389e0eda6de6c9c" +dependencies = [ + "polkavm-common 0.8.0", + "proc-macro2", + "quote", + "syn 2.0.55", +] + +[[package]] +name = "polkavm-derive-impl" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c4fdfc49717fb9a196e74a5d28e0bc764eb394a2c803eb11133a31ac996c60c" +dependencies = [ + "polkavm-common 0.9.0", + "proc-macro2", + "quote", + "syn 2.0.55", +] + +[[package]] +name = "polkavm-derive-impl-macro" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15e85319a0d5129dc9f021c62607e0804f5fb777a05cdda44d750ac0732def66" +dependencies = [ + "polkavm-derive-impl 0.8.0", + "syn 2.0.55", +] + +[[package]] +name = "polkavm-derive-impl-macro" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ba81f7b5faac81e528eb6158a6f3c9e0bb1008e0ffa19653bc8dea925ecb429" +dependencies = [ + "polkavm-derive-impl 0.9.0", + "syn 2.0.55", +] [[package]] name = "polling" -version = "3.5.0" +version = "3.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24f040dee2588b4963afb4e420540439d126f73fdacf4a9c486a96d840bac3c9" +checksum = "e0c976a60b2d7e99d6f229e414670a9b85d13ac305cc6d1e9c134de58c5aaaf6" dependencies = [ "cfg-if", "concurrent-queue", + "hermit-abi", "pin-project-lite", - "rustix 0.38.31", + "rustix 0.38.32", "tracing", "windows-sys 0.52.0", ] @@ -2795,7 +2869,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22020dfcf177fcc7bf5deaf7440af371400c67c0de14c399938d8ed4fb4645d3" dependencies = [ "proc-macro2", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] @@ -2865,9 +2939,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.78" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" dependencies = [ "unicode-ident", ] @@ -2939,9 +3013,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ea5cf7b021db88f1af45a9b2ecdbe5bc1c5cbebc146632269d572cdd435f5cf" dependencies = [ "futures", - "jsonrpsee 0.22.2", + "jsonrpsee", "serde_json", - "subxt", "thiserror", "tokio", "tokio-retry", @@ -2951,9 +3024,9 @@ dependencies = [ [[package]] name = "redb" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72623e6275cd430215b741f41ebda34db93a13ebde253f908b70871c46afc5ba" +checksum = "a1100a056c5dcdd4e5513d5333385223b26ef1bf92f31eb38f407e8c20549256" dependencies = [ "libc", ] @@ -2984,19 +3057,19 @@ checksum = "5fddb4f8d99b0a2ebafc65a87a69a7b9875e4b1ae1f00db265d300ef7f28bccc" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] name = "regex" -version = "1.10.3" +version = "1.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" dependencies = [ "aho-corasick", "memchr", "regex-automata 0.4.6", - "regex-syntax 0.8.2", + "regex-syntax 0.8.3", ] [[package]] @@ -3016,7 +3089,7 @@ checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.2", + "regex-syntax 0.8.3", ] [[package]] @@ -3027,9 +3100,19 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac 0.12.1", + "subtle", +] [[package]] name = "ring" @@ -3089,11 +3172,11 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.31" +version = "0.38.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" +checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "errno", "libc", "linux-raw-sys 0.4.13", @@ -3114,9 +3197,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.22.2" +version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e87c9956bd9807afa1f77e0f7594af32566e830e088a5576d27c5b6f30f49d41" +checksum = "99008d7ad0bbbea527ec27bddbc0e432c5b87d8175178cee68d2eec9c4a1813c" dependencies = [ "log", "ring", @@ -3172,9 +3255,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.3.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ede67b28608b4c60685c7d54122d4400d90f62b40caee7700e700380a390fa8" +checksum = "868e20fada228fefaf6b652e00cc73623d54f8171e7352c18bb281571f2d92da" [[package]] name = "rustls-webpki" @@ -3231,38 +3314,38 @@ dependencies = [ [[package]] name = "scale-bits" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "036575c29af9b6e4866ffb7fa055dbf623fe7a9cc159b33786de6013a6969d89" +checksum = "662d10dcd57b1c2a3c41c9cf68f71fb09747ada1ea932ad961aca7e2ca28315f" dependencies = [ "parity-scale-codec", "scale-info", + "scale-type-resolver", "serde", ] [[package]] name = "scale-decode" -version = "0.10.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7caaf753f8ed1ab4752c6afb20174f03598c664724e0e32628e161c21000ff76" +checksum = "afc79ba56a1c742f5aeeed1f1801f3edf51f7e818f0a54582cac6f131364ea7b" dependencies = [ "derive_more", "parity-scale-codec", "primitive-types", "scale-bits", "scale-decode-derive", - "scale-info", + "scale-type-resolver", "smallvec", ] [[package]] name = "scale-decode-derive" -version = "0.10.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3475108a1b62c7efd1b5c65974f30109a598b2f45f23c9ae030acb9686966db" +checksum = "5398fdb3c7bea3cb419bac4983aadacae93fe1a7b5f693f4ebd98c3821aad7a5" dependencies = [ "darling 0.14.4", - "proc-macro-crate 1.3.1", "proc-macro2", "quote", "syn 1.0.109", @@ -3270,24 +3353,24 @@ dependencies = [ [[package]] name = "scale-encode" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d70cb4b29360105483fac1ed567ff95d65224a14dd275b6303ed0a654c78de5" +checksum = "628800925a33794fb5387781b883b5e14d130fece9af5a63613867b8de07c5c7" dependencies = [ "derive_more", "parity-scale-codec", "primitive-types", "scale-bits", "scale-encode-derive", - "scale-info", + "scale-type-resolver", "smallvec", ] [[package]] name = "scale-encode-derive" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "995491f110efdc6bea96d6a746140e32bfceb4ea47510750a5467295a4707a25" +checksum = "7a304e1af7cdfbe7a24e08b012721456cc8cecdedadc14b3d10513eada63233c" dependencies = [ "darling 0.14.4", "proc-macro-crate 1.3.1", @@ -3298,9 +3381,9 @@ dependencies = [ [[package]] name = "scale-info" -version = "2.10.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7d66a1128282b7ef025a8ead62a4a9fcf017382ec53b8ffbf4d7bf77bd3c60" +checksum = "788745a868b0e751750388f4e6546eb921ef714a4317fa6954f7cde114eb2eb7" dependencies = [ "bitvec", "cfg-if", @@ -3312,9 +3395,9 @@ dependencies = [ [[package]] name = "scale-info-derive" -version = "2.10.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abf2c68b89cafb3b8d918dd07b42be0da66ff202cf1155c5739a4e0c1ea0dc19" +checksum = "7dc2f4e8bc344b9fc3d5f74f72c2e55bfc38d28dc2ebc69c194a3df424e4d9ac" dependencies = [ "proc-macro-crate 1.3.1", "proc-macro2", @@ -3323,23 +3406,33 @@ dependencies = [ ] [[package]] -name = "scale-typegen" +name = "scale-type-resolver" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00860983481ac590ac87972062909bef0d6a658013b592ccc0f2feb272feab11" +checksum = "10b800069bfd43374e0f96f653e0d46882a2cb16d6d961ac43bea80f26c76843" +dependencies = [ + "scale-info", + "smallvec", +] + +[[package]] +name = "scale-typegen" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d6108609f017741c78d35967c7afe4aeaa3999b848282581041428e10d23b63" dependencies = [ "proc-macro2", "quote", "scale-info", - "syn 2.0.52", + "syn 2.0.55", "thiserror", ] [[package]] name = "scale-value" -version = "0.13.0" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58223c7691bf0bd46b43c9aea6f0472d1067f378d574180232358d7c6e0a8089" +checksum = "c07ccfee963104335c971aaf8b7b0e749be8569116322df23f1f75c4ca9e4a28" dependencies = [ "base58", "blake2", @@ -3351,6 +3444,7 @@ dependencies = [ "scale-decode", "scale-encode", "scale-info", + "scale-type-resolver", "serde", "yap", ] @@ -3410,6 +3504,21 @@ dependencies = [ "untrusted", ] +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "serdect", + "subtle", + "zeroize", +] + [[package]] name = "secp256k1" version = "0.28.2" @@ -3492,14 +3601,14 @@ checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] name = "serde_json" -version = "1.0.114" +version = "1.0.115" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" +checksum = "12dc5c46daa8e9fdf4f5e71b6cf9a53f2487da0e86e55808e2d35539666497dd" dependencies = [ "itoa", "ryu", @@ -3508,9 +3617,9 @@ dependencies = [ [[package]] name = "serde_path_to_error" -version = "0.1.15" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebd154a240de39fdebcf5775d2675c204d7c13cf39a4c697be6493c8e734337c" +checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6" dependencies = [ "itoa", "serde", @@ -3537,6 +3646,16 @@ dependencies = [ "serde", ] +[[package]] +name = "serdect" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a84f14a19e9a014bb9f4512488d9829a68e04ecabffb0f9904cd1ace94598177" +dependencies = [ + "base16ct", + "serde", +] + [[package]] name = "sha-1" version = "0.9.8" @@ -3608,6 +3727,7 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ + "digest 0.10.7", "rand_core 0.6.4", ] @@ -3619,9 +3739,9 @@ checksum = "620a1d43d70e142b1d46a929af51d44f383db9c7a2ec122de2cd992ccfcf3c18" [[package]] name = "siphasher" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54ac45299ccbd390721be55b412d41931911f654fa99e2cb8bfb57184b2061fe" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" [[package]] name = "slab" @@ -3634,9 +3754,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.13.1" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "smol" @@ -3689,7 +3809,7 @@ dependencies = [ "num-bigint", "num-rational", "num-traits", - "pbkdf2 0.12.2", + "pbkdf2", "pin-project", "poly1305", "rand", @@ -3773,9 +3893,9 @@ dependencies = [ [[package]] name = "sp-application-crypto" -version = "30.0.0" +version = "33.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e4fe7a9b7fa9da76272b201e2fb3c7900d97d32a46b66af9a04dad457f73c71" +checksum = "13ca6121c22c8bd3d1dce1f05c479101fd0d7b159bef2a3e8c834138d839c75c" dependencies = [ "parity-scale-codec", "scale-info", @@ -3787,9 +3907,9 @@ dependencies = [ [[package]] name = "sp-arithmetic" -version = "23.0.0" +version = "25.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f42721f072b421f292a072e8f52a3b3c0fbc27428f0c9fe24067bc47046bad63" +checksum = "910c07fa263b20bf7271fdd4adcb5d3217dfdac14270592e0780223542e7e114" dependencies = [ "integer-sqrt", "num-traits", @@ -3802,12 +3922,11 @@ dependencies = [ [[package]] name = "sp-core" -version = "28.0.0" +version = "31.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f230cb12575455070da0fc174815958423a0b9a641d5e304a9457113c7cb4007" +checksum = "26d7a0fd8f16dcc3761198fc83be12872f823b37b749bc72a3a6a1f702509366" dependencies = [ "array-bytes", - "bip39", "bitflags 1.3.2", "blake2", "bounded-collections", @@ -3819,9 +3938,11 @@ dependencies = [ "hash256-std-hasher", "impl-serde", "itertools 0.10.5", + "k256", "libsecp256k1", "log", "merlin", + "parity-bip39", "parity-scale-codec", "parking_lot", "paste", @@ -3832,7 +3953,7 @@ dependencies = [ "secp256k1", "secrecy", "serde", - "sp-core-hashing", + "sp-crypto-hashing", "sp-debug-derive", "sp-externalities", "sp-runtime-interface", @@ -3847,10 +3968,10 @@ dependencies = [ ] [[package]] -name = "sp-core-hashing" -version = "15.0.0" +name = "sp-crypto-hashing" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e0f4990add7b2cefdeca883c0efa99bb4d912cb2196120e1500c0cc099553b0" +checksum = "bc9927a7f81334ed5b8a98a4a978c81324d12bd9713ec76b5c68fd410174c5eb" dependencies = [ "blake2b_simd", "byteorder", @@ -3868,14 +3989,14 @@ checksum = "48d09fa0a5f7299fb81ee25ae3853d26200f7a348148aed6de76be905c007dbe" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] name = "sp-externalities" -version = "0.25.0" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63867ec85950ced90d4ab1bba902a47db1b1efdf2829f653945669b2bb470a9c" +checksum = "a1d6a4572eadd4a63cff92509a210bf425501a0c5e76574b30a366ac77653787" dependencies = [ "environmental", "parity-scale-codec", @@ -3885,18 +4006,20 @@ dependencies = [ [[package]] name = "sp-io" -version = "30.0.0" +version = "33.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c55f26d89feedaf0faf81688b6e1e1e81329cd8b4c6a4fd6c5b97ed9dd068b8a" +checksum = "3e09bba780b55bd9e67979cd8f654a31e4a6cf45426ff371394a65953d2177f2" dependencies = [ "bytes", "ed25519-dalek", "libsecp256k1", "log", "parity-scale-codec", + "polkavm-derive 0.9.1", "rustversion", "secp256k1", "sp-core", + "sp-crypto-hashing", "sp-externalities", "sp-keystore", "sp-runtime-interface", @@ -3910,15 +4033,14 @@ dependencies = [ [[package]] name = "sp-keystore" -version = "0.34.0" +version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96806a28a62ed9ddecd0b28857b1344d029390f7c5c42a2ff9199cbf5638635c" +checksum = "bdbab8b61bd61d5f8625a0c75753b5d5a23be55d3445419acd42caf59cf6236b" dependencies = [ "parity-scale-codec", "parking_lot", "sp-core", "sp-externalities", - "thiserror", ] [[package]] @@ -3934,9 +4056,9 @@ dependencies = [ [[package]] name = "sp-runtime" -version = "31.0.1" +version = "34.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3bb49a4475d390198dfd3d41bef4564ab569fbaf1b5e38ae69b35fc01199d91" +checksum = "ec3cb126971e7db2f0fcf8053dce740684c438c7180cfca1959598230f342c58" dependencies = [ "docify", "either", @@ -3959,13 +4081,14 @@ dependencies = [ [[package]] name = "sp-runtime-interface" -version = "24.0.0" +version = "26.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f66b66d8cec3d785fa6289336c1d9cbd4305d5d84f7134378c4d79ed7983e6fb" +checksum = "e48a675ea4858333d4d755899ed5ed780174aa34fec15953428d516af5452295" dependencies = [ "bytes", "impl-trait-for-tuples", "parity-scale-codec", + "polkavm-derive 0.8.0", "primitive-types", "sp-externalities", "sp-runtime-interface-proc-macro", @@ -3978,23 +4101,23 @@ dependencies = [ [[package]] name = "sp-runtime-interface-proc-macro" -version = "17.0.0" +version = "18.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfaf6e85b2ec12a4b99cd6d8d57d083e30c94b7f1b0d8f93547121495aae6f0c" +checksum = "0195f32c628fee3ce1dfbbf2e7e52a30ea85f3589da9fe62a8b816d70fc06294" dependencies = [ "Inflector", "expander", "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] name = "sp-state-machine" -version = "0.35.0" +version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "718c779ad1d6fcc0be64c7ce030b33fa44b5c8914b3a1319ef63bb5f27fb98df" +checksum = "1eae0eac8034ba14437e772366336f579398a46d101de13dbb781ab1e35e67c5" dependencies = [ "hash-db", "log", @@ -4020,9 +4143,9 @@ checksum = "12f8ee986414b0a9ad741776762f4083cd3a5128449b982a3919c4df36874834" [[package]] name = "sp-storage" -version = "19.0.0" +version = "20.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fb92d7b24033a8a856d6e20dd980b653cbd7af7ec471cc988b1b7c1d2e3a32b" +checksum = "8dba5791cb3978e95daf99dad919ecb3ec35565604e88cd38d805d9d4981e8bd" dependencies = [ "impl-serde", "parity-scale-codec", @@ -4047,9 +4170,9 @@ dependencies = [ [[package]] name = "sp-trie" -version = "29.0.0" +version = "32.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e4d24d84a0beb44a71dcac1b41980e1edf7fb722c7f3046710136a283cd479b" +checksum = "f1aa91ad26c62b93d73e65f9ce7ebd04459c4bad086599348846a81988d6faa4" dependencies = [ "ahash 0.8.11", "hash-db", @@ -4086,9 +4209,9 @@ dependencies = [ [[package]] name = "sp-weights" -version = "27.0.0" +version = "30.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e874bdf9dd3fd3242f5b7867a4eaedd545b02f29041a46d222a9d9d5caaaa5c" +checksum = "9af6c661fe3066b29f9e1d258000f402ff5cc2529a9191972d214e5871d0ba87" dependencies = [ "bounded-collections", "parity-scale-codec", @@ -4118,9 +4241,9 @@ dependencies = [ [[package]] name = "ss58-registry" -version = "1.46.0" +version = "1.47.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1114ee5900b8569bbc8b1a014a942f937b752af4b44f4607430b5f86cedaac0" +checksum = "4743ce898933fbff7bbf414f497c459a782d496269644b3d650a398ae6a487ba" dependencies = [ "Inflector", "num-format", @@ -4151,14 +4274,14 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "substrate-bip39" -version = "0.4.6" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a7590dc041b9bc2825e52ce5af8416c73dbe9d0654402bfd4b4941938b94d8f" +checksum = "a2b564c293e6194e8b222e52436bcb99f60de72043c7f845cf6c4406db4df121" dependencies = [ - "hmac 0.11.0", - "pbkdf2 0.8.0", + "hmac 0.12.1", + "pbkdf2", "schnorrkel", - "sha2 0.9.9", + "sha2 0.10.8", "zeroize", ] @@ -4170,9 +4293,9 @@ checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" [[package]] name = "subxt" -version = "0.34.0" +version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3323d5c27898b139d043dc1ee971f602f937b99354ee33ee933bd90e0009fbd" +checksum = "7542baa3949f5dbddd85491e8eb75f4935ca21fbb80781fb86c5b32e317bb4bf" dependencies = [ "async-trait", "base58", @@ -4184,9 +4307,10 @@ dependencies = [ "hex", "impl-serde", "instant", - "jsonrpsee 0.21.0", + "jsonrpsee", "parity-scale-codec", "primitive-types", + "reconnecting-jsonrpsee-ws-client", "scale-bits", "scale-decode", "scale-encode", @@ -4195,7 +4319,7 @@ dependencies = [ "serde", "serde_json", "sp-core", - "sp-core-hashing", + "sp-crypto-hashing", "sp-runtime", "subxt-lightclient", "subxt-macro", @@ -4208,30 +4332,30 @@ dependencies = [ [[package]] name = "subxt-codegen" -version = "0.34.0" +version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d0e58c3f88651cff26aa52bae0a0a85f806a2e923a20eb438c16474990743ea" +checksum = "2afcd8f197bd7fc8bc215b381d6464005dd0a594e1ec3ff5ef310930019ac69f" dependencies = [ "frame-metadata 16.0.0", "heck", "hex", - "jsonrpsee 0.21.0", + "jsonrpsee", "parity-scale-codec", "proc-macro2", "quote", "scale-info", "scale-typegen", "subxt-metadata", - "syn 2.0.52", + "syn 2.0.55", "thiserror", "tokio", ] [[package]] name = "subxt-lightclient" -version = "0.34.0" +version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecec7066ba7bc0c3608fcd1d0c7d9584390990cd06095b6ae4f114f74c4b8550" +checksum = "c6e8ec205884dfb7aae0ec9755233c2006b54d9e0135efedc6095682f01f3437" dependencies = [ "futures", "futures-util", @@ -4246,9 +4370,9 @@ dependencies = [ [[package]] name = "subxt-macro" -version = "0.34.0" +version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "365251668613323064803427af8c7c7bc366cd8b28e33639640757669dafebd5" +checksum = "e48afc319f8f413270da8cefda8141228bd1b3d17394f6daccfc03b7623fe24f" dependencies = [ "darling 0.20.8", "parity-scale-codec", @@ -4256,20 +4380,21 @@ dependencies = [ "quote", "scale-typegen", "subxt-codegen", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] name = "subxt-metadata" -version = "0.34.0" +version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c02aca8d39a1f6c55fff3a8fd81557d30a610fedc1cef03f889a81bc0f8f0b52" +checksum = "2cfbc0746a40aec24e535973d0a158de4968ea3f23399106d2bb63eb41900d8d" dependencies = [ + "derive_more", "frame-metadata 16.0.0", + "hashbrown 0.14.3", "parity-scale-codec", "scale-info", - "sp-core-hashing", - "thiserror", + "sp-crypto-hashing", ] [[package]] @@ -4285,9 +4410,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.52" +version = "2.0.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07" +checksum = "002a1b3dbf967edfafc32655d0f377ab0bb7b994aa1d32c8cc7e9b8bf3ebb8f0" dependencies = [ "proc-macro2", "quote", @@ -4300,6 +4425,12 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" +[[package]] +name = "sync_wrapper" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384595c11a4e2969895cad5a8c4029115f5ab956a9e5ef4de79d11a426e5f20c" + [[package]] name = "tap" version = "1.0.1" @@ -4323,22 +4454,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.57" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b" +checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.57" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81" +checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] @@ -4377,7 +4508,6 @@ dependencies = [ "libc", "mio", "num_cpus", - "parking_lot", "pin-project-lite", "signal-hook-registry", "socket2", @@ -4393,7 +4523,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] @@ -4423,16 +4553,16 @@ version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" dependencies = [ - "rustls 0.22.2", + "rustls 0.22.3", "rustls-pki-types", "tokio", ] [[package]] name = "tokio-stream" -version = "0.1.14" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" +checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" dependencies = [ "futures-core", "pin-project-lite", @@ -4452,21 +4582,20 @@ dependencies = [ "futures-util", "hashbrown 0.14.3", "pin-project-lite", - "slab", "tokio", "tracing", ] [[package]] name = "toml" -version = "0.8.10" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a9aad4a3066010876e8dcf5a8a06e70a558751117a145c6ce2b82c2e2054290" +checksum = "e9dd1545e8208b4a5af1aa9bbd0b4cf7e9ea08fabc5d0a5c67fcaafa17433aa3" dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.22.6", + "toml_edit 0.22.9", ] [[package]] @@ -4484,7 +4613,7 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.2.5", + "indexmap 2.2.6", "toml_datetime", "winnow 0.5.40", ] @@ -4495,7 +4624,7 @@ version = "0.20.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81" dependencies = [ - "indexmap 2.2.5", + "indexmap 2.2.6", "toml_datetime", "winnow 0.5.40", ] @@ -4506,18 +4635,18 @@ version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" dependencies = [ - "indexmap 2.2.5", + "indexmap 2.2.6", "toml_datetime", "winnow 0.5.40", ] [[package]] name = "toml_edit" -version = "0.22.6" +version = "0.22.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c1b5fd4128cc8d3e0cb74d4ed9a9cc7c7284becd4df68f5f940e1ad123606f6" +checksum = "8e40bb779c5187258fd7aad0eb68cb8706a0a81fa712fbea808ab43c4b8374c4" dependencies = [ - "indexmap 2.2.5", + "indexmap 2.2.6", "serde", "serde_spanned", "toml_datetime", @@ -4572,7 +4701,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] @@ -4729,6 +4858,18 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +[[package]] +name = "ureq" +version = "2.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11f214ce18d8b2cbe84ed3aa6486ed3f5b285cf8d8fbdbce9f3f767a724adc35" +dependencies = [ + "base64 0.21.7", + "log", + "once_cell", + "url", +] + [[package]] name = "url" version = "2.5.0" @@ -4758,6 +4899,12 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "vsimd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" + [[package]] name = "w3f-bls" version = "0.1.3" @@ -4828,7 +4975,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", "wasm-bindgen-shared", ] @@ -4850,7 +4997,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -5343,7 +5490,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] @@ -5363,5 +5510,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", ] diff --git a/Cargo.toml b/Cargo.toml index b63f785..50a8898 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "kalatori" authors = ["Alzymologist Oy "] -version = "0.1.2" +version = "0.2.0" edition = "2021" description = "A gateway daemon for Kalatori." license = "GPL-3.0-or-later" @@ -10,22 +10,24 @@ readme = true keywords = ["substrate", "blockchain", "finance", "service", "middleware"] categories = ["finance"] -[workspace] -members = ["kalatori-ah"] - [dependencies] -tokio = { version = "1", features = ["full"] } -tokio-util = { version = "0.7", features = ["full"] } +env_logger = { version = "0.11", default-features = false, features = ["humantime", "auto-color"] } +toml_edit = { version = "0.22", default-features = false, features = ["parse", "serde"] } +axum = { version = "0.7", default-features = false, features = ["tokio", "http1", "query", "json", "matched-path"] } + +subxt = { version = "=0.35", features = ["substrate-compat", "unstable-reconnecting-rpc-client"] } +tokio-util = { version = "0.7", features = ["rt"] } +tokio = { version = "1", features = ["signal"] } + +ureq = { version = "2", default-features = false } +names = { version = "0.14", default-features = false } + +hex-simd = "0.8" +serde = "1" 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"] } +redb = "2" +humantime = "2" [profile.release] strip = true @@ -43,3 +45,5 @@ shadow_reuse = "warn" shadow_same = "warn" shadow_unrelated = "warn" cargo_common_metadata = "warn" +arithmetic_side_effects = "warn" +pedantic = "warn" diff --git a/kalatori-ah/Cargo.toml b/kalatori-ah/Cargo.toml deleted file mode 100644 index 8a3230a..0000000 --- a/kalatori-ah/Cargo.toml +++ /dev/null @@ -1,42 +0,0 @@ -[package] -name = "kalatori-ah" -authors = ["Alzymologist Oy "] -version = "0.1.2" -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.10" -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/kalatori-test.toml b/kalatori-test.toml new file mode 100644 index 0000000..2832a8a --- /dev/null +++ b/kalatori-test.toml @@ -0,0 +1,31 @@ +recipient = "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty" +account-lifetime = 3600 # 1 hour. +depth = 86400 # 1 day. +debug = 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://polkadot-asset-hub-rpc.polkadot.io", + "wss://statemint-rpc.dwellir.com", +] +multi-location-assets = true + +[[chain.asset]] +name = "JOE" +id = 8 + +[[chain.asset]] +name = "TEST" +id = 1234 diff --git a/kalatori.toml b/kalatori.toml new file mode 100644 index 0000000..1a10b9d --- /dev/null +++ b/kalatori.toml @@ -0,0 +1,36 @@ +recipient = "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty" +account-lifetime = 86400 # 1 day. +depth = 604800 # 1 week. + +[[chain]] +name = "polkadot" +native-token = "DOT" +decimals = 10 +endpoints = [ + "wss://rpc.polkadot.io", + "wss://1rpc.io/dot", +] + +[[chain]] +name = "assethub-polkadot" +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 + +[[chain]] +name = "kusama" +native-token = "KSM" +decimals = 12 +endpoints = [ + "wss://kusama-rpc.polkadot.io", + "wss://1rpc.io/ksm", +] 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/src/callback.rs b/src/callback.rs new file mode 100644 index 0000000..1fa0c7e --- /dev/null +++ b/src/callback.rs @@ -0,0 +1,2 @@ + +pub const MODULE: &str = module_path!(); diff --git a/src/database.rs b/src/database.rs index b06187b..3439919 100644 --- a/src/database.rs +++ b/src/database.rs @@ -1,80 +1,102 @@ -use crate::{ - rpc::{ChainProperties, EndpointProperties}, - Account, Balance, BlockNumber, RuntimeConfig, Version, DATABASE_VERSION, OVERRIDE_RPC, SEED, -}; +use crate::{rpc::ConnectedChain, AssetId, Balance, BlockNumber, Nonce, Timestamp}; 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}, - task, +use redb::{Database, ReadableTable, Table, TableDefinition, TableHandle, TypeName, Value}; +use std::{collections::HashMap, sync::Arc}; +use subxt::ext::{ + codec::{Compact, Decode, Encode}, + sp_core::sr25519::{Pair, Public}, }; -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"); +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 HIT_LIST: TableDefinition<'_, Timestamp, (ChainHash, AssetId, Account)> = + TableDefinition::new("hit_list"); + +const ACCOUNTS: &str = "accounts"; -// Keys +type ACCOUNTS_KEY = (Option, Account); +type ACCOUNTS_VALUE = InvoiceKey; + +const TRANSACTIONS: &str = "transactions"; + +type TRANSACTIONS_KEY = BlockNumber; +type TRANSACTIONS_VALUE = (Account, Nonce, Transfer); + +// `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"; // Slots -#[derive(Debug, Encode, Decode)] +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]; +type Account = [u8; 32]; + +#[derive(Encode, Decode)] #[codec(crate = subxt::ext::codec)] -pub struct Invoice { - pub recipient: Account, - pub order: Order, - pub status: InvoiceStatus, +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)] +#[codec(crate = subxt::ext::codec)] +struct DaemonInfo { + chains: Vec<(String, ChainProperties)>, + current_key: PublicSlot, + old_keys_death_timestamps: Vec<(PublicSlot, Timestamp)>, +} + +#[derive(Encode, Decode)] +#[codec(crate = subxt::ext::codec)] +struct ChainProperties { + genesis: BlockHash, + hash: ChainHash, + kind: ChainKind, } -#[derive(Debug, Encode, Decode)] +#[derive(Encode, Decode)] +#[codec(crate = subxt::ext::codec)] +struct Transfer(Option>, #[codec(compact)] BalanceSlot); + +#[derive(Encode, Decode, Debug)] #[codec(crate = subxt::ext::codec)] -pub enum InvoiceStatus { - Unpaid(Balance), - Paid(Balance), +struct Invoice { + derivation: (PublicSlot, Derivation), + paid: bool, + #[codec(compact)] + timestamp: Timestamp, + #[codec(compact)] + price: BalanceSlot, + callback: String, + message: String, + asset: Option>, + transactions: Vec, } -impl RedbValue for Invoice { +#[derive(Encode, Decode, Debug)] +#[codec(crate = subxt::ext::codec)] +struct TransferTx { + encoded: Vec, + recipient: Account, + exact_amount: Option>, +} + +impl Value for Invoice { type SelfType<'a> = Self; type AsBytes<'a> = Vec; @@ -83,7 +105,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, { @@ -99,278 +121,256 @@ impl RedbValue for 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, +pub struct State { + db: Database, + // properties: Arc>, + // pair: Pair, + // rpc: String, + // destination: Option, } -impl Database { +impl State { 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 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") + current_pair: (Pair, Public), + old_pairs: HashMap, + connected_chains: HashMap, + ) -> Result> { + // let mut database = Database::create(path_option.unwrap()).unwrap(); + + // 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, + // )) + + todo!() } -} - -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(()) - } + // 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 + // } } -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") -} +// 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/src/lib.rs b/src/lib.rs deleted file mode 100644 index 2c5fc9e..0000000 --- a/src/lib.rs +++ /dev/null @@ -1,266 +0,0 @@ -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(), - } -} diff --git a/src/main.rs b/src/main.rs index 5bef903..4c3e15b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,450 @@ -use anyhow::Result; +use anyhow::{Context, Error, Result}; +use env_logger::{Builder, Env}; +use log::LevelFilter; +use serde::Deserialize; +use std::{ + collections::HashMap, + env::{self, VarError}, + fs, + future::Future, + net::{IpAddr, Ipv4Addr, SocketAddr}, + ops::Deref, + panic, str, +}; +use subxt::{ + config::PolkadotExtrinsicParams, + ext::{ + codec::{Encode, Output}, + scale_decode::DecodeAsType, + scale_encode::{self, EncodeAsType, TypeResolver}, + sp_core::{ + crypto::{AccountId32, Ss58Codec}, + sr25519::Pair, + Pair as _, + }, + }, + PolkadotConfig, +}; +use tokio::{ + signal, + sync::mpsc::{self, UnboundedSender}, +}; +use tokio_util::{sync::CancellationToken, task::TaskTracker}; +use toml_edit::de; -fn main() -> Result<()> { - kalatori::main() +mod callback; +mod database; +mod rpc; +mod server; + +const CONFIG: &str = "KALATORI_CONFIG"; +const LOG: &str = "KALATORI_LOG"; +const LOG_STYLE: &str = "KALATORI_LOG_STYLE"; +const SEED: &str = "KALATORI_SEED"; +const OLD_SEED: &str = "KALATORI_OLD_SEED_"; + +const DB_VERSION: Version = 0; + +const DEFAULT_CONFIG: &str = "kalatori.toml"; +const DEFAULT_SOCKET: SocketAddr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 16726); +const DEFAULT_DATABASE: &str = "kalatori.xlsx"; + +type AssetId = u32; +type Decimals = u8; +type BlockNumber = u64; +type ExtrinsicIndex = u32; +type Version = u64; +type AccountId = ::AccountId; +type Nonce = u32; +type Timestamp = u64; +type PalletIndex = u8; +type BlockHash = ::Hash; +type OnlineClient = subxt::OnlineClient; + +struct RuntimeConfig; + +impl subxt::Config for RuntimeConfig { + type Hash = ::Hash; + type AccountId = AccountId32; + type Address = ::Address; + type Signature = ::Signature; + type Hasher = ::Hasher; + type Header = ::Header; + type ExtrinsicParams = PolkadotExtrinsicParams; + type AssetId = u32; +} + +enum Asset { + Id(AssetId), + MultiLocation(PalletIndex, AssetId), +} + +impl EncodeAsType for Asset { + fn encode_as_type_to( + &self, + type_id: &R::TypeId, + types: &R, + out: &mut Vec, + ) -> Result<(), scale_encode::Error> { + match self { + Self::Id(id) => id.encode_as_type_to(type_id, types, out), + Self::MultiLocation(assets_pallet, asset_id) => { + MultiLocation::new(*assets_pallet, *asset_id).encode_as_type_to(type_id, types, out) + } + } + } +} + +impl Encode for Asset { + fn size_hint(&self) -> usize { + match self { + Self::Id(id) => id.size_hint(), + Self::MultiLocation(assets_pallet, asset_id) => { + MultiLocation::new(*assets_pallet, *asset_id).size_hint() + } + } + } + + fn encode_to(&self, dest: &mut T) { + match self { + Self::Id(id) => id.encode_to(dest), + Self::MultiLocation(assets_pallet, asset_id) => { + MultiLocation::new(*assets_pallet, *asset_id).encode_to(dest); + } + } + } +} + +#[derive(EncodeAsType, DecodeAsType, Encode)] +#[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 { + parents: u8, + interior: Junctions, +} + +impl MultiLocation { + fn new(assets_pallet: PalletIndex, asset_id: AssetId) -> Self { + Self { + parents: 0, + interior: Junctions::X2( + Junction::PalletInstance(assets_pallet), + Junction::GeneralIndex(asset_id.into()), + ), + } + } +} + +#[derive(EncodeAsType, DecodeAsType, Encode)] +#[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 { + #[codec(index = 2)] + X2(Junction, Junction), +} + +#[derive(EncodeAsType, DecodeAsType, Encode)] +#[encode_as_type(crate_path = "subxt::ext::scale_encode")] +#[decode_as_type(crate_path = "subxt::ext::scale_decode")] +#[codec(crate = subxt::ext::codec)] +pub enum Junction { + #[codec(index = 4)] + PalletInstance(PalletIndex), + #[codec(index = 5)] + GeneralIndex(u128), +} + +#[tokio::main] +#[allow(clippy::too_many_lines)] +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(callback::MODULE, LevelFilter::Info) + .filter_module(database::MODULE, LevelFilter::Info) + .filter_module(rpc::MODULE, LevelFilter::Info) + .filter_module(server::MODULE, LevelFilter::Info) + .filter_module(env!("CARGO_PKG_NAME"), LevelFilter::Info) + } + .parse_env(Env::from(LOG).write_style(LOG_STYLE)) + .init(); + + let pair = Pair::from_string( + &env::var(SEED).with_context(|| format!("failed to read `{SEED}`"))?, + None, + ) + .with_context(|| format!("failed to generate a key pair from `{SEED}`"))?; + let pair_public = pair.public(); + + let mut old_seeds = HashMap::new(); + + for (raw_key, raw_value) in env::vars_os() { + let raw_key_bytes = raw_key.as_encoded_bytes(); + + if let Some(stripped_raw_key) = raw_key_bytes.strip_prefix(OLD_SEED.as_bytes()) { + let key = str::from_utf8(stripped_raw_key) + .context("failed to read an old seed environment variable name")?; + let value = raw_value + .to_str() + .with_context(|| format!("failed to read a seed phrase from `{OLD_SEED}{key}`"))?; + let old_pair = Pair::from_string(value, None) + .with_context(|| format!("failed to generate a key pair from `{OLD_SEED}{key}`"))?; + let old_pair_public = old_pair.public(); + + if old_pair_public == pair_public { + anyhow::bail!("public key generated from `{OLD_SEED}{key}` equals the one generated from `{SEED}`"); + } + + old_seeds.insert(key.to_owned(), (old_pair, old_pair_public)); + } + } + + let config_path = env::var(CONFIG).or_else(|error| match error { + VarError::NotUnicode(_) => Err(error).with_context(|| format!("failed to read `{CONFIG}`")), + VarError::NotPresent => { + log::debug!( + "`{CONFIG}` isn't present, using the default value instead: {DEFAULT_CONFIG:?}." + ); + + Ok(DEFAULT_CONFIG.into()) + } + })?; + let unparsed_config = fs::read_to_string(&config_path) + .with_context(|| format!("failed to read a config file at {config_path:?}"))?; + let config: Config = de::from_str(&unparsed_config) + .with_context(|| format!("failed to parse the config at {config_path:?}"))?; + + let host = if let Some(unparsed_host) = config.host { + unparsed_host + .parse() + .context("failed to convert `host` from the config to a socket address")? + } else { + DEFAULT_SOCKET + }; + + let debug = config.debug.unwrap_or_default(); + + let database_path = 'database: { + if debug { + if config.in_memory_db.unwrap_or_default() { + break 'database None; + } + } else if config.in_memory_db.is_some() { + log::warn!("`in_memory_db` is set in the config but ignored because `debug` isn't set"); + } + + Some(config.database.unwrap_or_else(|| { + log::debug!( + "`database` isn't present in the config, using the default value instead: {DEFAULT_DATABASE:?}." + ); + + DEFAULT_DATABASE.to_owned() + })) + }; + + let recipient = AccountId::from_string(&config.recipient) + .context("failed to convert `recipient` from the config to an account address")?; + + 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 shutdown_notification_for_panic = shutdown_notification.clone(); + + panic::set_hook(Box::new(move |panic_info| { + let at = panic_info + .location() + .map(|location| format!(" at `{location}`")) + .unwrap_or_default(); + let panic_message = panic_info + .payload() + .downcast_ref::<&str>() + .map_or_else(|| ".".into(), |message| format!(":\n{message}\n")); + + log::error!( + "A panic detected{at}{panic_message}\nThis is a bug. Please report it at {}.", + env!("CARGO_PKG_REPOSITORY") + ); + + shutdown_notification_for_panic.cancel(); + })); + + let chains = rpc::prepare(config.chain) + .await + .context("failed while preparing the RPC 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) + .await + .context("failed to initialise the server module")?; + + let task_tracker = TaskTracker::new(); + + task_tracker.close(); + task_tracker.spawn(try_task( + "the shutdown listener", + shutdown_listener(shutdown_notification.clone()), + error_tx.clone(), + )); + // task_tracker.spawn(shutdown( + // processor.ignite(last_saved_block, task_tracker.clone(), error_tx.clone()), + // error_tx, + // )); + task_tracker.spawn(try_task("the server module", server, error_tx)); + + while let Some((from, error)) = error_rx.recv().await { + log::error!("Received a fatal error from {from}!\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 try_task<'a>( + name: &'a str, + task: impl Future>, + error_tx: UnboundedSender<(&'a str, Error)>, +) { + match task.await { + Ok(shutdown_message) if !shutdown_message.is_empty() => log::info!("{shutdown_message}"), + Err(error) => error_tx + .send((name, error)) + .expect("error channel shouldn't be dropped/closed"), + _ => {} + } +} + +async fn shutdown_listener(shutdown_notification: CancellationToken) -> Result { + 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(); + } + () = shutdown_notification.cancelled() => {} + } + + Ok("The shutdown signal listener is shut down.".into()) +} + +#[derive(Deserialize)] +#[serde(rename_all = "kebab-case")] +struct Config { + recipient: String, + account_lifetime: Timestamp, + depth: Timestamp, + host: Option, + database: Option, + remark: Option, + debug: Option, + in_memory_db: Option, + chain: Vec, +} + +#[derive(Deserialize)] +#[serde(rename_all = "kebab-case")] +struct Chain { + name: String, + endpoints: Vec, + #[serde(flatten)] + native_token: Option, + asset: Option>, + multi_location_assets: Option, +} + +#[derive(Deserialize)] +#[serde(rename_all = "kebab-case")] +struct NativeToken { + native_token: String, + decimals: Decimals, +} + +#[derive(Deserialize)] +struct AssetInfo { + name: String, + id: AssetId, +} + +#[derive(Debug)] +struct Balance(u128); + +impl Deref for Balance { + type Target = u128; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl Balance { + fn format(&self, decimals: Decimals) -> f64 { + #[allow(clippy::cast_precision_loss)] + let float = **self as f64; + + float / decimal_exponent_product(decimals) + } + + fn parse(float: f64, decimals: Decimals) -> Self { + let parsed_float = (float * decimal_exponent_product(decimals)).round(); + + #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] + Self(parsed_float as _) + } +} + +fn decimal_exponent_product(decimals: Decimals) -> f64 { + 10f64.powi(decimals.into()) +} + +#[cfg(test)] +#[test] +#[allow( + clippy::inconsistent_digit_grouping, + clippy::unreadable_literal, + clippy::float_cmp +)] +fn balance_insufficient_precision() { + const DECIMALS: 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/rpc.rs b/src/rpc.rs index 0011868..5a66e0a 100644 --- a/src/rpc.rs +++ b/src/rpc.rs @@ -1,102 +1,79 @@ use crate::{ - database::{Database, Invoice, InvoiceStatus, ReadInvoices}, - shutdown, Account, Balance, BlockNumber, Decimals, Hash, Nonce, OnlineClient, RuntimeConfig, - DECIMALS, SCANNER_TO_LISTENER_SWITCH_POINT, + AssetId, Balance, BlockHash, BlockNumber, Chain, Decimals, OnlineClient, PalletIndex, + RuntimeConfig, }; 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, + collections::HashMap, + fmt::{self, Debug, Formatter}, }; use subxt::{ backend::{ - legacy::{LegacyBackend, LegacyRpcMethods}, - rpc::{RpcClient, RpcSubscription}, - Backend, BackendExt, RuntimeVersion, - }, - blocks::{Block, BlocksClient}, - config::{ - signed_extensions::{ChargeTransactionPaymentParams, CheckMortalityParams}, - Header, + legacy::LegacyRpcMethods, + rpc::{reconnecting_rpc_client::Client, RpcClient}, }, 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, + dynamic::{self, At}, + ext::{scale_decode::DecodeAsType, sp_core::crypto::Ss58AddressFormat}, + storage::Storage, }; -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"; +// 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) -} +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_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, @@ -109,1091 +86,1268 @@ fn fetch_constant( .with_context(|| format!("failed to decode the constant {constant:?}")) } +#[derive(Debug)] pub struct ChainProperties { pub address_format: Ss58AddressFormat, pub existential_deposit: Balance, - pub decimals: Decimals, + pub assets: HashMap, pub block_hash_count: BlockNumber, } +#[derive(Debug)] +pub struct AssetProperties { + pub min_balance: Balance, + pub decimals: Decimals, +} + impl ChainProperties { - fn fetch_only_constants( + async fn fetch( constants: &ConstantsClient, - decimals: Decimals, + storage_finalized: Storage, + assets: Vec, ) -> Result { const ADDRESS_PREFIX: (&str, &str) = (SYSTEM, "SS58Prefix"); const EXISTENTIAL_DEPOSIT: (&str, &str) = (BALANCES, "ExistentialDeposit"); const BLOCK_HASH_COUNT: (&str, &str) = (SYSTEM, "BlockHashCount"); + let ex_dep: u128 = fetch_constant(constants, EXISTENTIAL_DEPOSIT)?; + let mut assets_props = HashMap::new(); + + for asset in assets { + assets_props.insert( + asset, + AssetProperties { + min_balance: fetch_min_balance(storage_finalized.clone(), asset).await?, + decimals: fetch_decimals(storage_finalized.clone(), asset).await?, + }, + ); + } + Ok(Self { address_format: Ss58AddressFormat::custom(fetch_constant(constants, ADDRESS_PREFIX)?), - existential_deposit: fetch_constant(constants, EXISTENTIAL_DEPOSIT)?, + existential_deposit: Balance(ex_dep), + assets: assets_props, 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>, -} +async fn fetch_min_balance( + storage_finalized: Storage, + asset: AssetId, +) -> Result { + const ASSET: &str = "Asset"; + const MIN_BALANCE: &str = "min_balance"; -pub struct EndpointProperties { - pub url: CheckedUrl, - pub chain: Arc>, -} - -pub struct CheckedUrl(String); - -impl CheckedUrl { - pub fn get(self) -> String { - self.0 - } + let asset_info = storage_finalized + .fetch(&dynamic::storage(ASSETS, ASSET, vec![asset.into()])) + .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().map(Balance).with_context(|| { + format!("expected `u128` as the type of the min balance, got {encoded_min_balance}") + }) } -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")?, - ); +async fn fetch_decimals( + storage: Storage, + asset: AssetId, +) -> Result { + const METADATA: &str = "Metadata"; + const DECIMALS: &str = "decimals"; - 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) + let asset_metadata = storage + .fetch(&dynamic::storage(ASSETS, METADATA, vec![asset.into()])) .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, + .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}") + }) } -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, +// 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(chains: Vec) -> Result> { + let mut connected_chains = HashMap::with_capacity(chains.len()); + + for chain in chains { + let endpoint = chain.endpoints.first().with_context(|| { + format!( + "{:?} chain doesn't have any `endpoints` in the config", + chain.name ) - }; - 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() + })?; + let rpc_client = RpcClient::new( + Client::builder() + .build(endpoint.into()) .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") - } + .with_context(|| { + format!( + "failed to construct the RPC client for the {:?} chain", + chain.name + ) + })?, + ); - async fn process_block(&self, number: BlockNumber, hash: Hash) -> Result<()> { - log::debug!("background block {number}"); + log::info!( + "Connected to an RPC server for the {:?} chain at {endpoint:?}.", + chain.name + ); - let block = self - .client - .blocks() - .at(hash) + let methods = LegacyRpcMethods::new(rpc_client.clone()); + let client = OnlineClient::from_rpc_client(rpc_client) .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")?, + .with_context(|| { + format!( + "failed to construct the API client for the {:?} chain", + chain.name ) - .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, + let connected_chain = ConnectedChain { 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(), - ); + genesis: client.genesis_hash(), + assets: client + .metadata() + .pallet_by_name(ASSETS) + .map(|pallet| pallet.index()), + properties: ChainProperties::fetch( + &client.constants(), + client.storage().at_latest().await?, + chain + .asset + .map(|assets| assets.into_iter().map(|asset| asset.id).collect()) + .unwrap_or_default(), + ) + .await?, + client, + }; - self.api - .tx - .create_signed_with_nonce(&call, signer, nonce, extensions) - .context("failed to create a transfer transaction") - } + log::debug!("\n{connected_chain:#?}"); - 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")?; - } + if connected_chains + .insert(chain.name.clone(), connected_chain) + .is_some() + { + anyhow::bail!( + "found `[chain]`s in the config with the same name ({:?}), all chain names must be unique", + chain.name + ); } - - 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, + Ok(connected_chains) } -#[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)) +pub struct ConnectedChain { + methods: LegacyRpcMethods, + client: OnlineClient, + genesis: BlockHash, + assets: Option, + properties: ChainProperties, } -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(()) +impl Debug for ConnectedChain { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.debug_struct(stringify!(ConnectedChain)) + .field("genesis", &self.genesis) + .field("assets", &self.assets) + .field("properties", &self.properties) + .finish_non_exhaustive() } } + +// pub async fn prepare( +// chains: Vec, +// 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..0d01117 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,209 +1,382 @@ -use crate::{ - database::{Database, Invoice, InvoiceStatus}, - Account, -}; +use crate::{AssetId, BlockNumber, Decimals, ExtrinsicIndex}; use anyhow::{Context, Result}; use axum::{ - extract::{Path, State}, - routing::get, + extract::{rejection::RawPathParamsRejection, MatchedPath, Query, RawPathParams}, + http::{header, HeaderName, StatusCode}, + response::{IntoResponse, Response}, + routing::{get, post}, Json, Router, }; -use serde::Serialize; -use std::{future::Future, net::SocketAddr, sync::Arc}; -use subxt::ext::sp_core::{hexdisplay::HexDisplay, DeriveJunction, Pair}; +use serde::{Serialize, Serializer}; +use std::{collections::HashMap, future::Future, net::SocketAddr}; use tokio::net::TcpListener; use tokio_util::sync::CancellationToken; -pub(crate) const MODULE: &str = module_path!(); +pub const MODULE: &str = module_path!(); + +const AMOUNT: &str = "amount"; +const CURRENCY: &str = "currency"; +const CALLBACK: &str = "callback"; + +#[derive(Serialize)] +struct OrderStatus { + order: String, + payment_status: PaymentStatus, + message: String, + recipient: String, + server_info: ServerInfo, + #[serde(skip_serializing_if = "Option::is_none", flatten)] + order_info: Option, +} + +#[derive(Serialize)] +struct OrderInfo { + withdrawal_status: WithdrawalStatus, + amount: f64, + currency: CurrencyInfo, + callback: String, + transactions: Vec, + payment_account: String, +} + +#[derive(Serialize)] +#[serde(rename_all = "lowercase")] +enum PaymentStatus { + Pending, + Paid, + Unknown, +} + +#[derive(Serialize)] +#[serde(rename_all = "lowercase")] +enum WithdrawalStatus { + Waiting, + Failed, + Completed, +} + +#[derive(Serialize)] +struct ServerStatus { + description: ServerInfo, + supported_currencies: Vec, +} + +#[derive(Serialize)] +struct ServerHealth { + description: ServerInfo, + connected_rpcs: Vec, + status: Health, +} + +#[derive(Serialize)] +struct RpcInfo { + rpc_url: String, + chain_name: String, + status: Health, +} + +#[derive(Serialize)] +#[serde(rename_all = "lowercase")] +enum Health { + Ok, + Degraded, + Critical, +} #[derive(Serialize)] -#[serde(untagged)] -pub enum Response { - Error(Error), - Success(Success), +struct CurrencyInfo { + currency: String, + chain_name: String, + kind: TokenKind, + decimals: Decimals, + rpc_url: String, + #[serde(skip_serializing_if = "Option::is_none")] + asset_id: Option, } #[derive(Serialize)] -pub struct Error { - error: String, - wss: String, - mul: u64, - version: String, +#[serde(rename_all = "lowercase")] +enum TokenKind { + Assets, + Balances, } #[derive(Serialize)] -pub struct Success { - pay_account: String, - price: f64, +struct ServerInfo { + version: &'static str, + instance_id: String, + #[serde(skip_serializing_if = "Option::is_none")] + debug: Option, + #[serde(skip_serializing_if = "Option::is_none")] + kalatori_remark: Option, +} + +#[derive(Serialize)] +struct TransactionInfo { + #[serde(skip_serializing_if = "Option::is_none", flatten)] + finalized_tx: Option, + transaction_bytes: String, + sender: String, recipient: String, - order: String, - wss: String, - mul: u64, - result: String, - version: String, + #[serde(serialize_with = "amount_serializer")] + amount: Amount, + currency: CurrencyInfo, + status: TxStatus, +} + +#[derive(Serialize)] +struct FinalizedTx { + block_number: BlockNumber, + position_in_block: ExtrinsicIndex, + timestamp: String, +} + +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(Serialize)] +#[serde(rename_all = "lowercase")] +enum TxStatus { + Pending, + Finalized, + Failed, } -pub(crate) async fn new( +pub 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); +) -> Result>> { + let v2 = Router::new() + .route("/order/:order_id", post(order)) + .route("/status", get(status)) + .route("/health", get(health)); + let app = Router::new().nest("/v2", v2); let listener = TcpListener::bind(host) .await - .context("failed to bind the TCP listener")?; + .with_context(|| format!("failed to bind the TCP listener to {host:?}"))?; - log::info!("The server is listening on {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")?; + .await?; - 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(), - }), - } - .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() +enum OrderSuccess { + Created, + Found, } -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})."); - } - } +enum OrderError { + LessThanExistentialDeposit(f64), + UnknownCurrency, + MissingParameter(String), + InvalidParameter(String), + AlreadyProcessed(Box), + InternalError, +} + +#[derive(Serialize)] +struct InvalidParameter { + parameter: String, + message: String, +} - 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)?, +fn process_order( + matched_path: &MatchedPath, + path_result: Result, + query: &HashMap, +) -> Result<(OrderStatus, OrderSuccess), OrderError> { + 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() { + // TODO: try to query an order from the database. + + Ok(( + OrderStatus { + order, + payment_status: PaymentStatus::Unknown, + message: String::new(), + recipient: String::new(), + server_info: ServerInfo { + version: env!("CARGO_PKG_VERSION"), + instance_id: String::new(), + debug: None, + kalatori_remark: None, + }, + order_info: None, }, - 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(), - }) + OrderSuccess::Found, + )) } else { - let tx = database.write()?; - - tx.invoices()?.save( - &invoice_account, - &Invoice { - recipient: recipient_account, - order: order_encoded, - status: InvoiceStatus::Unpaid(price), + 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()))?; + + // TODO: try to query & update or create an order in the database. + + if currency == "USDCT" { + return Err(OrderError::UnknownCurrency); + } + + if amount < 50.0 { + return Err(OrderError::LessThanExistentialDeposit(50.0)); + } + + Ok(( + OrderStatus { + order, + payment_status: PaymentStatus::Pending, + message: String::new(), + recipient: String::new(), + server_info: ServerInfo { + version: env!("CARGO_PKG_VERSION"), + instance_id: String::new(), + debug: None, + kalatori_remark: None, + }, + order_info: Some(OrderInfo { + withdrawal_status: WithdrawalStatus::Waiting, + amount, + currency: CurrencyInfo { + currency, + chain_name: String::new(), + kind: TokenKind::Balances, + decimals: 0, + rpc_url: String::new(), + asset_id: None, + }, + callback, + transactions: vec![], + payment_account: String::new(), + }), }, - )?; - - 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(), - }) + OrderSuccess::Created, + )) } } -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 order( + matched_path: MatchedPath, + path_result: Result, + query: Query>, +) -> Response { + match process_order(&matched_path, path_result, &query) { + Ok((order_status, order_success)) => match order_success { + OrderSuccess::Created => (StatusCode::CREATED, Json(order_status)), + OrderSuccess::Found => (StatusCode::OK, Json(order_status)), + } + .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::AlreadyProcessed(order_status) => { + (StatusCode::CONFLICT, Json(order_status)).into_response() + } + OrderError::InternalError => StatusCode::INTERNAL_SERVER_ERROR.into_response(), + }, + } +} - Ok(numfl / mul) +async fn status() -> ([(HeaderName, &'static str); 1], Json) { + ( + [(header::CACHE_CONTROL, "no-store")], + ServerStatus { + description: ServerInfo { + version: env!("CARGO_PKG_VERSION"), + instance_id: String::new(), + debug: None, + kalatori_remark: None, + }, + supported_currencies: vec![CurrencyInfo { + currency: String::new(), + chain_name: String::new(), + kind: TokenKind::Balances, + decimals: 0, + rpc_url: String::new(), + asset_id: None, + }], + } + .into(), + ) +} + +async fn health() -> ([(HeaderName, &'static str); 1], Json) { + ( + [(header::CACHE_CONTROL, "no-store")], + ServerHealth { + description: ServerInfo { + version: env!("CARGO_PKG_VERSION"), + instance_id: String::new(), + debug: None, + kalatori_remark: None, + }, + connected_rpcs: [RpcInfo { + rpc_url: String::new(), + chain_name: String::new(), + status: Health::Critical, + }] + .into(), + status: Health::Degraded, + } + .into(), + ) } From bbd71854a416b89f8774e0499ad7a89d4507c755 Mon Sep 17 00:00:00 2001 From: Fluid <90795031+fluiderson@users.noreply.github.com> Date: Mon, 1 Apr 2024 09:32:35 +0300 Subject: [PATCH 02/76] New API progress --- Cargo.lock | 297 +++--- Cargo.toml | 12 +- kalatori-test.toml | 34 +- kalatori.toml | 15 +- src/asset.rs | 244 +++++ src/callback.rs | 1 - src/database.rs | 243 ++--- src/main.rs | 380 ++++---- src/rpc.rs | 2182 ++++++++++++++++++++------------------------ src/server.rs | 246 +++-- 10 files changed, 1934 insertions(+), 1720 deletions(-) create mode 100644 src/asset.rs diff --git a/Cargo.lock b/Cargo.lock index 3483005..36649c7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -109,54 +109,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "anstream" -version = "0.6.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" -dependencies = [ - "anstyle", - "anstyle-parse", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "utf8parse", -] - -[[package]] -name = "anstyle" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" - -[[package]] -name = "anstyle-parse" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" -dependencies = [ - "utf8parse", -] - -[[package]] -name = "anstyle-query" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" -dependencies = [ - "windows-sys 0.52.0", -] - -[[package]] -name = "anstyle-wincon" -version = "3.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" -dependencies = [ - "anstyle", - "windows-sys 0.52.0", -] - [[package]] name = "anyhow" version = "1.0.81" @@ -327,16 +279,16 @@ checksum = "f28243a43d821d11341ab73c80bed182dc015c514b951616cf79bd4af39af0c3" dependencies = [ "concurrent-queue", "event-listener 5.2.0", - "event-listener-strategy 0.5.0", + "event-listener-strategy 0.5.1", "futures-core", "pin-project-lite", ] [[package]] name = "async-executor" -version = "1.8.0" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17ae5ebefcc48e7452b4987947920dac9450be1110cadf34d1b8c116bdbaf97c" +checksum = "10b3e585719c2358d2660232671ca8ca4ddb4be4ce8a1842d6c2dc8685303316" dependencies = [ "async-lock 3.3.0", "async-task", @@ -409,19 +361,21 @@ dependencies = [ [[package]] name = "async-process" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "451e3cf68011bd56771c79db04a9e333095ab6349f7e47592b788e9b98720cc8" +checksum = "d999d925640d51b662b7b4e404224dd81de70f4aa4a199383c2c5e5b86885fa3" dependencies = [ "async-channel", "async-io", "async-lock 3.3.0", "async-signal", + "async-task", "blocking", "cfg-if", "event-listener 5.2.0", "futures-lite", "rustix 0.38.32", + "tracing", "windows-sys 0.52.0", ] @@ -457,7 +411,7 @@ checksum = "a507401cad91ec6a857ed5513a2073c82a9b9048762b885bb98655b306964681" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.57", ] [[package]] @@ -783,9 +737,9 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.35" +version = "0.4.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaf5903dcbc0a39312feb77df2ff4c76387d591b9fc7b04a238dcf8bb62639a" +checksum = "8a0d04d43504c61aa6c7531f1871dd0d418d91130162063b789da00fd7057a5e" dependencies = [ "android-tzdata", "iana-time-zone", @@ -803,12 +757,6 @@ dependencies = [ "inout", ] -[[package]] -name = "colorchoice" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" - [[package]] name = "common-path" version = "1.0.0" @@ -998,7 +946,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.57", ] [[package]] @@ -1046,7 +994,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.55", + "syn 2.0.57", ] [[package]] @@ -1068,7 +1016,7 @@ checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f" dependencies = [ "darling_core 0.20.8", "quote", - "syn 2.0.55", + "syn 2.0.57", ] [[package]] @@ -1081,6 +1029,15 @@ dependencies = [ "zeroize", ] +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", +] + [[package]] name = "derivative" version = "2.2.0" @@ -1158,7 +1115,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.55", + "syn 2.0.57", "termcolor", "toml", "walkdir", @@ -1291,28 +1248,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "env_filter" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea" -dependencies = [ - "log", -] - -[[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" @@ -1375,9 +1310,9 @@ dependencies = [ [[package]] name = "event-listener-strategy" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "feedafcaa9b749175d5ac357452a9d41ea2911da598fde46ce1fe02c37751291" +checksum = "332f51cb23d20b0de8458b86580878211da09bcd4503cb579c225b3d124cabb3" dependencies = [ "event-listener 5.2.0", "pin-project-lite", @@ -1394,7 +1329,7 @@ dependencies = [ "prettier-please", "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.57", ] [[package]] @@ -1560,7 +1495,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.57", ] [[package]] @@ -1854,12 +1789,6 @@ 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" @@ -2225,17 +2154,17 @@ version = "0.2.0" dependencies = [ "anyhow", "axum", - "env_logger", "hex-simd", - "humantime", - "log", "names", "redb", + "scale-info", "serde", "subxt", "tokio", "tokio-util", "toml_edit 0.22.9", + "tracing", + "tracing-subscriber 0.3.18", "ureq", ] @@ -2369,6 +2298,15 @@ dependencies = [ "regex-automata 0.1.10", ] +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + [[package]] name = "matchit" version = "0.7.3" @@ -2377,9 +2315,9 @@ checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" [[package]] name = "memchr" -version = "2.7.1" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" [[package]] name = "memfd" @@ -2489,6 +2427,16 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + [[package]] name = "num-bigint" version = "0.4.4" @@ -2500,6 +2448,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + [[package]] name = "num-format" version = "0.4.4" @@ -2595,6 +2549,12 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4030760ffd992bef45b0ae3f10ce1aba99e33464c90d14dd7c039884963ddc7a" +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + [[package]] name = "parity-bip39" version = "2.0.1" @@ -2714,14 +2674,14 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.57", ] [[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" @@ -2795,7 +2755,7 @@ dependencies = [ "polkavm-common 0.8.0", "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.57", ] [[package]] @@ -2807,7 +2767,7 @@ dependencies = [ "polkavm-common 0.9.0", "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.57", ] [[package]] @@ -2817,7 +2777,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "15e85319a0d5129dc9f021c62607e0804f5fb777a05cdda44d750ac0732def66" dependencies = [ "polkavm-derive-impl 0.8.0", - "syn 2.0.55", + "syn 2.0.57", ] [[package]] @@ -2827,7 +2787,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ba81f7b5faac81e528eb6158a6f3c9e0bb1008e0ffa19653bc8dea925ecb429" dependencies = [ "polkavm-derive-impl 0.9.0", - "syn 2.0.55", + "syn 2.0.57", ] [[package]] @@ -2856,6 +2816,12 @@ dependencies = [ "universal-hash", ] +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -2869,7 +2835,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22020dfcf177fcc7bf5deaf7440af371400c67c0de14c399938d8ed4fb4645d3" dependencies = [ "proc-macro2", - "syn 2.0.55", + "syn 2.0.57", ] [[package]] @@ -3057,7 +3023,7 @@ checksum = "5fddb4f8d99b0a2ebafc65a87a69a7b9875e4b1ae1f00db265d300ef7f28bccc" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.57", ] [[package]] @@ -3255,9 +3221,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "868e20fada228fefaf6b652e00cc73623d54f8171e7352c18bb281571f2d92da" +checksum = "ecd36cc4259e3e4514335c4a138c6b43171a8d61d8f5c9348f9fc7529416f247" [[package]] name = "rustls-webpki" @@ -3424,7 +3390,7 @@ dependencies = [ "proc-macro2", "quote", "scale-info", - "syn 2.0.55", + "syn 2.0.57", "thiserror", ] @@ -3548,9 +3514,9 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.9.2" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +checksum = "770452e37cad93e0a50d5abc3990d2bc351c36d0328f86cefec2f2fb206eaef6" dependencies = [ "bitflags 1.3.2", "core-foundation", @@ -3561,9 +3527,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.9.1" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +checksum = "41f3cc463c0ef97e11c3461a9d3787412d30e8e7eb907c79180c4a57bf7c04ef" dependencies = [ "core-foundation-sys", "libc", @@ -3601,7 +3567,7 @@ checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.57", ] [[package]] @@ -3989,7 +3955,7 @@ checksum = "48d09fa0a5f7299fb81ee25ae3853d26200f7a348148aed6de76be905c007dbe" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.57", ] [[package]] @@ -4110,7 +4076,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.57", ] [[package]] @@ -4165,7 +4131,7 @@ dependencies = [ "sp-std", "tracing", "tracing-core", - "tracing-subscriber", + "tracing-subscriber 0.2.25", ] [[package]] @@ -4346,7 +4312,7 @@ dependencies = [ "scale-info", "scale-typegen", "subxt-metadata", - "syn 2.0.55", + "syn 2.0.57", "thiserror", "tokio", ] @@ -4380,7 +4346,7 @@ dependencies = [ "quote", "scale-typegen", "subxt-codegen", - "syn 2.0.55", + "syn 2.0.57", ] [[package]] @@ -4410,9 +4376,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.55" +version = "2.0.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "002a1b3dbf967edfafc32655d0f377ab0bb7b994aa1d32c8cc7e9b8bf3ebb8f0" +checksum = "11a6ae1e52eb25aab8f3fb9fca13be982a373b8f1157ca14b897a825ba4a2d35" dependencies = [ "proc-macro2", "quote", @@ -4469,7 +4435,7 @@ checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.57", ] [[package]] @@ -4482,6 +4448,37 @@ dependencies = [ "once_cell", ] +[[package]] +name = "time" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "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.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ba3a3ef41e6672a2f0f001392bb5dcd3ff0a9992d618ca761a11c3121547774" +dependencies = [ + "num-conv", + "time-core", +] + [[package]] name = "tinyvec" version = "1.6.0" @@ -4499,9 +4496,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.36.0" +version = "1.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931" +checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" dependencies = [ "backtrace", "bytes", @@ -4523,7 +4520,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.57", ] [[package]] @@ -4701,7 +4698,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.57", ] [[package]] @@ -4744,7 +4741,7 @@ dependencies = [ "ansi_term", "chrono", "lazy_static", - "matchers", + "matchers 0.0.1", "regex", "serde", "serde_json", @@ -4757,6 +4754,24 @@ dependencies = [ "tracing-serde", ] +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "matchers 0.1.0", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "time", + "tracing", + "tracing-core", +] + [[package]] name = "trie-db" version = "0.28.0" @@ -4867,6 +4882,8 @@ dependencies = [ "base64 0.21.7", "log", "once_cell", + "serde", + "serde_json", "url", ] @@ -4881,12 +4898,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "utf8parse" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" - [[package]] name = "valuable" version = "0.1.0" @@ -4975,7 +4986,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.57", "wasm-bindgen-shared", ] @@ -4997,7 +5008,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.57", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -5490,7 +5501,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.57", ] [[package]] @@ -5510,5 +5521,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.57", ] diff --git a/Cargo.toml b/Cargo.toml index 50a8898..8c55877 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,23 +11,23 @@ keywords = ["substrate", "blockchain", "finance", "service", "middleware"] categories = ["finance"] [dependencies] -env_logger = { version = "0.11", default-features = false, features = ["humantime", "auto-color"] } toml_edit = { version = "0.22", default-features = false, features = ["parse", "serde"] } 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 } subxt = { version = "=0.35", features = ["substrate-compat", "unstable-reconnecting-rpc-client"] } tokio-util = { version = "0.7", features = ["rt"] } tokio = { version = "1", features = ["signal"] } -ureq = { version = "2", default-features = false } -names = { version = "0.14", default-features = false } - hex-simd = "0.8" serde = "1" anyhow = "1" -log = "0.4" redb = "2" -humantime = "2" +tracing = "0.1" +scale-info = "2" [profile.release] strip = true diff --git a/kalatori-test.toml b/kalatori-test.toml index 2832a8a..195ea86 100644 --- a/kalatori-test.toml +++ b/kalatori-test.toml @@ -1,6 +1,6 @@ recipient = "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty" -account-lifetime = 3600 # 1 hour. -depth = 86400 # 1 day. +account-lifetime = 86400000 # 1 day. +depth = 3600000 # 1 hour. debug = true [[chain]] @@ -17,10 +17,9 @@ name = "westmint" native-token = "WND AH" decimals = 12 endpoints = [ - "wss://polkadot-asset-hub-rpc.polkadot.io", - "wss://statemint-rpc.dwellir.com", + "wss://westend-asset-hub-rpc.polkadot.io", + "wss://westmint-rpc.dwellir.com", ] -multi-location-assets = true [[chain.asset]] name = "JOE" @@ -29,3 +28,28 @@ 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/kalatori.toml b/kalatori.toml index 1a10b9d..75c2091 100644 --- a/kalatori.toml +++ b/kalatori.toml @@ -1,6 +1,6 @@ recipient = "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty" -account-lifetime = 86400 # 1 day. -depth = 604800 # 1 week. +account-lifetime = 604800000 # 1 week. +depth = 86400000 # 1 day. [[chain]] name = "polkadot" @@ -34,3 +34,14 @@ endpoints = [ "wss://kusama-rpc.polkadot.io", "wss://1rpc.io/ksm", ] + +[[chain]] +name = "assethub-kusama" +endpoints = [ + "wss://kusama-asset-hub-rpc.polkadot.io", + "wss://statemine-rpc.dwellir.com", +] + +[[chain.asset]] +name = "RMRK" +id = 8 diff --git a/src/asset.rs b/src/asset.rs new file mode 100644 index 0000000..54fe210 --- /dev/null +++ b/src/asset.rs @@ -0,0 +1,244 @@ +use crate::{AssetId, PalletIndex}; +use std::marker::PhantomData; +use subxt::ext::{ + codec::{Encode, Output}, + scale_decode::{ + self, + error::ErrorKind, + visitor::{ + types::{Composite, Tuple}, + TypeIdFor, + }, + DecodeAsType, IntoVisitor, TypeResolver, Visitor, + }, + scale_encode::{self, EncodeAsType}, +}; + +#[derive(Clone, Debug)] +pub enum Asset { + Id(AssetId), + MultiLocation(PalletIndex, AssetId), +} + +impl Asset { + const ID: &'static str = "Id"; + const MULTI_LOCATION: &'static str = "MultiLocation"; +} + +pub struct AssetVisitor(PhantomData); + +fn try_into_asset_id( + number: impl TryInto + ToString + Copy, +) -> Result { + number.try_into().map_err(|_| { + scale_decode::Error::new(ErrorKind::NumberOutOfRange { + value: number.to_string(), + }) + }) +} + +macro_rules! visit_number { + ($visit:ident, $number:ty) => { + fn $visit<'scale, 'resolver>( + self, + number: $number, + type_id: &TypeIdFor, + ) -> Result, Self::Error> { + self.visit_u32(try_into_asset_id(number)?, type_id) + } + }; +} + +macro_rules! visit_composite_or_tuple { + ($visit:ident, $composite_or_tuple:ident) => { + fn $visit<'scale, 'resolver>( + self, + composite_or_tuple: &mut $composite_or_tuple<'scale, 'resolver, Self::TypeResolver>, + type_id: &TypeIdFor, + ) -> Result, Self::Error> { + if composite_or_tuple.remaining() == 1 { + // Shouldn't panic. We've just checked remaining items above. + return composite_or_tuple + .decode_item(self) + .unwrap() + .map_err(|error| error.at_variant(Self::Value::ID)); + } + + let (pallet_index, asset_id) = (|| { + let MultiLocation { + interior: Junctions::X2(first, second), + .. + } = MultiLocation::into_visitor().$visit(composite_or_tuple, type_id)?; + + let Junction::PalletInstance(pallet_index) = first else { + return Err(scale_decode::Error::new(ErrorKind::CannotFindVariant { + got: first.name().into(), + expected: vec![Junction::PALLET_INSTANCE], + }) + .at_idx(0)); + }; + + let asset_id = (|| { + let Junction::GeneralIndex(general_index) = second else { + return Err(scale_decode::Error::new(ErrorKind::CannotFindVariant { + got: first.name().into(), + expected: vec![Junction::GENERAL_INDEX], + })); + }; + + let asset_id = general_index.try_into().map_err(|_| { + scale_decode::Error::new(ErrorKind::NumberOutOfRange { + value: general_index.to_string(), + }) + })?; + + Ok(asset_id) + })() + .map_err(|error| error.at_idx(1))?; + + Ok((pallet_index, asset_id)) + })() + .map_err(|error| error.at_variant(Self::Value::MULTI_LOCATION))?; + + Ok(Self::Value::MultiLocation(pallet_index, asset_id)) + } + }; +} + +impl Visitor for AssetVisitor { + type Value<'scale, 'resolver> = Asset; + type Error = scale_decode::Error; + type TypeResolver = R; + + fn visit_u32<'scale, 'resolver>( + self, + value: u32, + _: &TypeIdFor, + ) -> Result, Self::Error> { + Ok(Self::Value::Id(value)) + } + + fn visit_u8<'scale, 'resolver>( + self, + value: u8, + type_id: &TypeIdFor, + ) -> Result, Self::Error> { + self.visit_u32(value.into(), type_id) + } + + fn visit_u16<'scale, 'resolver>( + self, + value: u16, + type_id: &TypeIdFor, + ) -> Result, Self::Error> { + self.visit_u32(value.into(), type_id) + } + + visit_number!(visit_i8, i8); + visit_number!(visit_i16, i16); + visit_number!(visit_i32, i32); + visit_number!(visit_u64, u64); + visit_number!(visit_i64, i64); + visit_number!(visit_i128, i128); + visit_number!(visit_u128, u128); + + visit_composite_or_tuple!(visit_tuple, Tuple); + visit_composite_or_tuple!(visit_composite, Composite); +} + +impl IntoVisitor for Asset { + type AnyVisitor = AssetVisitor; + + fn into_visitor() -> Self::AnyVisitor { + AssetVisitor(PhantomData) + } +} + +impl EncodeAsType for Asset { + fn encode_as_type_to( + &self, + type_id: &R::TypeId, + types: &R, + out: &mut Vec, + ) -> Result<(), scale_encode::Error> { + match self { + Self::Id(id) => id.encode_as_type_to(type_id, types, out), + Self::MultiLocation(assets_pallet, asset_id) => { + MultiLocation::new(*assets_pallet, *asset_id).encode_as_type_to(type_id, types, out) + } + } + } +} + +impl Encode for Asset { + fn size_hint(&self) -> usize { + match self { + Self::Id(id) => id.size_hint(), + Self::MultiLocation(assets_pallet, asset_id) => { + MultiLocation::new(*assets_pallet, *asset_id).size_hint() + } + } + } + + fn encode_to(&self, dest: &mut T) { + match self { + Self::Id(id) => id.encode_to(dest), + Self::MultiLocation(assets_pallet, asset_id) => { + MultiLocation::new(*assets_pallet, *asset_id).encode_to(dest); + } + } + } +} + +#[derive(EncodeAsType, DecodeAsType, Encode)] +#[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 { + parents: u8, + interior: Junctions, +} + +impl MultiLocation { + fn new(assets_pallet: PalletIndex, asset_id: AssetId) -> Self { + Self { + parents: 0, + interior: Junctions::X2( + Junction::PalletInstance(assets_pallet), + Junction::GeneralIndex(asset_id.into()), + ), + } + } +} + +#[derive(EncodeAsType, DecodeAsType, Encode)] +#[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 { + #[codec(index = 2)] + X2(Junction, Junction), +} + +#[derive(EncodeAsType, DecodeAsType, Encode)] +#[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 { + #[codec(index = 4)] + PalletInstance(PalletIndex), + #[codec(index = 5)] + GeneralIndex(u128), +} + +impl Junction { + const PALLET_INSTANCE: &'static str = "PalletInstance"; + const GENERAL_INDEX: &'static str = "GeneralIndex"; + + fn name(&self) -> &str { + match self { + Self::PalletInstance(_) => Self::PALLET_INSTANCE, + Self::GeneralIndex(_) => Self::GENERAL_INDEX, + } + } +} diff --git a/src/callback.rs b/src/callback.rs index 1fa0c7e..7763b55 100644 --- a/src/callback.rs +++ b/src/callback.rs @@ -1,2 +1 @@ - pub const MODULE: &str = module_path!(); diff --git a/src/database.rs b/src/database.rs index 3439919..b428a51 100644 --- a/src/database.rs +++ b/src/database.rs @@ -1,11 +1,22 @@ -use crate::{rpc::ConnectedChain, AssetId, Balance, BlockNumber, Nonce, Timestamp}; +use crate::{ + rpc::{ConnectedChain, Currency}, + AccountId, AssetId, Balance, BlockNumber, Config, Nonce, Timestamp, Version, +}; use anyhow::{Context, Result}; -use redb::{Database, ReadableTable, Table, TableDefinition, TableHandle, TypeName, Value}; -use std::{collections::HashMap, sync::Arc}; +use redb::{ + backends::{FileBackend, InMemoryBackend}, + Database, ReadableTable, Table, TableDefinition, TableHandle, TypeName, Value, +}; +use serde::Deserialize; +use std::{collections::HashMap, fs::File, io::ErrorKind, sync::Arc}; use subxt::ext::{ codec::{Compact, Decode, Encode}, - sp_core::sr25519::{Pair, Public}, + sp_core::{ + crypto::Ss58Codec, + sr25519::{Pair, Public}, + }, }; +use tokio::sync::RwLock; pub const MODULE: &str = module_path!(); @@ -15,8 +26,6 @@ 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 HIT_LIST: TableDefinition<'_, Timestamp, (ChainHash, AssetId, Account)> = - TableDefinition::new("hit_list"); const ACCOUNTS: &str = "accounts"; @@ -28,6 +37,11 @@ const TRANSACTIONS: &str = "transactions"; type TRANSACTIONS_KEY = BlockNumber; type TRANSACTIONS_VALUE = (Account, Nonce, Transfer); +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 @@ -84,14 +98,36 @@ struct Invoice { price: BalanceSlot, callback: String, message: String, - asset: Option>, - transactions: Vec, + transactions: TransferTxs, +} + +#[derive(Encode, Decode, Debug)] +#[codec(crate = subxt::ext::codec)] +enum TransferTxs { + Asset { + #[codec(compact)] + id: AssetId, + // transactions: TransferTxsAsset, + }, + Native { + recipient: Account, + encoded: Vec, + exact_amount: Option>, + }, } +// #[derive(Encode, Decode, Debug)] +// #[codec(crate = subxt::ext::codec)] +// struct TransferTxsAsset { +// recipient: Account, +// encoded: Vec, +// #[codec(compact)] +// amount: BalanceSlot, +// } + #[derive(Encode, Decode, Debug)] #[codec(crate = subxt::ext::codec)] struct TransferTx { - encoded: Vec, recipient: Account, exact_amount: Option>, } @@ -121,124 +157,95 @@ impl Value for Invoice { } } +pub struct ConfigWoChains { + pub recipient: String, + pub debug: Option, + pub remark: Option, + pub depth: Option, + pub account_lifetime: BlockNumber, + pub rpc: String, +} + pub struct State { - db: Database, - // properties: Arc>, - // pair: Pair, - // rpc: String, - // destination: Option, + pub currencies: HashMap, + pub recipient: AccountId, + pub pair: Pair, + pub depth: Option, + pub account_lifetime: Timestamp, + pub debug: Option, + pub remark: Option, + + pub invoices: RwLock>, + pub rpc: String, +} + +#[derive(Deserialize, Debug)] +pub struct Invoicee { + pub callback: String, + pub amount: Balance, + pub paid: bool, + pub paym_acc: AccountId, } impl State { pub fn initialise( path_option: Option, + currencies: HashMap, current_pair: (Pair, Public), old_pairs: HashMap, - connected_chains: HashMap, + ConfigWoChains { + recipient, + debug, + remark, + depth, + account_lifetime, + rpc, + }: ConfigWoChains, ) -> Result> { - // let mut database = Database::create(path_option.unwrap()).unwrap(); - - // 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, - // )) - - todo!() + let builder = Database::builder(); + let is_new; + + let database = if let Some(path) = path_option { + tracing::info!("Creating/Opening the database at {path:?}."); + + match File::create_new(&path) { + Ok(file) => { + is_new = true; + + FileBackend::new(file).and_then(|backend| builder.create_with_backend(backend)) + } + Err(error) if error.kind() == ErrorKind::AlreadyExists => { + is_new = false; + + builder.create(path) + } + Err(error) => Err(error.into()) + } + } else { + tracing::warn!( + "The in-memory backend for the database is selected. All saved data will be deleted after the shutdown!" + ); + + is_new = true; + + builder.create_with_backend(InMemoryBackend::new()) + }.context("failed to create/open the database")?; + + // + + Ok(Arc::new(Self { + currencies, + recipient: AccountId::from_string(&recipient) + .context("failed to convert \"recipient\" from the config to an account address")?, + pair: current_pair.0, + depth, + account_lifetime, + debug, + remark, + + invoices: RwLock::new(HashMap::new()), + rpc, + })) } // pub fn rpc(&self) -> &str { diff --git a/src/main.rs b/src/main.rs index 4c3e15b..b71b75e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,10 @@ use anyhow::{Context, Error, Result}; -use env_logger::{Builder, Env}; -use log::LevelFilter; use serde::Deserialize; use std::{ + borrow::Cow, collections::HashMap, env::{self, VarError}, + error::Error as _, fs, future::Future, net::{IpAddr, Ipv4Addr, SocketAddr}, @@ -12,34 +12,35 @@ use std::{ panic, str, }; use subxt::{ - config::PolkadotExtrinsicParams, - ext::{ - codec::{Encode, Output}, - scale_decode::DecodeAsType, - scale_encode::{self, EncodeAsType, TypeResolver}, - sp_core::{ - crypto::{AccountId32, Ss58Codec}, - sr25519::Pair, - Pair as _, - }, + config::{substrate::SubstrateHeader, PolkadotExtrinsicParams}, + ext::sp_core::{ + crypto::{AccountId32, Ss58Codec}, + sr25519::Pair, + Pair as _, }, PolkadotConfig, }; use tokio::{ signal, - sync::mpsc::{self, UnboundedSender}, + sync::mpsc::{self, UnboundedReceiver, UnboundedSender}, + task::JoinHandle, }; -use tokio_util::{sync::CancellationToken, task::TaskTracker}; +use tokio_util::{sync::CancellationToken, task}; use toml_edit::de; +use tracing_subscriber::{fmt::time::UtcTime, EnvFilter}; +mod asset; mod callback; mod database; mod rpc; mod server; +use asset::Asset; +use database::{ConfigWoChains, State}; +use rpc::Processor; + const CONFIG: &str = "KALATORI_CONFIG"; const LOG: &str = "KALATORI_LOG"; -const LOG_STYLE: &str = "KALATORI_LOG_STYLE"; const SEED: &str = "KALATORI_SEED"; const OLD_SEED: &str = "KALATORI_OLD_SEED_"; @@ -54,11 +55,12 @@ type Decimals = u8; type BlockNumber = u64; type ExtrinsicIndex = u32; type Version = u64; -type AccountId = ::AccountId; type Nonce = u32; type Timestamp = u64; type PalletIndex = u8; + type BlockHash = ::Hash; +type AccountId = ::AccountId; type OnlineClient = subxt::OnlineClient; struct RuntimeConfig; @@ -69,111 +71,38 @@ impl subxt::Config for RuntimeConfig { type Address = ::Address; type Signature = ::Signature; type Hasher = ::Hasher; - type Header = ::Header; + type Header = SubstrateHeader; type ExtrinsicParams = PolkadotExtrinsicParams; - type AssetId = u32; -} - -enum Asset { - Id(AssetId), - MultiLocation(PalletIndex, AssetId), -} - -impl EncodeAsType for Asset { - fn encode_as_type_to( - &self, - type_id: &R::TypeId, - types: &R, - out: &mut Vec, - ) -> Result<(), scale_encode::Error> { - match self { - Self::Id(id) => id.encode_as_type_to(type_id, types, out), - Self::MultiLocation(assets_pallet, asset_id) => { - MultiLocation::new(*assets_pallet, *asset_id).encode_as_type_to(type_id, types, out) - } - } - } -} - -impl Encode for Asset { - fn size_hint(&self) -> usize { - match self { - Self::Id(id) => id.size_hint(), - Self::MultiLocation(assets_pallet, asset_id) => { - MultiLocation::new(*assets_pallet, *asset_id).size_hint() - } - } - } - - fn encode_to(&self, dest: &mut T) { - match self { - Self::Id(id) => id.encode_to(dest), - Self::MultiLocation(assets_pallet, asset_id) => { - MultiLocation::new(*assets_pallet, *asset_id).encode_to(dest); - } - } - } -} - -#[derive(EncodeAsType, DecodeAsType, Encode)] -#[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 { - parents: u8, - interior: Junctions, -} - -impl MultiLocation { - fn new(assets_pallet: PalletIndex, asset_id: AssetId) -> Self { - Self { - parents: 0, - interior: Junctions::X2( - Junction::PalletInstance(assets_pallet), - Junction::GeneralIndex(asset_id.into()), - ), - } - } -} - -#[derive(EncodeAsType, DecodeAsType, Encode)] -#[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 { - #[codec(index = 2)] - X2(Junction, Junction), -} - -#[derive(EncodeAsType, DecodeAsType, Encode)] -#[encode_as_type(crate_path = "subxt::ext::scale_encode")] -#[decode_as_type(crate_path = "subxt::ext::scale_decode")] -#[codec(crate = subxt::ext::codec)] -pub enum Junction { - #[codec(index = 4)] - PalletInstance(PalletIndex), - #[codec(index = 5)] - GeneralIndex(u128), + type AssetId = Asset; } #[tokio::main] #[allow(clippy::too_many_lines)] async fn main() -> Result<()> { - let mut builder = Builder::new(); + let filter = match EnvFilter::try_from_env(LOG) { + Err(error) => { + let Some(VarError::NotPresent) = error + .source() + .expect("should always be `Some`") + .downcast_ref() + else { + return Err(error).with_context(|| format!("failed to parse `{LOG}`")); + }; + + if cfg!(debug_assertions) { + EnvFilter::try_new("debug") + } else { + EnvFilter::try_new(default_filter()) + } + .unwrap() + } + Ok(filter) => filter, + }; - if cfg!(debug_assertions) { - builder.filter_level(LevelFilter::Debug) - } else { - builder - .filter_level(LevelFilter::Off) - .filter_module(callback::MODULE, LevelFilter::Info) - .filter_module(database::MODULE, LevelFilter::Info) - .filter_module(rpc::MODULE, LevelFilter::Info) - .filter_module(server::MODULE, LevelFilter::Info) - .filter_module(env!("CARGO_PKG_NAME"), LevelFilter::Info) - } - .parse_env(Env::from(LOG).write_style(LOG_STYLE)) - .init(); + tracing_subscriber::fmt() + .with_timer(UtcTime::rfc_3339()) + .with_env_filter(filter) + .init(); let pair = Pair::from_string( &env::var(SEED).with_context(|| format!("failed to read `{SEED}`"))?, @@ -208,7 +137,7 @@ async fn main() -> Result<()> { let config_path = env::var(CONFIG).or_else(|error| match error { VarError::NotUnicode(_) => Err(error).with_context(|| format!("failed to read `{CONFIG}`")), VarError::NotPresent => { - log::debug!( + tracing::debug!( "`{CONFIG}` isn't present, using the default value instead: {DEFAULT_CONFIG:?}." ); @@ -236,29 +165,27 @@ async fn main() -> Result<()> { break 'database None; } } else if config.in_memory_db.is_some() { - log::warn!("`in_memory_db` is set in the config but ignored because `debug` isn't set"); + tracing::warn!( + "`in_memory_db` is set in the config but ignored because `debug` isn't set" + ); } Some(config.database.unwrap_or_else(|| { - log::debug!( + tracing::debug!( "`database` isn't present in the config, using the default value instead: {DEFAULT_DATABASE:?}." ); - DEFAULT_DATABASE.to_owned() + DEFAULT_DATABASE.into() })) }; - let recipient = AccountId::from_string(&config.recipient) - .context("failed to convert `recipient` from the config to an account address")?; - - log::info!( + tracing::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 shutdown_notification_for_panic = shutdown_notification.clone(); panic::set_hook(Box::new(move |panic_info| { @@ -266,85 +193,187 @@ async fn main() -> Result<()> { .location() .map(|location| format!(" at `{location}`")) .unwrap_or_default(); - let panic_message = panic_info - .payload() - .downcast_ref::<&str>() - .map_or_else(|| ".".into(), |message| format!(":\n{message}\n")); - - log::error!( - "A panic detected{at}{panic_message}\nThis is a bug. Please report it at {}.", + let payload = panic_info.payload(); + + let message = match payload.downcast_ref::<&str>() { + Some(string) => Some(*string), + None => payload.downcast_ref::().map(|string| &string[..]), + }; + let formatted_message = match message { + Some(string) => format!(":\n{string}\n"), + None => ".".into(), + }; + + tracing::error!( + "A panic detected{at}{formatted_message}\nThis is a bug. Please report it at {}.", env!("CARGO_PKG_REPOSITORY") ); shutdown_notification_for_panic.cancel(); })); - let chains = rpc::prepare(config.chain) + let (task_tracker, error_rx) = TaskTracker::new(); + + task_tracker.spawn( + "the shutdown listener", + shutdown_listener(shutdown_notification.clone()), + ); + + let (chains, currencies) = rpc::prepare(config.chain, config.account_lifetime, config.depth) .await .context("failed while preparing the RPC module")?; - // let (database, last_saved_block) = Database::initialise( - // database_path, - // override_rpc, - // pair, - // endpoint_properties, - // destination, - // ) - // .context("failed to initialise the database module")?; + let rpc = env::var("KALATORI_RPC").unwrap(); + + let state = State::initialise( + database_path, + currencies, + (pair, pair_public), + old_seeds, + ConfigWoChains { + recipient: config.recipient, + debug: config.debug, + remark: config.remark, + depth: config.depth, + account_lifetime: config.account_lifetime, + rpc, + }, + ) + .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")?; + task_tracker.spawn( + "proc", + Processor::ignite(state.clone(), shutdown_notification.clone()), + ); - let server = server::new(shutdown_notification.clone(), host) + let server = server::new(shutdown_notification.clone(), host, state) .await .context("failed to initialise the server module")?; - let task_tracker = TaskTracker::new(); - - task_tracker.close(); - task_tracker.spawn(try_task( - "the shutdown listener", - shutdown_listener(shutdown_notification.clone()), - error_tx.clone(), - )); // task_tracker.spawn(shutdown( // processor.ignite(last_saved_block, task_tracker.clone(), error_tx.clone()), // error_tx, // )); - task_tracker.spawn(try_task("the server module", server, error_tx)); + task_tracker.spawn("the server module", server); - while let Some((from, error)) = error_rx.recv().await { - log::error!("Received a fatal error from {from}!\n{error:?}"); + task_tracker + .wait_with_notification(error_rx, shutdown_notification) + .await; - if !shutdown_notification.is_cancelled() { - log::info!("Initialising the shutdown..."); + tracing::info!("Goodbye!"); - shutdown_notification.cancel(); + Ok(()) +} + +fn default_filter() -> String { + const TARGETS: &[&str] = &[ + callback::MODULE, + database::MODULE, + rpc::MODULE, + server::MODULE, + env!("CARGO_PKG_NAME"), + ]; + const COMMA: &str = ","; + const INFO: &str = "=info"; + const OFF: &str = "off"; + + let mut filter = String::with_capacity( + OFF.len().saturating_add( + TARGETS + .iter() + .map(|module| { + COMMA + .len() + .saturating_add(module.len()) + .saturating_add(INFO.len()) + }) + .sum(), + ), + ); + + filter.push_str(OFF); + + for target in TARGETS { + filter.push_str(COMMA); + filter.push_str(target); + filter.push_str(INFO); + } + + filter +} + +#[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>> + 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: CancellationToken, + ) { + drop(self.error_tx); + + while let Some((from, error)) = error_rx.recv().await { + tracing::error!("Received a fatal error from {from}!\n{error:?}"); + + if !shutdown_notification.is_cancelled() { + tracing::info!("Initialising the shutdown..."); + + shutdown_notification.cancel(); + } } + + self.inner.wait().await; } - task_tracker.wait().await; + async fn try_wait( + self, + mut error_rx: UnboundedReceiver<(Cow<'static, str>, Error)>, + ) -> Result<()> { + drop(self.error_tx); - log::info!("Goodbye!"); + if let Some((from, error)) = error_rx.recv().await { + return Err(error).with_context(|| format!("received a fatal error from {from}")); + } - Ok(()) -} + self.inner.wait().await; -async fn try_task<'a>( - name: &'a str, - task: impl Future>, - error_tx: UnboundedSender<(&'a str, Error)>, -) { - match task.await { - Ok(shutdown_message) if !shutdown_message.is_empty() => log::info!("{shutdown_message}"), - Err(error) => error_tx - .send((name, error)) - .expect("error channel shouldn't be dropped/closed"), - _ => {} + Ok(()) } } -async fn shutdown_listener(shutdown_notification: CancellationToken) -> Result { +async fn shutdown_listener(shutdown_notification: CancellationToken) -> Result> { tokio::select! { biased; signal = signal::ctrl_c() => { @@ -353,7 +382,7 @@ async fn shutdown_listener(shutdown_notification: CancellationToken) -> Result Result, host: Option, database: Option, remark: Option, @@ -385,7 +414,6 @@ struct Chain { #[serde(flatten)] native_token: Option, asset: Option>, - multi_location_assets: Option, } #[derive(Deserialize)] @@ -401,7 +429,7 @@ struct AssetInfo { id: AssetId, } -#[derive(Debug)] +#[derive(Deserialize, Debug)] struct Balance(u128); impl Deref for Balance { diff --git a/src/rpc.rs b/src/rpc.rs index 5a66e0a..91df300 100644 --- a/src/rpc.rs +++ b/src/rpc.rs @@ -1,27 +1,61 @@ use crate::{ - AssetId, Balance, BlockHash, BlockNumber, Chain, Decimals, OnlineClient, PalletIndex, - RuntimeConfig, + asset::Asset, + database::{Invoicee, State}, + server::{ + CurrencyInfo, OrderInfo, OrderStatus, PaymentStatus, ServerInfo, TokenKind, + WithdrawalStatus, + }, + AccountId, AssetId, AssetInfo, Balance, BlockHash, BlockNumber, Chain, Decimals, NativeToken, + Nonce, OnlineClient, PalletIndex, RuntimeConfig, TaskTracker, Timestamp, }; use anyhow::{Context, Result}; +use scale_info::TypeDef; +use serde::{Deserialize, Deserializer}; use std::{ - collections::HashMap, - fmt::{self, Debug, Formatter}, + borrow::Cow, + collections::{hash_map::Entry, HashMap}, + error::Error, + fmt::{self, Debug, Display, Formatter}, + num::NonZeroU64, + sync::Arc, }; use subxt::{ backend::{ - legacy::LegacyRpcMethods, - rpc::{reconnecting_rpc_client::Client, RpcClient}, + legacy::{LegacyBackend, LegacyRpcMethods}, + rpc::{reconnecting_rpc_client::Client, RpcClient, RpcSubscription}, + Backend, BackendExt, RuntimeVersion, }, + blocks::Block, + config::{DefaultExtrinsicParamsBuilder, Header}, constants::ConstantsClient, - dynamic::{self, At}, - ext::{scale_decode::DecodeAsType, sp_core::crypto::Ss58AddressFormat}, - storage::Storage, + dynamic::{self, At, Value}, + error::RpcError, + ext::{ + futures::TryFutureExt, + scale_decode::DecodeAsType, + scale_value, + sp_core::{ + crypto::{Ss58AddressFormat, Ss58Codec}, + sr25519::Pair, + }, + }, + runtime_api::RuntimeApiClient, + storage::{Storage, StorageClient}, + tx::{PairSigner, SubmittableExtrinsic}, + Config, Metadata, +}; +use tokio::sync::{ + mpsc::{self, UnboundedSender}, + oneshot::{self, Sender}, }; +use tokio_util::sync::CancellationToken; 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"; +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"; // Pallets @@ -29,51 +63,75 @@ const SYSTEM: &str = "System"; const BALANCES: &str = "Balances"; const UTILITY: &str = "Utility"; const ASSETS: &str = "Assets"; +const BABE: &str = "Babe"; -// 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") -// } +// Runtime APIs -// 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")?, -// )) -// } +const AURA: &str = "AuraApi"; -// async fn fetch_metadata(backend: &impl Backend, at: Hash) -> Result { -// const LATEST_SUPPORTED_METADATA_VERSION: u32 = 15; +type ConnectedChainsChannel = ( + Sender, ConnectedChain)>>, + (Arc, ConnectedChain), +); +type CurrenciesChannel = (Sender>, (String, Currency)); -// 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) -// } +struct AssetsInfoFetcher<'a> { + assets: (AssetInfo, Vec), + storage: &'a Storage, + pallet_index: Option, +} + +async fn fetch_finalized_head_number_and_hash( + methods: &LegacyRpcMethods, +) -> Result<(BlockNumber, BlockHash)> { + let head_hash = methods + .chain_get_finalized_head() + .await + .context("failed to get the finalized head hash")?; + let head = 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 fetch_runtime( + methods: &LegacyRpcMethods, + backend: &impl Backend, + at: BlockHash, +) -> Result<(Metadata, RuntimeVersion)> { + Ok(( + fetch_metadata(backend, at) + .await + .context("failed to fetch metadata")?, + methods + .state_get_runtime_version(Some(at)) + .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: BlockHash) -> 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, @@ -87,76 +145,202 @@ fn fetch_constant( } #[derive(Debug)] -pub struct ChainProperties { - pub address_format: Ss58AddressFormat, - pub existential_deposit: Balance, - pub assets: HashMap, - pub block_hash_count: BlockNumber, +struct ChainProperties { + address_format: Ss58AddressFormat, + 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)] -pub struct AssetProperties { - pub min_balance: Balance, - pub decimals: Decimals, +struct AssetProperties { + min_balance: Balance, + decimals: Decimals, +} + +impl AssetProperties { + async fn fetch(storage: &Storage, asset: AssetId) -> Result { + Ok(Self { + min_balance: check_sufficiency_and_fetch_min_balance(storage, asset).await?, + decimals: fetch_asset_decimals(storage, asset).await?, + }) + } } impl ChainProperties { async fn fetch( + chain: Arc, + currencies: UnboundedSender, constants: &ConstantsClient, - storage_finalized: Storage, - assets: Vec, - ) -> Result { + native_token_option: Option, + assets_fetcher: Option>, + account_lifetime: BlockNumber, + depth: Option, + ) -> Result<(Self, Option)> { const ADDRESS_PREFIX: (&str, &str) = (SYSTEM, "SS58Prefix"); const EXISTENTIAL_DEPOSIT: (&str, &str) = (BALANCES, "ExistentialDeposit"); const BLOCK_HASH_COUNT: (&str, &str) = (SYSTEM, "BlockHashCount"); - let ex_dep: u128 = fetch_constant(constants, EXISTENTIAL_DEPOSIT)?; - let mut assets_props = HashMap::new(); + let chain_clone = chain.clone(); + + let try_add_currency = |name, asset| async move { + let (tx, rx) = oneshot::channel(); + + currencies + .send((tx, (name, Currency { chain, asset }))) + .unwrap(); - for asset in assets { - assets_props.insert( - asset, - AssetProperties { - min_balance: fetch_min_balance(storage_finalized.clone(), asset).await?, - decimals: fetch_decimals(storage_finalized.clone(), asset).await?, + if let Some(( + name, + Currency { + chain: other_chain, .. }, - ); - } + )) = rx.await.unwrap() + { + Err(anyhow::anyhow!( + "chain {other_chain:?} already has the native token or an asset with the name {name:?}, all currency names must be unique" + )) + } else { + Ok(()) + } + }; - Ok(Self { - address_format: Ss58AddressFormat::custom(fetch_constant(constants, ADDRESS_PREFIX)?), - existential_deposit: Balance(ex_dep), - assets: assets_props, - block_hash_count: fetch_constant(constants, BLOCK_HASH_COUNT)?, + let assets_pallet = if let Some(AssetsInfoFetcher { + assets: (last_asset_info, assets_info), + storage, + pallet_index, + }) = assets_fetcher + { + async fn try_add_asset( + assets: &mut HashMap, + id: AssetId, + chain: Arc, + storage: &Storage, + ) -> Result<()> { + match assets.entry(id) { + Entry::Occupied(_) => Err(anyhow::anyhow!( + "chain {chain} has 2 assets with the same ID {id}", + )), + Entry::Vacant(entry) => { + entry.insert(AssetProperties::fetch(storage, id).await?); + + Ok(()) + } + } + } + + let mut assets = HashMap::with_capacity(assets_info.len().saturating_add(1)); + + for asset_info in assets_info { + try_add_currency.clone()(asset_info.name, Some(asset_info.id)).await?; + try_add_asset(&mut assets, asset_info.id, chain_clone.clone(), storage).await?; + } + + try_add_currency.clone()(last_asset_info.name, Some(last_asset_info.id)).await?; + try_add_asset( + &mut assets, + last_asset_info.id, + chain_clone.clone(), + storage, + ) + .await?; + + Some(AssetsPallet { + assets, + multi_location: pallet_index, + }) + } else { + None + }; + + let address_format = Ss58AddressFormat::custom(fetch_constant(constants, ADDRESS_PREFIX)?); + let block_hash_count = fetch_constant(constants, BLOCK_HASH_COUNT)?; + + Ok(if let Some(native_token) = native_token_option { + try_add_currency(native_token.native_token, None).await?; + + ( + Self { + address_format, + existential_deposit: Some( + fetch_constant(constants, EXISTENTIAL_DEPOSIT).map(Balance)?, + ), + assets_pallet, + block_hash_count, + account_lifetime, + depth, + }, + Some(native_token.decimals), + ) + } else { + ( + Self { + address_format, + existential_deposit: None, + assets_pallet, + block_hash_count, + account_lifetime, + depth, + }, + None, + ) }) } } -async fn fetch_min_balance( - storage_finalized: Storage, +async fn check_sufficiency_and_fetch_min_balance( + storage: &Storage, asset: AssetId, ) -> Result { const ASSET: &str = "Asset"; const MIN_BALANCE: &str = "min_balance"; + const IS_SUFFICIENT: &str = "is_sufficient"; - let asset_info = storage_finalized + let asset_info = storage .fetch(&dynamic::storage(ASSETS, ASSET, vec![asset.into()])) .await - .context("failed to fetch asset info from the chain")? - .context("received nothing after fetching asset info from the chain")? + .with_context(|| format!("failed to fetch asset {asset} info from a chain"))? + .with_context(|| { + format!("received nothing after fetching asset info {asset} from a chain") + })? .to_value() - .context("failed to decode account info")?; + .with_context(|| format!("failed to decode asset {asset} info"))?; + + let encoded_is_sufficient = asset_info + .at(IS_SUFFICIENT) + .with_context(|| format!("{IS_SUFFICIENT} field wasn't found in asset {asset} info"))?; + + if !encoded_is_sufficient.as_bool().with_context(|| { + format!( + "expected `bool` as the type of {IS_SUFFICIENT:?} in asset {asset} info, got `{:?}`", + encoded_is_sufficient.value + ) + })? { + anyhow::bail!("only sufficient assets are supported, asset {asset} isn't sufficient"); + } + let encoded_min_balance = asset_info .at(MIN_BALANCE) - .with_context(|| format!("{MIN_BALANCE} field wasn't found in asset info"))?; + .with_context(|| format!("{MIN_BALANCE} field wasn't found in asset {asset} info"))?; encoded_min_balance.as_u128().map(Balance).with_context(|| { - format!("expected `u128` as the type of the min balance, got {encoded_min_balance}") + format!( + "expected `u128` as the type of {MIN_BALANCE:?} in asset {asset} info, got `{:?}`", + encoded_min_balance.value + ) }) } -async fn fetch_decimals( - storage: Storage, +async fn fetch_asset_decimals( + storage: &Storage, asset: AssetId, ) -> Result { const METADATA: &str = "Metadata"; @@ -165,1165 +349,827 @@ async fn fetch_decimals( let asset_metadata = storage .fetch(&dynamic::storage(ASSETS, METADATA, vec![asset.into()])) .await - .context("failed to fetch asset info from the chain")? - .context("received nothing after fetching asset info from the chain")? + .with_context(|| format!("failed to fetch asset {asset} metadata from a chain"))? + .with_context(|| { + format!("received nothing after fetching asset {asset} metadata from a chain") + })? .to_value() - .context("failed to decode account info")?; + .with_context(|| format!("failed to decode asset {asset} metadata"))?; let encoded_decimals = asset_metadata .at(DECIMALS) - .with_context(|| format!("{DECIMALS} field wasn't found in asset info"))?; + .with_context(|| format!("{DECIMALS} field wasn't found in asset {asset} metadata"))?; - 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}") - }) + let decimals = encoded_decimals.as_u128().with_context(|| { + format!( + "expected `u128` as the type of asset {asset} {DECIMALS:?}, got `{:?}`", + encoded_decimals.value + ) + })?; + + decimals.try_into().with_context(|| { + format!("asset {asset} {DECIMALS:?} must be less than `u8`, got {decimals}") + }) } -// 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, -// }) -// } +pub async fn prepare( + chains: Vec, + account_lifetime: Timestamp, + depth: Option, +) -> Result<(HashMap, ConnectedChain>, HashMap)> { + let mut connected_chains = HashMap::with_capacity(chains.len()); + let mut currencies = HashMap::with_capacity( + chains + .iter() + .map(|chain| { + chain + .asset + .as_ref() + .map(Vec::len) + .unwrap_or_default() + .saturating_add(chain.native_token.is_some().into()) + }) + .sum(), + ); + + let (connected_chains_tx, mut connected_chains_rx) = + mpsc::unbounded_channel::(); + let (currencies_tx, mut currencies_rx) = mpsc::unbounded_channel::(); + + let connected_chains_jh = tokio::spawn(async move { + while let Some((tx, (name, chain))) = connected_chains_rx.recv().await { + tx.send(match connected_chains.entry(name) { + Entry::Occupied(entry) => Some(entry.remove_entry()), + Entry::Vacant(entry) => { + tracing::info!("Prepared the {:?} chain:\n{:#?}", entry.key(), chain); + + entry.insert(chain); + + None + } + }) + .unwrap(); + } -// 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) -// } -// } + connected_chains + }); + + let currencies_jh = tokio::spawn(async move { + while let Some((tx, (name, currency))) = currencies_rx.recv().await { + tx.send(match currencies.entry(name) { + Entry::Occupied(entry) => Some(entry.remove_entry()), + Entry::Vacant(entry) => { + tracing::info!( + %currency.chain, ?currency.asset, + "Registered the currency {:?}.", + entry.key(), + ); + + entry.insert(currency); + + None + } + }) + .unwrap(); + } -// pub struct ApiConfig { -// api: Arc, -// methods: Arc>, -// backend: Arc>, -// } + currencies + }); -// pub struct EndpointProperties { -// pub url: CheckedUrl, -// pub chain: Arc>, -// } + let (task_tracker, error_rx) = TaskTracker::new(); -// pub struct CheckedUrl(String); + for chain in chains { + task_tracker.spawn( + format!("the {:?} chain preparator", chain.name), + prepare_chain( + chain, + connected_chains_tx.clone(), + currencies_tx.clone(), + account_lifetime, + depth, + ), + ); + } -// impl CheckedUrl { -// pub fn get(self) -> String { -// self.0 -// } -// } + drop((connected_chains_tx, currencies_tx)); -pub async fn prepare(chains: Vec) -> Result> { - let mut connected_chains = HashMap::with_capacity(chains.len()); + task_tracker.try_wait(error_rx).await?; - for chain in chains { - let endpoint = chain.endpoints.first().with_context(|| { - format!( - "{:?} chain doesn't have any `endpoints` in the config", - chain.name - ) - })?; - let rpc_client = RpcClient::new( - Client::builder() - .build(endpoint.into()) + Ok((connected_chains_jh.await?, currencies_jh.await?)) +} + +#[tracing::instrument(skip_all, fields(chain = chain.name))] +async fn prepare_chain( + chain: Chain, + connected_chains: UnboundedSender, + currencies: UnboundedSender, + account_lifetime: Timestamp, + depth_option: Option, +) -> Result> { + let chain_name: Arc = chain.name.into(); + let chain_name_clone = chain_name.clone(); + let endpoint = chain + .endpoints + .first() + .context("chain doesn't have any `endpoints` in the config")?; + let rpc_client = RpcClient::new( + Client::builder() + .build(endpoint.into()) + .await + .context("failed to construct the RPC client")?, + ); + + let methods = LegacyRpcMethods::new(rpc_client.clone()); + let backend = Arc::new(LegacyBackend::builder().build(rpc_client.clone())); + + let genesis = methods + .genesis_hash() + .await + .context("failed to fetch the genesis hash")?; + let (finalized_number, finalized_hash) = fetch_finalized_head_number_and_hash(&methods).await?; + let (metadata, runtime_version) = fetch_runtime(&methods, &*backend, finalized_hash).await?; + + let client = OnlineClient::from_backend_with( + genesis, + runtime_version, + metadata.clone(), + backend.clone(), + ) + .context("failed to construct the API client")?; + let constants = client.constants(); + + let (block_time, runtime_api) = if metadata.pallet_by_name(BABE).is_some() { + const EXPECTED_BLOCK_TIME: (&str, &str) = (BABE, "ExpectedBlockTime"); + + (fetch_constant(&constants, EXPECTED_BLOCK_TIME)?, None) + } else { + const SLOT_DURATION: &str = "slot_duration"; + + let runtime_api = client.runtime_api(); + + ( + runtime_api + .at(finalized_hash) + .call(dynamic::runtime_api_call( + AURA, + SLOT_DURATION, + Vec::::new(), + )) .await - .with_context(|| { - format!( - "failed to construct the RPC client for the {:?} chain", - chain.name - ) - })?, - ); + .context("failed to fetch Aura's slot duration")? + .as_type() + .context("failed to decode Aura's slot duration")?, + Some(runtime_api), + ) + }; - log::info!( - "Connected to an RPC server for the {:?} chain at {endpoint:?}.", - chain.name - ); + let block_time_non_zero = + NonZeroU64::new(block_time).context("block interval can't equal 0")?; - let methods = LegacyRpcMethods::new(rpc_client.clone()); - let client = OnlineClient::from_rpc_client(rpc_client) - .await + let account_lifetime_in_blocks = account_lifetime / block_time_non_zero; + + if account_lifetime_in_blocks == 0 { + anyhow::bail!("block interval is longer than the given `account-lifetime`"); + } + + let depth_in_blocks = if let Some(depth) = depth_option { + let depth_in_blocks = depth / block_time_non_zero; + + if depth_in_blocks > account_lifetime_in_blocks { + anyhow::bail!("`depth` can't be greater than `account-lifetime`"); + } + + Some( + NonZeroU64::new(depth_in_blocks) + .context("block interval is longer than the given `depth`")?, + ) + } else { + None + }; + + let rpc = endpoint.into(); + + let connected_chain = if let Some(assets) = chain + .asset + .and_then(|mut assets| assets.pop().map(|latest| (latest, assets))) + { + const ASSET_ID: &str = "asset_id"; + const SOME: &str = "Some"; + + let storage_client = client.storage(); + let storage = storage_client.at(finalized_hash); + + let extension = metadata + .extrinsic() + .signed_extensions() + .iter() + .find(|extension| extension.identifier() == CHARGE_ASSET_TX_PAYMENT) + .with_context(|| { + format!("failed to find the {CHARGE_ASSET_TX_PAYMENT:?} extension in metadata") + })? + .extra_ty(); + let types = metadata.types(); + + let TypeDef::Composite(ref extension_type) = types + .resolve(extension) + .with_context(|| { + format!("failed to resolve the type of the {CHARGE_ASSET_TX_PAYMENT:?} extension") + })? + .type_def + else { + anyhow::bail!("{CHARGE_ASSET_TX_PAYMENT:?} extension has an unexpected type"); + }; + + let asset_id_field = extension_type + .fields + .iter() + .find_map(|field| { + field + .name + .as_ref() + .and_then(|name| (name == ASSET_ID).then_some(field.ty.id)) + }) .with_context(|| { format!( - "failed to construct the API client for the {:?} chain", - chain.name - ) + "failed to find the field {ASSET_ID:?} in the {CHARGE_ASSET_TX_PAYMENT:?} extension" + ) })?; - let connected_chain = ConnectedChain { - methods, - genesis: client.genesis_hash(), - assets: client - .metadata() - .pallet_by_name(ASSETS) - .map(|pallet| pallet.index()), - properties: ChainProperties::fetch( - &client.constants(), - client.storage().at_latest().await?, - chain - .asset - .map(|assets| assets.into_iter().map(|asset| asset.id).collect()) - .unwrap_or_default(), + let TypeDef::Variant(ref option) = types.resolve(asset_id_field).with_context(|| { + format!( + "failed to resolve the type of the field {ASSET_ID:?} in the {CHARGE_ASSET_TX_PAYMENT:?} extension" ) - .await?, - client, + })?.type_def else { + anyhow::bail!( + "field {ASSET_ID:?} in the {CHARGE_ASSET_TX_PAYMENT:?} extension has an unexpected type" + ); }; - log::debug!("\n{connected_chain:#?}"); + let asset_id_some = option.variants.iter().find_map(|variant| { + if variant.name == SOME { + variant.fields.first().map(|field| { + if variant.fields.len() > 1 { + tracing::warn!( + ?variant.fields, + "The field {ASSET_ID:?} in the {CHARGE_ASSET_TX_PAYMENT:?} extension contains multiple inner fields instead of just 1." + ); + } + + field.ty.id + }) + } else { + None + } + }).with_context(|| format!( + "field {ASSET_ID:?} in the {CHARGE_ASSET_TX_PAYMENT:?} extension doesn't contain the {SOME:?} variant" + ))?; + + let asset_id = &types.resolve(asset_id_some).with_context(|| { + format!( + "failed to resolve the type of the {SOME:?} variant of the field {ASSET_ID:?} in the {CHARGE_ASSET_TX_PAYMENT:?} extension" + ) + })?.type_def; - if connected_chains - .insert(chain.name.clone(), connected_chain) - .is_some() - { - anyhow::bail!( - "found `[chain]`s in the config with the same name ({:?}), all chain names must be unique", - chain.name - ); + let pallet_index = if let TypeDef::Primitive(_) = asset_id { + None + } else { + Some(metadata.pallet_by_name_err(ASSETS)?.index()) + }; + + let (properties, decimals) = ChainProperties::fetch( + chain_name, + currencies, + &constants, + chain.native_token, + Some(AssetsInfoFetcher { + assets, + storage: &storage, + pallet_index, + }), + account_lifetime_in_blocks, + depth_in_blocks, + ) + .await?; + + ConnectedChain { + methods, + genesis, + rpc, + client, + storage: Some(storage_client), + properties, + constants, + decimals, + runtime_api, + backend, } + } else { + let (properties, decimals) = ChainProperties::fetch( + chain_name, + currencies, + &constants, + chain.native_token, + None, + account_lifetime_in_blocks, + depth_in_blocks, + ) + .await?; + + ConnectedChain { + methods, + genesis, + rpc, + client, + storage: None, + properties, + constants, + decimals, + runtime_api, + backend, + } + }; + + let (tx, rx) = oneshot::channel(); + + connected_chains + .send((tx, (chain_name_clone, connected_chain))) + .unwrap(); + + if let Some((name, _)) = rx.await.unwrap() { + anyhow::bail!( + "found `[chain]`s with the same name ({name:?}) in the config, all chain names must be unique", + ); } - Ok(connected_chains) + Ok("".into()) +} + +#[derive(Debug)] +pub struct Currency { + chain: Arc, + asset: Option, } pub struct ConnectedChain { + rpc: String, methods: LegacyRpcMethods, + backend: Arc>, client: OnlineClient, genesis: BlockHash, - assets: Option, properties: ChainProperties, + constants: ConstantsClient, + storage: Option>, + runtime_api: Option>, + decimals: Option, } impl Debug for ConnectedChain { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.debug_struct(stringify!(ConnectedChain)) + .field("rpc", &self.rpc) .field("genesis", &self.genesis) - .field("assets", &self.assets) .field("properties", &self.properties) + .field("decimals", &self.decimals) .finish_non_exhaustive() } } -// pub async fn prepare( -// chains: Vec, -// 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); +#[derive(Debug)] +struct Shutdown; -// log::debug!("background scan {changed_invoice:?}"); +impl Error for Shutdown {} -// write_invoices -// .save(&Account::from(*invoice.0.value()), &changed_invoice)?; -// } -// } -// InvoiceStatus::Paid(_) => continue, -// } -// } - -// drop(write_invoices); +// Not used, but required for the `anyhow::Context` trait. +impl Display for Shutdown { + fn fmt(&self, _: &mut Formatter<'_>) -> fmt::Result { + unimplemented!() + } +} -// write_tx.commit()?; +pub struct Processor { + state: Arc, + backend: Arc>, + shutdown_notification: CancellationToken, + methods: LegacyRpcMethods, + client: OnlineClient, + storage: StorageClient, +} -// let mut subscription = self.finalized_heads().await?; +impl Processor { + pub async fn ignite(state: Arc, notif: CancellationToken) -> Result> { + let client = Client::builder().build(state.rpc.clone()).await.unwrap(); + let rpc_c = RpcClient::new(client); + let methods = LegacyRpcMethods::new(rpc_c.clone()); + let backend = Arc::new(LegacyBackend::builder().build(rpc_c)); + let onl = OnlineClient::from_backend(backend.clone()).await.unwrap(); + let st = onl.storage(); + + Processor { + state, + backend, + shutdown_notification: notif, + methods, + client: onl, + storage: st, + } + .execute() + .await + .or_else(|error| { + error + .downcast() + .map(|Shutdown| "The RPC module is shut down.".into()) + }) + } -// loop { -// self.process_finalized_heads(subscription, &mut finalized_number) -// .await?; + async fn execute(mut self) -> Result> { + let (head_number, head_hash) = self + .finalized_head_number_and_hash() + .await + .context("failed to get the chain head")?; -// log::warn!("Lost the connection while processing finalized heads. Retrying..."); + let mut next_unscanned_number; + let mut subscription; -// subscription = self -// .finalized_heads() -// .await -// .context("failed to update the subscription while processing finalized heads")?; -// } -// } + next_unscanned_number = head_number.checked_add(1).context(MAX_BLOCK_NUMBER_ERROR)?; + subscription = self.finalized_heads().await?; -// 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()); -// } + loop { + self.process_finalized_heads(subscription, &mut next_unscanned_number) + .await?; -// 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")?; + tracing::warn!("Lost the connection while processing finalized heads. Retrying..."); -// self.process_block(skipped_number, skipped_hash).await?; -// } + subscription = self + .finalized_heads() + .await + .context("failed to update the subscription while processing finalized heads")?; + } + } -// *next_unscanned = head; + async fn finalized_head_number_and_hash(&self) -> Result<(BlockNumber, BlockHash)> { + 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(()) -// } + Ok((head.block.header.number, head_hash)) + } -// 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; -// } -// } -// } -// } + async fn finalized_heads(&self) -> Result::Header>> { + self.methods + .chain_subscribe_finalized_heads() + .await + .context("failed to subscribe to finalized heads") + } -// Ok(()) -// } + 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")?; -// async fn finalized_heads(&self) -> Result::Header>> { -// self.methods -// .chain_subscribe_finalized_heads() -// .await -// .context("failed to subscribe to finalized heads") -// } + self.process_block(skipped_number, skipped_hash).await?; + } -// 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)?, -// _ => {} -// } -// } + *next_unscanned = head; -// let write_tx = self.database.write()?; -// let mut write_invoices = write_tx.invoices()?; + Ok(()) + } -// for (invoice, mut changes) in invoices_changes { -// if let InvoiceStatus::Unpaid(price) = changes.invoice.status { -// let balance = self.balance(hash, &invoice).await?; + 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; + } + } + } + } -// if balance >= price { -// changes.invoice.status = InvoiceStatus::Paid(price); + Ok(()) + } -// write_invoices.save(&invoice, &changes.invoice)?; -// } -// } -// } + async fn process_block(&self, number: BlockNumber, hash: BlockHash) -> Result<()> { + tracing::debug!("Processing the block: {number}."); -// drop(write_invoices); + 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 invoices = &mut *self.state.invoices.write().await; + + // 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(); + + #[allow(clippy::single_match)] + match (metadata.pallet.name(), &*metadata.variant.name) { + // (SYSTEM, UPDATE) => update = true, + (ASSETS, TRANSFERRED) => { + let tr = Transferred::deserialize( + event + .field_values() + .context("failed to decode event's fields")?, + ) + .context("failed to deserialize a transfer event")?; + + tracing::info!("{tr:?}"); + + #[allow(clippy::unnecessary_find_map)] + if let Some(invoic) = invoices.iter().find_map(|invoic| { + tracing::info!("{tr:?} {invoic:?}"); + tracing::info!("{}", tr.to == invoic.1.paym_acc); + tracing::info!("{}", *invoic.1.amount >= tr.amount); + + if tr.to == invoic.1.paym_acc && *invoic.1.amount <= tr.amount { + Some(invoic) + } else { + None + } + }) { + tracing::info!("{invoic:?}"); + + if !invoic.1.callback.is_empty() { + tracing::info!("{:?}", invoic.1.callback); + let req = ureq::post(&invoic.1.callback); + + let d = req + .send_json(OrderStatus { + order: invoic.0.clone(), + payment_status: PaymentStatus::Paid, + message: "".into(), + recipient: self.state.recipient.to_ss58check(), + server_info: ServerInfo { + version: env!("CARGO_PKG_VERSION"), + instance_id: String::new(), + debug: self.state.debug, + kalatori_remark: self.state.remark.clone(), + }, + order_info: Some(OrderInfo { + withdrawal_status: WithdrawalStatus::Waiting, + amount: invoic.1.amount.format(6), + currency: CurrencyInfo { + currency: "USDC".into(), + chain_name: "assethub-polkadot".into(), + kind: TokenKind::Assets, + decimals: 6, + rpc_url: self.state.rpc.clone(), + asset_id: Some(1337), + }, + callback: invoic.1.callback.clone(), + transactions: vec![], + payment_account: invoic.1.paym_acc.to_ss58check(), + }), + }) + .unwrap(); + } + + invoices.insert( + invoic.0.clone(), + Invoicee { + callback: invoic.1.callback.clone(), + amount: Balance(*invoic.1.amount), + paid: true, + paym_acc: invoic.1.paym_acc.clone(), + }, + ); + } + } + _ => {} + } + } -// write_tx.commit()?; + // for (invoice, changes) in invoices_changes { + // let price = match changes.invoice.status { + // InvoiceStatus::Unpaid(price) | InvoiceStatus::Paid(price) => price, + // }; -// if update { -// self.set_client_metadata(hash) -// .await -// .context("failed to update metadata in the finalized client")?; + // self.process_unpaid(&block, changes, hash, invoice, price) + // .await + // .context("failed to process an unpaid invoice")?; + // } -// log::info!("A metadata update has been found and applied for the finalized client."); -// } + Ok(()) + } -// Ok(()) -// } + async fn balance(&self, hash: BlockHash, account: &AccountId) -> Result { + const ACCOUNT: &str = "Account"; + const BALANCE: &str = "balance"; + + if let Some(account_info) = self + .storage + .at(hash) + .fetch(&dynamic::storage( + ASSETS, + ACCOUNT, + vec![ + Value::from(1337u32), + 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().map(Balance).with_context(|| { + format!("expected `u128` as the type of a balance, got {encoded_balance}") + }) + } else { + Ok(Balance(0)) + } + } -// 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")?; + 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, Asset::Id(1337)); + + self.client + .tx() + .create_signed(&call, signer, extensions.build()) + .await + .context("failed to create a transfer transaction") + } -// self.client.set_metadata(metadata); + // async fn current_nonce(&self, account: &AccountId) -> Result { + // self.api + // .blocks + // .at(self.finalized_head_number_and_hash().await?.0) + // .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: BlockHash, + // invoice: AccountId, + // 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(()) + // } +} -// Ok(()) -// } +fn construct_transfer(to: &AccountId, amount: u128) -> Value { + const TRANSFER_KEEP_ALIVE: &str = "transfer"; + + dbg!(amount); + + dynamic::tx( + ASSETS, + TRANSFER_KEEP_ALIVE, + vec![ + 1337.into(), + scale_value::value!(Id(Value::from_bytes(to))), + amount.into(), + ], + ) + .into_value() +} -// 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}") -// }) -// } -// } +#[derive(Debug)] +struct InvoiceChanges { + invoice: Invoicee, + incoming: HashMap, +} -// pub struct Processor { -// api: Api, -// scanner: Scanner, -// methods: Arc>, -// database: Arc, -// backend: Arc>, -// shutdown_notification: CancellationToken, -// } +#[derive(Deserialize, Debug)] +struct Transferred { + asset_id: u32, + // The implementation of `Deserialize` for `AccountId32` works only with strings. + #[serde(deserialize_with = "account_deserializer")] + from: AccountId, + #[serde(deserialize_with = "account_deserializer")] + to: AccountId, + amount: u128, +} -// 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, -// }) -// } +fn account_deserializer<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + <([u8; 32],)>::deserialize(deserializer).map(|address| AccountId::new(address.0)) +} -// pub async fn ignite( +// impl Transferred { +// fn process( // 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, +// invoices_changes: &mut HashMap, +// invoices: &mut HashMap, // ) -> 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"; +// let usd_asset = 1337u32; -// 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, -// } +// tracing::debug!("Transferred event: {self:?}"); -// 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 { +// if self.from == self.to || self.amount == 0 || self.asset_id != usd_asset { // return Ok(()); // } @@ -1333,8 +1179,8 @@ impl Debug for ConnectedChain { // .into_mut() // .incoming // .entry(self.from) -// .and_modify(|amount| *amount = amount.saturating_add(self.amount)) -// .or_insert(self.amount); +// .and_modify(|amount| *amount = Balance(amount.saturating_add(self.amount))) +// .or_insert(Balance(self.amount)); // } // Entry::Vacant(entry) => { // if let (None, Some(encoded_invoice)) = diff --git a/src/server.rs b/src/server.rs index 0d01117..c9ef898 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,14 +1,16 @@ -use crate::{AssetId, BlockNumber, Decimals, ExtrinsicIndex}; +use crate::{ + database::{Invoicee, State}, AccountId, AssetId, Balance, BlockNumber, Decimals, ExtrinsicIndex +}; use anyhow::{Context, Result}; use axum::{ - extract::{rejection::RawPathParamsRejection, MatchedPath, Query, RawPathParams}, + extract::{self, rejection::RawPathParamsRejection, MatchedPath, Query, RawPathParams}, http::{header, HeaderName, StatusCode}, response::{IntoResponse, Response}, - routing::{get, post}, - Json, Router, + routing, Json, Router, }; use serde::{Serialize, Serializer}; -use std::{collections::HashMap, future::Future, net::SocketAddr}; +use std::{borrow::Cow, collections::HashMap, future::Future, net::SocketAddr, sync::Arc}; +use subxt::ext::sp_core::{crypto::Ss58Codec, DeriveJunction, Pair}; use tokio::net::TcpListener; use tokio_util::sync::CancellationToken; @@ -19,29 +21,29 @@ const CURRENCY: &str = "currency"; const CALLBACK: &str = "callback"; #[derive(Serialize)] -struct OrderStatus { - order: String, - payment_status: PaymentStatus, - message: String, - recipient: String, - server_info: ServerInfo, +pub struct OrderStatus { + pub order: String, + pub payment_status: PaymentStatus, + pub message: String, + pub recipient: String, + pub server_info: ServerInfo, #[serde(skip_serializing_if = "Option::is_none", flatten)] - order_info: Option, + pub order_info: Option, } #[derive(Serialize)] -struct OrderInfo { - withdrawal_status: WithdrawalStatus, - amount: f64, - currency: CurrencyInfo, - callback: String, - transactions: Vec, - payment_account: String, +pub struct OrderInfo { + pub withdrawal_status: WithdrawalStatus, + pub amount: f64, + pub currency: CurrencyInfo, + pub callback: String, + pub transactions: Vec, + pub payment_account: String, } #[derive(Serialize)] #[serde(rename_all = "lowercase")] -enum PaymentStatus { +pub enum PaymentStatus { Pending, Paid, Unknown, @@ -49,7 +51,7 @@ enum PaymentStatus { #[derive(Serialize)] #[serde(rename_all = "lowercase")] -enum WithdrawalStatus { +pub enum WithdrawalStatus { Waiting, Failed, Completed, @@ -84,35 +86,35 @@ enum Health { } #[derive(Serialize)] -struct CurrencyInfo { - currency: String, - chain_name: String, - kind: TokenKind, - decimals: Decimals, - rpc_url: String, +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")] - asset_id: Option, + pub asset_id: Option, } #[derive(Serialize)] #[serde(rename_all = "lowercase")] -enum TokenKind { +pub enum TokenKind { Assets, Balances, } #[derive(Serialize)] -struct ServerInfo { - version: &'static str, - instance_id: String, +pub struct ServerInfo { + pub version: &'static str, + pub instance_id: String, #[serde(skip_serializing_if = "Option::is_none")] - debug: Option, + pub debug: Option, #[serde(skip_serializing_if = "Option::is_none")] - kalatori_remark: Option, + pub kalatori_remark: Option, } #[derive(Serialize)] -struct TransactionInfo { +pub struct TransactionInfo { #[serde(skip_serializing_if = "Option::is_none", flatten)] finalized_tx: Option, transaction_bytes: String, @@ -154,12 +156,12 @@ enum TxStatus { pub async fn new( shutdown_notification: CancellationToken, host: SocketAddr, -) -> Result>> { + state: Arc, +) -> Result>>> { let v2 = Router::new() - .route("/order/:order_id", post(order)) - .route("/status", get(status)) - .route("/health", get(health)); - let app = Router::new().nest("/v2", v2); + .route("/order/:order_id", routing::post(order)) + .route("/status", routing::get(status)); + let app = Router::new().nest("/v2", v2).with_state(state); let listener = TcpListener::bind(host) .await @@ -194,7 +196,9 @@ struct InvalidParameter { message: String, } -fn process_order( +#[allow(clippy::too_many_lines)] +async fn process_order( + state: extract::State>, matched_path: &MatchedPath, path_result: Result, query: &HashMap, @@ -212,22 +216,61 @@ fn process_order( if query.is_empty() { // TODO: try to query an order from the database. - Ok(( - OrderStatus { - order, - payment_status: PaymentStatus::Unknown, - message: String::new(), - recipient: String::new(), - server_info: ServerInfo { - version: env!("CARGO_PKG_VERSION"), - instance_id: String::new(), - debug: None, - kalatori_remark: None, + let invoices = state.0.invoices.read().await; + + if let Some(invoice) = invoices.get(&order) { + Ok(( + OrderStatus { + order, + payment_status: if invoice.paid { + PaymentStatus::Paid + } else { + PaymentStatus::Unknown + }, + message: String::new(), + recipient: state.0.recipient.to_ss58check(), + server_info: ServerInfo { + version: env!("CARGO_PKG_VERSION"), + instance_id: String::new(), + debug: state.0.debug, + kalatori_remark: state.remark.clone(), + }, + order_info: Some(OrderInfo { + withdrawal_status: WithdrawalStatus::Waiting, + amount: invoice.amount.format(6), + currency: CurrencyInfo { + currency: "USDC".into(), + chain_name: "assethub-polkadot".into(), + kind: TokenKind::Assets, + decimals: 6, + rpc_url: state.rpc.clone(), + asset_id: Some(1337), + }, + callback: invoice.callback.clone(), + transactions: vec![], + payment_account: invoice.paym_acc.to_ss58check(), + }), }, - order_info: None, - }, - OrderSuccess::Found, - )) + OrderSuccess::Found, + )) + } else { + Ok(( + OrderStatus { + order, + payment_status: PaymentStatus::Unknown, + message: String::new(), + recipient: state.0.recipient.to_ss58check(), + server_info: ServerInfo { + version: env!("CARGO_PKG_VERSION"), + instance_id: String::new(), + debug: state.0.debug, + kalatori_remark: state.remark.clone(), + }, + order_info: None, + }, + OrderSuccess::Found, + )) + } } else { let get_parameter = |parameter: &str| { query @@ -243,40 +286,60 @@ fn process_order( // TODO: try to query & update or create an order in the database. - if currency == "USDCT" { + if currency != "USDC" { return Err(OrderError::UnknownCurrency); } - if amount < 50.0 { - return Err(OrderError::LessThanExistentialDeposit(50.0)); + if amount < 0.07 { + return Err(OrderError::LessThanExistentialDeposit(0.07)); } + let mut invoices = state.0.invoices.write().await; + let pay_acc: AccountId = state + .0 + .pair + .derive(vec![DeriveJunction::hard(order.clone())].into_iter(), None) + .unwrap() + .0 + .public() + .into(); + + invoices.insert( + order.clone(), + Invoicee { + callback: callback.clone(), + amount: Balance::parse(amount, 6), + paid: false, + paym_acc: pay_acc.clone(), + }, + ); + Ok(( OrderStatus { order, payment_status: PaymentStatus::Pending, message: String::new(), - recipient: String::new(), + recipient: state.0.recipient.to_ss58check(), server_info: ServerInfo { version: env!("CARGO_PKG_VERSION"), instance_id: String::new(), - debug: None, - kalatori_remark: None, + debug: state.0.debug, + kalatori_remark: state.0.remark.clone(), }, order_info: Some(OrderInfo { withdrawal_status: WithdrawalStatus::Waiting, amount, currency: CurrencyInfo { - currency, - chain_name: String::new(), - kind: TokenKind::Balances, - decimals: 0, - rpc_url: String::new(), - asset_id: None, + currency: "USDC".into(), + chain_name: "assethub-polkadot".into(), + kind: TokenKind::Assets, + decimals: 6, + rpc_url: state.rpc.clone(), + asset_id: Some(1337), }, callback, transactions: vec![], - payment_account: String::new(), + payment_account: pay_acc.to_ss58check(), }), }, OrderSuccess::Created, @@ -285,11 +348,12 @@ fn process_order( } async fn order( + state: extract::State>, matched_path: MatchedPath, path_result: Result, query: Query>, ) -> Response { - match process_order(&matched_path, path_result, &query) { + match process_order(state, &matched_path, path_result, &query).await { Ok((order_status, order_success)) => match order_success { OrderSuccess::Created => (StatusCode::CREATED, Json(order_status)), OrderSuccess::Found => (StatusCode::OK, Json(order_status)), @@ -336,47 +400,27 @@ async fn order( } } -async fn status() -> ([(HeaderName, &'static str); 1], Json) { +async fn status( + state: extract::State>, +) -> ([(HeaderName, &'static str); 1], Json) { ( [(header::CACHE_CONTROL, "no-store")], ServerStatus { description: ServerInfo { version: env!("CARGO_PKG_VERSION"), instance_id: String::new(), - debug: None, - kalatori_remark: None, + debug: state.0.debug, + kalatori_remark: state.0.remark.clone(), }, supported_currencies: vec![CurrencyInfo { - currency: String::new(), - chain_name: String::new(), - kind: TokenKind::Balances, - decimals: 0, - rpc_url: String::new(), - asset_id: None, + currency: "USDC".into(), + chain_name: "assethub-polkadot".into(), + kind: TokenKind::Assets, + decimals: 6, + rpc_url: state.rpc.clone(), + asset_id: Some(1337), }], } .into(), ) } - -async fn health() -> ([(HeaderName, &'static str); 1], Json) { - ( - [(header::CACHE_CONTROL, "no-store")], - ServerHealth { - description: ServerInfo { - version: env!("CARGO_PKG_VERSION"), - instance_id: String::new(), - debug: None, - kalatori_remark: None, - }, - connected_rpcs: [RpcInfo { - rpc_url: String::new(), - chain_name: String::new(), - status: Health::Critical, - }] - .into(), - status: Health::Degraded, - } - .into(), - ) -} From 0e070ede86533c073a8130cca53f1e2cb16a929f Mon Sep 17 00:00:00 2001 From: Fluid <90795031+fluiderson@users.noreply.github.com> Date: Mon, 1 Apr 2024 09:43:02 +0300 Subject: [PATCH 03/76] remark and debug --- kalatori.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/kalatori.toml b/kalatori.toml index 75c2091..7a0846b 100644 --- a/kalatori.toml +++ b/kalatori.toml @@ -1,6 +1,8 @@ recipient = "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty" account-lifetime = 604800000 # 1 week. depth = 86400000 # 1 day. +remark = "34t5786437845t" +debug = true [[chain]] name = "polkadot" From ebc08237389ea4ce7807b72c163dca1fe9385077 Mon Sep 17 00:00:00 2001 From: Fluid <90795031+fluiderson@users.noreply.github.com> Date: Wed, 10 Apr 2024 20:50:13 +0300 Subject: [PATCH 04/76] add structure.md --- .gitignore | 4 +- Cargo.lock | 209 ++++++++--------- Cargo.toml | 2 +- configs/kusama.toml | 11 + kalatori.toml => configs/polkadot.toml | 23 -- kalatori-test.toml => configs/testnets.toml | 2 +- docs/structure.md | 176 +++++++++++++++ src/callback.rs | 57 +++++ src/database.rs | 6 +- src/main.rs | 238 +++++++++++--------- src/rpc.rs | 43 +--- src/server.rs | 8 +- start-ah.sh | 8 - 13 files changed, 506 insertions(+), 281 deletions(-) create mode 100644 configs/kusama.toml rename kalatori.toml => configs/polkadot.toml (50%) rename kalatori-test.toml => configs/testnets.toml (93%) create mode 100644 docs/structure.md delete mode 100755 start-ah.sh diff --git a/.gitignore b/.gitignore index 65420fe..0e6ca42 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,2 @@ target -database.redb -database-ah-usdc.redb -database-ah-usdt.redb +kalatori.db diff --git a/Cargo.lock b/Cargo.lock index 36649c7..93be423 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -111,9 +111,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.81" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" +checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519" [[package]] name = "ark-bls12-377" @@ -278,7 +278,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f28243a43d821d11341ab73c80bed182dc015c514b951616cf79bd4af39af0c3" dependencies = [ "concurrent-queue", - "event-listener 5.2.0", + "event-listener 5.3.0", "event-listener-strategy 0.5.1", "futures-core", "pin-project-lite", @@ -286,9 +286,9 @@ dependencies = [ [[package]] name = "async-executor" -version = "1.9.1" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10b3e585719c2358d2660232671ca8ca4ddb4be4ce8a1842d6c2dc8685303316" +checksum = "5f98c37cf288e302c16ef6c8472aad1e034c6c84ce5ea7b8101c98eb4a802fee" dependencies = [ "async-lock 3.3.0", "async-task", @@ -372,7 +372,7 @@ dependencies = [ "async-task", "blocking", "cfg-if", - "event-listener 5.2.0", + "event-listener 5.3.0", "futures-lite", "rustix 0.38.32", "tracing", @@ -411,7 +411,7 @@ checksum = "a507401cad91ec6a857ed5513a2073c82a9b9048762b885bb98655b306964681" dependencies = [ "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.58", ] [[package]] @@ -458,7 +458,7 @@ dependencies = [ "serde_json", "serde_path_to_error", "serde_urlencoded", - "sync_wrapper 1.0.0", + "sync_wrapper 1.0.1", "tokio", "tower", "tower-layer", @@ -524,6 +524,12 @@ version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" +[[package]] +name = "base64" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9475866fec1451be56a3c2400fd081ff546538961565ccb5b7142cbd22bc7a51" + [[package]] name = "base64ct" version = "1.6.0" @@ -690,9 +696,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.15.4" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "byte-slice-cast" @@ -714,9 +720,9 @@ checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" [[package]] name = "cc" -version = "1.0.90" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5" +checksum = "2678b2e3449475e95b0aa6f9b506a28e61b3dc8996592b983695e8ebb58a8b41" [[package]] name = "cfg-if" @@ -946,7 +952,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.58", ] [[package]] @@ -994,7 +1000,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.57", + "syn 2.0.58", ] [[package]] @@ -1016,14 +1022,14 @@ checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f" dependencies = [ "darling_core 0.20.8", "quote", - "syn 2.0.57", + "syn 2.0.58", ] [[package]] name = "der" -version = "0.7.8" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" dependencies = [ "const-oid", "zeroize", @@ -1051,13 +1057,13 @@ dependencies = [ [[package]] name = "derive-syn-parse" -version = "0.1.5" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e79116f119dd1dba1abf1f3405f03b9b0e79a27a3883864bfebded8a3dc768cd" +checksum = "d65d7ce8132b7c0e54497a4d9a55a1c2a0912a0d786cf894472ba818fba45762" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.58", ] [[package]] @@ -1096,18 +1102,18 @@ dependencies = [ [[package]] name = "docify" -version = "0.2.7" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cc4fd38aaa9fb98ac70794c82a00360d1e165a87fbf96a8a91f9dfc602aaee2" +checksum = "43a2f138ad521dc4a2ced1a4576148a6a610b4c5923933b062a263130a6802ce" dependencies = [ "docify_macros", ] [[package]] name = "docify_macros" -version = "0.2.7" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63fa215f3a0d40fb2a221b3aa90d8e1fbb8379785a990cb60d62ac71ebdc6460" +checksum = "1a081e51fb188742f5a7a1164ad752121abcb22874b21e2c3b0dd040c515fdad" dependencies = [ "common-path", "derive-syn-parse", @@ -1115,7 +1121,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.57", + "syn 2.0.58", "termcolor", "toml", "walkdir", @@ -1123,9 +1129,9 @@ dependencies = [ [[package]] name = "downcast-rs" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" [[package]] name = "dyn-clonable" @@ -1289,9 +1295,9 @@ dependencies = [ [[package]] name = "event-listener" -version = "5.2.0" +version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b5fb89194fa3cad959b833185b3063ba881dbfc7030680b314250779fb4cc91" +checksum = "6d9944b8ca13534cdfb2800775f8dd4902ff3fc75a50101466decadfdf322a24" dependencies = [ "concurrent-queue", "parking", @@ -1314,7 +1320,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "332f51cb23d20b0de8458b86580878211da09bcd4503cb579c225b3d124cabb3" dependencies = [ - "event-listener 5.2.0", + "event-listener 5.3.0", "pin-project-lite", ] @@ -1329,7 +1335,7 @@ dependencies = [ "prettier-please", "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.58", ] [[package]] @@ -1495,7 +1501,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.58", ] [[package]] @@ -1547,9 +1553,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.12" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" dependencies = [ "cfg-if", "libc", @@ -1596,9 +1602,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fbd2820c5e49886948654ab546d0688ff24530286bdcf8fca3cefb16d4618eb" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" dependencies = [ "bytes", "fnv", @@ -2032,9 +2038,9 @@ dependencies = [ [[package]] name = "jsonrpsee" -version = "0.22.3" +version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cdbb7cb6f3ba28f5b212dd250ab4483105efc3e381f5c8bb90340f14f0a2cc3" +checksum = "c4b0e68d9af1f066c06d6e2397583795b912d78537d7d907c561e82c13d69fa1" dependencies = [ "jsonrpsee-client-transport", "jsonrpsee-core", @@ -2045,9 +2051,9 @@ dependencies = [ [[package]] name = "jsonrpsee-client-transport" -version = "0.22.3" +version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ab2e14e727d2faf388c99d9ca5210566ed3b044f07d92c29c3611718d178380" +checksum = "92f254f56af1ae84815b9b1325094743dcf05b92abb5e94da2e81a35cff0cada" dependencies = [ "futures-util", "http 0.2.12", @@ -2066,12 +2072,11 @@ dependencies = [ [[package]] name = "jsonrpsee-core" -version = "0.22.3" +version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71962a1c49af43adf81d337e4ebc93f3c915faf6eccaa14d74e255107dfd7723" +checksum = "274d68152c24aa78977243bb56f28d7946e6aa309945b37d33174a3f92d89a3a" dependencies = [ "anyhow", - "async-lock 3.3.0", "async-trait", "beef", "futures-timer", @@ -2090,9 +2095,9 @@ dependencies = [ [[package]] name = "jsonrpsee-http-client" -version = "0.22.3" +version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c13987da51270bda2c1c9b40c19be0fe9b225c7a0553963d8f17e683a50ce84" +checksum = "ac13bc1e44cd00448a5ff485824a128629c945f02077804cb659c07a0ba41395" dependencies = [ "async-trait", "hyper 0.14.28", @@ -2110,9 +2115,9 @@ dependencies = [ [[package]] name = "jsonrpsee-types" -version = "0.22.3" +version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e53c72de6cd2ad6ac1aa6e848206ef8b736f92ed02354959130373dfa5b3cbd" +checksum = "3dc828e537868d6b12bbb07ec20324909a22ced6efca0057c825c3e1126b2c6d" dependencies = [ "anyhow", "beef", @@ -2123,9 +2128,9 @@ dependencies = [ [[package]] name = "jsonrpsee-ws-client" -version = "0.22.3" +version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8a07ab8da9a283b906f6735ddd17d3680158bb72259e853441d1dd0167079ec" +checksum = "32f00abe918bf34b785f87459b9205790e5361a3f7437adb50e928dc243f27eb" dependencies = [ "http 0.2.12", "jsonrpsee-client-transport", @@ -2150,7 +2155,7 @@ dependencies = [ [[package]] name = "kalatori" -version = "0.2.0" +version = "0.2.0-rc2" dependencies = [ "anyhow", "axum", @@ -2674,7 +2679,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.58", ] [[package]] @@ -2755,7 +2760,7 @@ dependencies = [ "polkavm-common 0.8.0", "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.58", ] [[package]] @@ -2767,7 +2772,7 @@ dependencies = [ "polkavm-common 0.9.0", "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.58", ] [[package]] @@ -2777,7 +2782,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "15e85319a0d5129dc9f021c62607e0804f5fb777a05cdda44d750ac0732def66" dependencies = [ "polkavm-derive-impl 0.8.0", - "syn 2.0.57", + "syn 2.0.58", ] [[package]] @@ -2787,7 +2792,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ba81f7b5faac81e528eb6158a6f3c9e0bb1008e0ffa19653bc8dea925ecb429" dependencies = [ "polkavm-derive-impl 0.9.0", - "syn 2.0.57", + "syn 2.0.58", ] [[package]] @@ -2835,7 +2840,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22020dfcf177fcc7bf5deaf7440af371400c67c0de14c399938d8ed4fb4645d3" dependencies = [ "proc-macro2", - "syn 2.0.57", + "syn 2.0.58", ] [[package]] @@ -2923,9 +2928,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.35" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] @@ -3023,7 +3028,7 @@ checksum = "5fddb4f8d99b0a2ebafc65a87a69a7b9875e4b1ae1f00db265d300ef7f28bccc" dependencies = [ "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.58", ] [[package]] @@ -3194,7 +3199,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f1fb85efa936c42c6d5fc28d2629bb51e4b2f4b8a5211e297d599cc5a093792" dependencies = [ "openssl-probe", - "rustls-pemfile 2.1.1", + "rustls-pemfile 2.1.2", "rustls-pki-types", "schannel", "security-framework", @@ -3211,11 +3216,11 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f48172685e6ff52a556baa527774f61fcaa884f59daf3375c62a3f1cd2549dab" +checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" dependencies = [ - "base64 0.21.7", + "base64 0.22.0", "rustls-pki-types", ] @@ -3248,9 +3253,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" +checksum = "80af6f9131f277a45a3fba6ce8e2258037bb0477a67e610d3c1fe046ab31de47" [[package]] name = "ruzstd" @@ -3347,9 +3352,9 @@ dependencies = [ [[package]] name = "scale-info" -version = "2.11.1" +version = "2.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "788745a868b0e751750388f4e6546eb921ef714a4317fa6954f7cde114eb2eb7" +checksum = "7c453e59a955f81fb62ee5d596b450383d699f152d350e9d23a0db2adb78e4c0" dependencies = [ "bitvec", "cfg-if", @@ -3361,9 +3366,9 @@ dependencies = [ [[package]] name = "scale-info-derive" -version = "2.11.1" +version = "2.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dc2f4e8bc344b9fc3d5f74f72c2e55bfc38d28dc2ebc69c194a3df424e4d9ac" +checksum = "18cf6c6447f813ef19eb450e985bcce6705f9ce7660db221b59093d15c79c4b7" dependencies = [ "proc-macro-crate 1.3.1", "proc-macro2", @@ -3383,14 +3388,14 @@ dependencies = [ [[package]] name = "scale-typegen" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d6108609f017741c78d35967c7afe4aeaa3999b848282581041428e10d23b63" +checksum = "d470fa75e71b12b3244a4113adc4bc49891f3daba2054703cacd06256066397e" dependencies = [ "proc-macro2", "quote", "scale-info", - "syn 2.0.57", + "syn 2.0.58", "thiserror", ] @@ -3567,7 +3572,7 @@ checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.58", ] [[package]] @@ -3955,7 +3960,7 @@ checksum = "48d09fa0a5f7299fb81ee25ae3853d26200f7a348148aed6de76be905c007dbe" dependencies = [ "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.58", ] [[package]] @@ -4076,7 +4081,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.58", ] [[package]] @@ -4259,9 +4264,9 @@ checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" [[package]] name = "subxt" -version = "0.35.0" +version = "0.35.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7542baa3949f5dbddd85491e8eb75f4935ca21fbb80781fb86c5b32e317bb4bf" +checksum = "388162448313740aabe675bff00698e72f876b1c6ec85d4d2c34783cfa32a0f7" dependencies = [ "async-trait", "base58", @@ -4298,9 +4303,9 @@ dependencies = [ [[package]] name = "subxt-codegen" -version = "0.35.0" +version = "0.35.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2afcd8f197bd7fc8bc215b381d6464005dd0a594e1ec3ff5ef310930019ac69f" +checksum = "cd94344feea939a37b919b7381e038dededfd1a88e01bedda67fe40847abfc78" dependencies = [ "frame-metadata 16.0.0", "heck", @@ -4312,16 +4317,16 @@ dependencies = [ "scale-info", "scale-typegen", "subxt-metadata", - "syn 2.0.57", + "syn 2.0.58", "thiserror", "tokio", ] [[package]] name = "subxt-lightclient" -version = "0.35.0" +version = "0.35.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6e8ec205884dfb7aae0ec9755233c2006b54d9e0135efedc6095682f01f3437" +checksum = "c0dbc6ed49c3c5607fc7423d7ebda4dae858eb3040cdaec602105a240d4f412f" dependencies = [ "futures", "futures-util", @@ -4336,9 +4341,9 @@ dependencies = [ [[package]] name = "subxt-macro" -version = "0.35.0" +version = "0.35.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e48afc319f8f413270da8cefda8141228bd1b3d17394f6daccfc03b7623fe24f" +checksum = "4707a920898a7c653210bc946d26904e81ae6fcbb4f91e3a56101d5979f72fe9" dependencies = [ "darling 0.20.8", "parity-scale-codec", @@ -4346,14 +4351,14 @@ dependencies = [ "quote", "scale-typegen", "subxt-codegen", - "syn 2.0.57", + "syn 2.0.58", ] [[package]] name = "subxt-metadata" -version = "0.35.0" +version = "0.35.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cfbc0746a40aec24e535973d0a158de4968ea3f23399106d2bb63eb41900d8d" +checksum = "65ffc8b7d246ebd38611f818547ee8e09fd69717cb79aae22e3a54fc423e6e14" dependencies = [ "derive_more", "frame-metadata 16.0.0", @@ -4376,9 +4381,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.57" +version = "2.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11a6ae1e52eb25aab8f3fb9fca13be982a373b8f1157ca14b897a825ba4a2d35" +checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687" dependencies = [ "proc-macro2", "quote", @@ -4393,9 +4398,9 @@ 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" [[package]] name = "tap" @@ -4435,7 +4440,7 @@ checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.58", ] [[package]] @@ -4450,9 +4455,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.34" +version = "0.3.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749" +checksum = "ef89ece63debf11bc32d1ed8d078ac870cbeb44da02afb02a9ff135ae7ca0582" dependencies = [ "deranged", "itoa", @@ -4471,9 +4476,9 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.17" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ba3a3ef41e6672a2f0f001392bb5dcd3ff0a9992d618ca761a11c3121547774" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" dependencies = [ "num-conv", "time-core", @@ -4520,7 +4525,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.58", ] [[package]] @@ -4698,7 +4703,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.58", ] [[package]] @@ -4986,7 +4991,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.58", "wasm-bindgen-shared", ] @@ -5008,7 +5013,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.58", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -5501,7 +5506,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.58", ] [[package]] @@ -5521,5 +5526,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.58", ] diff --git a/Cargo.toml b/Cargo.toml index 8c55877..c7db69f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "kalatori" authors = ["Alzymologist Oy "] -version = "0.2.0" +version = "0.2.0-rc2" edition = "2021" description = "A gateway daemon for Kalatori." license = "GPL-3.0-or-later" 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/kalatori.toml b/configs/polkadot.toml similarity index 50% rename from kalatori.toml rename to configs/polkadot.toml index 7a0846b..3a80a58 100644 --- a/kalatori.toml +++ b/configs/polkadot.toml @@ -1,8 +1,5 @@ -recipient = "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty" account-lifetime = 604800000 # 1 week. depth = 86400000 # 1 day. -remark = "34t5786437845t" -debug = true [[chain]] name = "polkadot" @@ -27,23 +24,3 @@ id = 1337 [[chain.asset]] name = "USDT" id = 1984 - -[[chain]] -name = "kusama" -native-token = "KSM" -decimals = 12 -endpoints = [ - "wss://kusama-rpc.polkadot.io", - "wss://1rpc.io/ksm", -] - -[[chain]] -name = "assethub-kusama" -endpoints = [ - "wss://kusama-asset-hub-rpc.polkadot.io", - "wss://statemine-rpc.dwellir.com", -] - -[[chain.asset]] -name = "RMRK" -id = 8 diff --git a/kalatori-test.toml b/configs/testnets.toml similarity index 93% rename from kalatori-test.toml rename to configs/testnets.toml index 195ea86..223773a 100644 --- a/kalatori-test.toml +++ b/configs/testnets.toml @@ -1,7 +1,7 @@ -recipient = "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty" account-lifetime = 86400000 # 1 day. depth = 3600000 # 1 hour. debug = true +in-memory-db = true [[chain]] name = "westend" diff --git a/docs/structure.md b/docs/structure.md new file mode 100644 index 0000000..d862c35 --- /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 standart, 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 standarts. + +#### [`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 standart, 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/src/callback.rs b/src/callback.rs index 7763b55..d2c4a73 100644 --- a/src/callback.rs +++ b/src/callback.rs @@ -1 +1,58 @@ +use crate::{ + server::{ + CurrencyInfo, OrderInfo, OrderStatus, PaymentStatus, ServerInfo, TokenKind, + WithdrawalStatus, + }, + AccountId, Balance, +}; +use subxt::ext::sp_core::crypto::Ss58Codec; +use tokio::task; + pub const MODULE: &str = module_path!(); + +pub async fn callback( + path: String, + order: String, + recipient: AccountId, + debug: Option, + remark: Option, + amount: Balance, + rpc_url: String, + paym_acc: AccountId, +) { + let req = ureq::post(&path); + + task::spawn_blocking(move || { + let d = req + .send_json(OrderStatus { + order, + payment_status: PaymentStatus::Paid, + message: String::new(), + recipient: recipient.to_ss58check(), + server_info: ServerInfo { + version: env!("CARGO_PKG_VERSION"), + instance_id: String::new(), + debug, + kalatori_remark: remark, + }, + order_info: Some(OrderInfo { + withdrawal_status: WithdrawalStatus::Waiting, + amount: amount.format(6), + currency: CurrencyInfo { + currency: "USDC".into(), + chain_name: "assethub-polkadot".into(), + kind: TokenKind::Asset, + decimals: 6, + rpc_url, + asset_id: Some(1337), + }, + callback: path, + transactions: vec![], + payment_account: paym_acc.to_ss58check(), + }), + }) + .unwrap(); + }) + .await + .unwrap(); +} diff --git a/src/database.rs b/src/database.rs index b428a51..8dceb48 100644 --- a/src/database.rs +++ b/src/database.rs @@ -191,8 +191,8 @@ impl State { pub fn initialise( path_option: Option, currencies: HashMap, - current_pair: (Pair, Public), - old_pairs: HashMap, + current_pair: Pair, + old_pairs: HashMap, ConfigWoChains { recipient, debug, @@ -237,7 +237,7 @@ impl State { currencies, recipient: AccountId::from_string(&recipient) .context("failed to convert \"recipient\" from the config to an account address")?, - pair: current_pair.0, + pair: current_pair, depth, account_lifetime, debug, diff --git a/src/main.rs b/src/main.rs index b71b75e..d989b60 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,7 +15,7 @@ use subxt::{ config::{substrate::SubstrateHeader, PolkadotExtrinsicParams}, ext::sp_core::{ crypto::{AccountId32, Ss58Codec}, - sr25519::Pair, + sr25519::{Pair, Public}, Pair as _, }, PolkadotConfig, @@ -42,13 +42,15 @@ use rpc::Processor; const CONFIG: &str = "KALATORI_CONFIG"; const LOG: &str = "KALATORI_LOG"; const SEED: &str = "KALATORI_SEED"; +const RECIPIENT: &str = "KALATORI_RECIPIENT"; +const REMARK: &str = "KALATORI_REMARK"; const OLD_SEED: &str = "KALATORI_OLD_SEED_"; const DB_VERSION: Version = 0; -const DEFAULT_CONFIG: &str = "kalatori.toml"; +const DEFAULT_CONFIG: &str = "configs/polkadot.toml"; const DEFAULT_SOCKET: SocketAddr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 16726); -const DEFAULT_DATABASE: &str = "kalatori.xlsx"; +const DEFAULT_DATABASE: &str = "kalatori.db"; type AssetId = u32; type Decimals = u8; @@ -77,77 +79,22 @@ impl subxt::Config for RuntimeConfig { } #[tokio::main] -#[allow(clippy::too_many_lines)] async fn main() -> Result<()> { - let filter = match EnvFilter::try_from_env(LOG) { - Err(error) => { - let Some(VarError::NotPresent) = error - .source() - .expect("should always be `Some`") - .downcast_ref() - else { - return Err(error).with_context(|| format!("failed to parse `{LOG}`")); - }; - - if cfg!(debug_assertions) { - EnvFilter::try_new("debug") - } else { - EnvFilter::try_new(default_filter()) - } - .unwrap() - } - Ok(filter) => filter, - }; - - tracing_subscriber::fmt() - .with_timer(UtcTime::rfc_3339()) - .with_env_filter(filter) - .init(); - - let pair = Pair::from_string( - &env::var(SEED).with_context(|| format!("failed to read `{SEED}`"))?, - None, - ) - .with_context(|| format!("failed to generate a key pair from `{SEED}`"))?; - let pair_public = pair.public(); - - let mut old_seeds = HashMap::new(); - - for (raw_key, raw_value) in env::vars_os() { - let raw_key_bytes = raw_key.as_encoded_bytes(); - - if let Some(stripped_raw_key) = raw_key_bytes.strip_prefix(OLD_SEED.as_bytes()) { - let key = str::from_utf8(stripped_raw_key) - .context("failed to read an old seed environment variable name")?; - let value = raw_value - .to_str() - .with_context(|| format!("failed to read a seed phrase from `{OLD_SEED}{key}`"))?; - let old_pair = Pair::from_string(value, None) - .with_context(|| format!("failed to generate a key pair from `{OLD_SEED}{key}`"))?; - let old_pair_public = old_pair.public(); + let shutdown_notification = CancellationToken::new(); - if old_pair_public == pair_public { - anyhow::bail!("public key generated from `{OLD_SEED}{key}` equals the one generated from `{SEED}`"); - } + set_panic_hook(shutdown_notification.clone()); + initialize_logger()?; - old_seeds.insert(key.to_owned(), (old_pair, old_pair_public)); - } - } + let (pair, old_pairs) = parse_seeds()?; + let recipient = env::var(RECIPIENT).with_context(|| format!("failed to read {RECIPIENT}"))?; - let config_path = env::var(CONFIG).or_else(|error| match error { - VarError::NotUnicode(_) => Err(error).with_context(|| format!("failed to read `{CONFIG}`")), - VarError::NotPresent => { - tracing::debug!( - "`{CONFIG}` isn't present, using the default value instead: {DEFAULT_CONFIG:?}." - ); + let remark = match env::var(REMARK) { + Ok(remark) => Some(remark), + Err(VarError::NotPresent) => None, + Err(error) => Err(error).with_context(|| format!("failed to read {REMARK}"))?, + }; - Ok(DEFAULT_CONFIG.into()) - } - })?; - let unparsed_config = fs::read_to_string(&config_path) - .with_context(|| format!("failed to read a config file at {config_path:?}"))?; - let config: Config = de::from_str(&unparsed_config) - .with_context(|| format!("failed to parse the config at {config_path:?}"))?; + let config = Config::parse()?; let host = if let Some(unparsed_host) = config.host { unparsed_host @@ -162,6 +109,12 @@ async fn main() -> Result<()> { let database_path = 'database: { if debug { if config.in_memory_db.unwrap_or_default() { + if config.database.is_some() { + tracing::warn!( + "`database` is set in the config but ignored because `in_memory_db` is \"true\"" + ); + } + break 'database None; } } else if config.in_memory_db.is_some() { @@ -185,33 +138,6 @@ async fn main() -> Result<()> { env!("CARGO_PKG_AUTHORS") ); - let shutdown_notification = CancellationToken::new(); - let shutdown_notification_for_panic = shutdown_notification.clone(); - - panic::set_hook(Box::new(move |panic_info| { - let at = panic_info - .location() - .map(|location| format!(" at `{location}`")) - .unwrap_or_default(); - let payload = panic_info.payload(); - - let message = match payload.downcast_ref::<&str>() { - Some(string) => Some(*string), - None => payload.downcast_ref::().map(|string| &string[..]), - }; - let formatted_message = match message { - Some(string) => format!(":\n{string}\n"), - None => ".".into(), - }; - - tracing::error!( - "A panic detected{at}{formatted_message}\nThis is a bug. Please report it at {}.", - env!("CARGO_PKG_REPOSITORY") - ); - - shutdown_notification_for_panic.cancel(); - })); - let (task_tracker, error_rx) = TaskTracker::new(); task_tracker.spawn( @@ -228,12 +154,12 @@ async fn main() -> Result<()> { let state = State::initialise( database_path, currencies, - (pair, pair_public), - old_seeds, + pair, + old_pairs, ConfigWoChains { - recipient: config.recipient, + recipient, debug: config.debug, - remark: config.remark, + remark, depth: config.depth, account_lifetime: config.account_lifetime, rpc, @@ -241,7 +167,6 @@ async fn main() -> Result<()> { ) .context("failed to initialise the database module")?; - task_tracker.spawn( "proc", Processor::ignite(state.clone(), shutdown_notification.clone()), @@ -266,6 +191,61 @@ async fn main() -> Result<()> { Ok(()) } +fn set_panic_hook(shutdown_notification: CancellationToken) { + panic::set_hook(Box::new(move |panic_info| { + let at = panic_info + .location() + .map(|location| format!(" at `{location}`")) + .unwrap_or_default(); + let payload = panic_info.payload(); + + let message = match payload.downcast_ref::<&str>() { + Some(string) => Some(*string), + None => payload.downcast_ref::().map(|string| &string[..]), + }; + let formatted_message = match message { + Some(string) => format!(":\n{string}\n"), + None => ".".into(), + }; + + tracing::error!( + "A panic detected{at}{formatted_message}\nThis is a bug. Please report it at {}.", + env!("CARGO_PKG_REPOSITORY") + ); + + shutdown_notification.cancel(); + })); +} + +fn initialize_logger() -> Result<()> { + let filter = match EnvFilter::try_from_env(LOG) { + Err(error) => { + let Some(VarError::NotPresent) = error + .source() + .expect("should always be `Some`") + .downcast_ref() + else { + return Err(error).with_context(|| format!("failed to parse `{LOG}`")); + }; + + if cfg!(debug_assertions) { + EnvFilter::try_new("debug") + } else { + EnvFilter::try_new(default_filter()) + } + .unwrap() + } + Ok(filter) => filter, + }; + + tracing_subscriber::fmt() + .with_timer(UtcTime::rfc_3339()) + .with_env_filter(filter) + .init(); + + Ok(()) +} + fn default_filter() -> String { const TARGETS: &[&str] = &[ callback::MODULE, @@ -303,6 +283,34 @@ fn default_filter() -> String { filter } +fn parse_seeds() -> Result<(Pair, HashMap)> { + let pair = Pair::from_string( + &env::var(SEED).with_context(|| format!("failed to read `{SEED}`"))?, + None, + ) + .with_context(|| format!("failed to generate a key pair from `{SEED}`"))?; + + let mut old_pairs = HashMap::new(); + + for (raw_key, raw_value) in env::vars_os() { + let raw_key_bytes = raw_key.as_encoded_bytes(); + + if let Some(stripped_raw_key) = raw_key_bytes.strip_prefix(OLD_SEED.as_bytes()) { + let key = str::from_utf8(stripped_raw_key) + .context("failed to read an old seed environment variable name")?; + let value = raw_value + .to_str() + .with_context(|| format!("failed to read a seed phrase from `{OLD_SEED}{key}`"))?; + let old_pair = Pair::from_string(value, None) + .with_context(|| format!("failed to generate a key pair from `{OLD_SEED}{key}`"))?; + + old_pairs.insert(key.to_owned(), old_pair); + } + } + + Ok((pair, old_pairs)) +} + #[derive(Clone)] struct TaskTracker { inner: task::TaskTracker, @@ -345,7 +353,7 @@ impl TaskTracker { drop(self.error_tx); while let Some((from, error)) = error_rx.recv().await { - tracing::error!("Received a fatal error from {from}!\n{error:?}"); + tracing::error!("Received a fatal error from {from}:\n{error:?}"); if !shutdown_notification.is_cancelled() { tracing::info!("Initialising the shutdown..."); @@ -395,17 +403,37 @@ async fn shutdown_listener(shutdown_notification: CancellationToken) -> Result, host: Option, database: Option, - remark: Option, debug: Option, in_memory_db: Option, chain: Vec, } +impl Config { + fn parse() -> Result { + let config_path = env::var(CONFIG).or_else(|error| match error { + VarError::NotUnicode(_) => { + Err(error).with_context(|| format!("failed to read `{CONFIG}`")) + } + VarError::NotPresent => { + tracing::debug!( + "`{CONFIG}` isn't present, using the default value instead: {DEFAULT_CONFIG:?}." + ); + + Ok(DEFAULT_CONFIG.into()) + } + })?; + let unparsed_config = fs::read_to_string(&config_path) + .with_context(|| format!("failed to read a config file at {config_path:?}"))?; + + de::from_str(&unparsed_config) + .with_context(|| format!("failed to parse the config at {config_path:?}")) + } +} + #[derive(Deserialize)] #[serde(rename_all = "kebab-case")] struct Chain { @@ -429,7 +457,7 @@ struct AssetInfo { id: AssetId, } -#[derive(Deserialize, Debug)] +#[derive(Deserialize, Debug, Clone, Copy)] struct Balance(u128); impl Deref for Balance { diff --git a/src/rpc.rs b/src/rpc.rs index 91df300..7e150c2 100644 --- a/src/rpc.rs +++ b/src/rpc.rs @@ -943,37 +943,18 @@ impl Processor { if !invoic.1.callback.is_empty() { tracing::info!("{:?}", invoic.1.callback); - let req = ureq::post(&invoic.1.callback); - - let d = req - .send_json(OrderStatus { - order: invoic.0.clone(), - payment_status: PaymentStatus::Paid, - message: "".into(), - recipient: self.state.recipient.to_ss58check(), - server_info: ServerInfo { - version: env!("CARGO_PKG_VERSION"), - instance_id: String::new(), - debug: self.state.debug, - kalatori_remark: self.state.remark.clone(), - }, - order_info: Some(OrderInfo { - withdrawal_status: WithdrawalStatus::Waiting, - amount: invoic.1.amount.format(6), - currency: CurrencyInfo { - currency: "USDC".into(), - chain_name: "assethub-polkadot".into(), - kind: TokenKind::Assets, - decimals: 6, - rpc_url: self.state.rpc.clone(), - asset_id: Some(1337), - }, - callback: invoic.1.callback.clone(), - transactions: vec![], - payment_account: invoic.1.paym_acc.to_ss58check(), - }), - }) - .unwrap(); + + crate::callback::callback( + invoic.1.callback.clone(), + invoic.0.to_string(), + self.state.recipient.clone(), + self.state.debug, + self.state.remark.clone(), + invoic.1.amount, + self.state.rpc.clone(), + invoic.1.paym_acc.clone(), + ) + .await; } invoices.insert( diff --git a/src/server.rs b/src/server.rs index c9ef898..4dae447 100644 --- a/src/server.rs +++ b/src/server.rs @@ -99,7 +99,7 @@ pub struct CurrencyInfo { #[derive(Serialize)] #[serde(rename_all = "lowercase")] pub enum TokenKind { - Assets, + Asset, Balances, } @@ -241,7 +241,7 @@ async fn process_order( currency: CurrencyInfo { currency: "USDC".into(), chain_name: "assethub-polkadot".into(), - kind: TokenKind::Assets, + kind: TokenKind::Asset, decimals: 6, rpc_url: state.rpc.clone(), asset_id: Some(1337), @@ -332,7 +332,7 @@ async fn process_order( currency: CurrencyInfo { currency: "USDC".into(), chain_name: "assethub-polkadot".into(), - kind: TokenKind::Assets, + kind: TokenKind::Asset, decimals: 6, rpc_url: state.rpc.clone(), asset_id: Some(1337), @@ -415,7 +415,7 @@ async fn status( supported_currencies: vec![CurrencyInfo { currency: "USDC".into(), chain_name: "assethub-polkadot".into(), - kind: TokenKind::Assets, + kind: TokenKind::Asset, decimals: 6, rpc_url: state.rpc.clone(), asset_id: Some(1337), 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 From d5e15313fb582083809be91f0b76b7c82f219e14 Mon Sep 17 00:00:00 2001 From: Slesarev Date: Tue, 16 Apr 2024 11:59:41 +0300 Subject: [PATCH 05/76] fix: make order info mandatory --- src/callback.rs | 4 ++-- src/server.rs | 28 +++++++++++++++++++++------- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/src/callback.rs b/src/callback.rs index d2c4a73..41a5d68 100644 --- a/src/callback.rs +++ b/src/callback.rs @@ -35,7 +35,7 @@ pub async fn callback( debug, kalatori_remark: remark, }, - order_info: Some(OrderInfo { + order_info: OrderInfo { withdrawal_status: WithdrawalStatus::Waiting, amount: amount.format(6), currency: CurrencyInfo { @@ -49,7 +49,7 @@ pub async fn callback( callback: path, transactions: vec![], payment_account: paym_acc.to_ss58check(), - }), + }, }) .unwrap(); }) diff --git a/src/server.rs b/src/server.rs index 4dae447..bf3f36f 100644 --- a/src/server.rs +++ b/src/server.rs @@ -27,8 +27,8 @@ pub struct OrderStatus { pub message: String, pub recipient: String, pub server_info: ServerInfo, - #[serde(skip_serializing_if = "Option::is_none", flatten)] - pub order_info: Option, + #[serde(flatten)] + pub order_info: OrderInfo, } #[derive(Serialize)] @@ -235,7 +235,7 @@ async fn process_order( debug: state.0.debug, kalatori_remark: state.remark.clone(), }, - order_info: Some(OrderInfo { + order_info: OrderInfo { withdrawal_status: WithdrawalStatus::Waiting, amount: invoice.amount.format(6), currency: CurrencyInfo { @@ -249,7 +249,7 @@ async fn process_order( callback: invoice.callback.clone(), transactions: vec![], payment_account: invoice.paym_acc.to_ss58check(), - }), + }, }, OrderSuccess::Found, )) @@ -266,7 +266,21 @@ async fn process_order( debug: state.0.debug, kalatori_remark: state.remark.clone(), }, - order_info: None, + 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, )) @@ -326,7 +340,7 @@ async fn process_order( debug: state.0.debug, kalatori_remark: state.0.remark.clone(), }, - order_info: Some(OrderInfo { + order_info: OrderInfo { withdrawal_status: WithdrawalStatus::Waiting, amount, currency: CurrencyInfo { @@ -340,7 +354,7 @@ async fn process_order( callback, transactions: vec![], payment_account: pay_acc.to_ss58check(), - }), + }, }, OrderSuccess::Created, )) From cf68d7ecd51cfb3461e8d1ec882608a3f75af222 Mon Sep 17 00:00:00 2001 From: Slesarev Date: Tue, 16 Apr 2024 15:17:29 +0300 Subject: [PATCH 06/76] chore: bump version --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index c7db69f..442ddaa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "kalatori" authors = ["Alzymologist Oy "] -version = "0.2.0-rc2" +version = "0.2.0-rc3" edition = "2021" description = "A gateway daemon for Kalatori." license = "GPL-3.0-or-later" From c4e3a99abfad3421235047c9f659d05a286807b9 Mon Sep 17 00:00:00 2001 From: Slesarev Date: Tue, 16 Apr 2024 15:18:25 +0300 Subject: [PATCH 07/76] fix: existing order status is not unknown --- src/server.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server.rs b/src/server.rs index bf3f36f..9581e70 100644 --- a/src/server.rs +++ b/src/server.rs @@ -225,7 +225,7 @@ async fn process_order( payment_status: if invoice.paid { PaymentStatus::Paid } else { - PaymentStatus::Unknown + PaymentStatus::Pending }, message: String::new(), recipient: state.0.recipient.to_ss58check(), From 3cd793d5caf49764010dfda4dea473a61cb6fb0d Mon Sep 17 00:00:00 2001 From: Slesarev Date: Tue, 16 Apr 2024 16:42:39 +0300 Subject: [PATCH 08/76] refactor: reuse server info --- Cargo.lock | 2 +- src/database.rs | 9 +++++++++ src/server.rs | 28 ++++------------------------ 3 files changed, 14 insertions(+), 25 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 93be423..420bd96 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2155,7 +2155,7 @@ dependencies = [ [[package]] name = "kalatori" -version = "0.2.0-rc2" +version = "0.2.0-rc3" dependencies = [ "anyhow", "axum", diff --git a/src/database.rs b/src/database.rs index 8dceb48..6da3f60 100644 --- a/src/database.rs +++ b/src/database.rs @@ -1,5 +1,6 @@ use crate::{ rpc::{ConnectedChain, Currency}, + server::ServerInfo, AccountId, AssetId, Balance, BlockNumber, Config, Nonce, Timestamp, Version, }; use anyhow::{Context, Result}; @@ -248,6 +249,14 @@ impl State { })) } + 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 rpc(&self) -> &str { // &self.rpc // } diff --git a/src/server.rs b/src/server.rs index 9581e70..1cf5554 100644 --- a/src/server.rs +++ b/src/server.rs @@ -229,12 +229,7 @@ async fn process_order( }, message: String::new(), recipient: state.0.recipient.to_ss58check(), - server_info: ServerInfo { - version: env!("CARGO_PKG_VERSION"), - instance_id: String::new(), - debug: state.0.debug, - kalatori_remark: state.remark.clone(), - }, + server_info: state.server_info(), order_info: OrderInfo { withdrawal_status: WithdrawalStatus::Waiting, amount: invoice.amount.format(6), @@ -260,12 +255,7 @@ async fn process_order( payment_status: PaymentStatus::Unknown, message: String::new(), recipient: state.0.recipient.to_ss58check(), - server_info: ServerInfo { - version: env!("CARGO_PKG_VERSION"), - instance_id: String::new(), - debug: state.0.debug, - kalatori_remark: state.remark.clone(), - }, + server_info: state.server_info(), order_info: OrderInfo { withdrawal_status: WithdrawalStatus::Waiting, amount: 0f64, @@ -334,12 +324,7 @@ async fn process_order( payment_status: PaymentStatus::Pending, message: String::new(), recipient: state.0.recipient.to_ss58check(), - server_info: ServerInfo { - version: env!("CARGO_PKG_VERSION"), - instance_id: String::new(), - debug: state.0.debug, - kalatori_remark: state.0.remark.clone(), - }, + server_info: state.server_info(), order_info: OrderInfo { withdrawal_status: WithdrawalStatus::Waiting, amount, @@ -420,12 +405,7 @@ async fn status( ( [(header::CACHE_CONTROL, "no-store")], ServerStatus { - description: ServerInfo { - version: env!("CARGO_PKG_VERSION"), - instance_id: String::new(), - debug: state.0.debug, - kalatori_remark: state.0.remark.clone(), - }, + description: state.server_info(), supported_currencies: vec![CurrencyInfo { currency: "USDC".into(), chain_name: "assethub-polkadot".into(), From 9df3ae60d7c7862045418c7387cfffa254aa8bb8 Mon Sep 17 00:00:00 2001 From: Slesarev Date: Tue, 16 Apr 2024 18:06:22 +0300 Subject: [PATCH 09/76] fix: update starter script --- start.sh | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/start.sh b/start.sh index 2722f51..fa626b4 100755 --- a/start.sh +++ b/start.sh @@ -1,6 +1,8 @@ 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_DATABASE="database-ah-usdc.redb" \ +KALATORI_RPC="wss://polkadot-asset-hub-rpc.polkadot.io" \ +KALATORI_RECIPIENT="5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY" \ +KALATORI_USD_ASSET="USDC" \ cargo r + From 34b3b39041f148029f718b5bb9f086d4d81a9829 Mon Sep 17 00:00:00 2001 From: Slesarev Date: Tue, 16 Apr 2024 18:06:51 +0300 Subject: [PATCH 10/76] feat: report host on boot --- src/main.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index d989b60..c005593 100644 --- a/src/main.rs +++ b/src/main.rs @@ -133,9 +133,10 @@ async fn main() -> Result<()> { }; tracing::info!( - "Kalatori {} by {} is starting...", + "Kalatori {} by {} is starting on {}...", env!("CARGO_PKG_VERSION"), - env!("CARGO_PKG_AUTHORS") + env!("CARGO_PKG_AUTHORS"), + host, ); let (task_tracker, error_rx) = TaskTracker::new(); @@ -495,6 +496,7 @@ fn decimal_exponent_product(decimals: Decimals) -> f64 { clippy::unreadable_literal, clippy::float_cmp )] + fn balance_insufficient_precision() { const DECIMALS: Decimals = 10; From dd871fded4ee433f0ac25e8eb1f75a22899137dc Mon Sep 17 00:00:00 2001 From: Slesarev Date: Wed, 17 Apr 2024 11:23:36 +0300 Subject: [PATCH 11/76] fix: proper currency api representation --- src/callback.rs | 4 +- src/database.rs | 25 ++++++--- src/main.rs | 7 ++- src/rpc.rs | 136 +++++++++++++++++++----------------------------- src/server.rs | 35 +++++++------ 5 files changed, 96 insertions(+), 111 deletions(-) diff --git a/src/callback.rs b/src/callback.rs index 41a5d68..8afa627 100644 --- a/src/callback.rs +++ b/src/callback.rs @@ -14,8 +14,8 @@ pub async fn callback( path: String, order: String, recipient: AccountId, - debug: Option, - remark: Option, + debug: bool, + remark: String, amount: Balance, rpc_url: String, paym_acc: AccountId, diff --git a/src/database.rs b/src/database.rs index 6da3f60..04979a3 100644 --- a/src/database.rs +++ b/src/database.rs @@ -1,6 +1,6 @@ use crate::{ rpc::{ConnectedChain, Currency}, - server::ServerInfo, + server::{CurrencyProperties, ServerInfo}, AccountId, AssetId, Balance, BlockNumber, Config, Nonce, Timestamp, Version, }; use anyhow::{Context, Result}; @@ -160,22 +160,21 @@ impl Value for Invoice { pub struct ConfigWoChains { pub recipient: String, - pub debug: Option, - pub remark: Option, + pub debug: bool, + pub remark: String, pub depth: Option, pub account_lifetime: BlockNumber, pub rpc: String, } pub struct State { - pub currencies: HashMap, + pub currencies: HashMap, pub recipient: AccountId, pub pair: Pair, pub depth: Option, pub account_lifetime: Timestamp, - pub debug: Option, - pub remark: Option, - + pub debug: bool, + pub remark: String, pub invoices: RwLock>, pub rpc: String, } @@ -191,7 +190,7 @@ pub struct Invoicee { impl State { pub fn initialise( path_option: Option, - currencies: HashMap, + currencies: HashMap, current_pair: Pair, old_pairs: HashMap, ConfigWoChains { @@ -257,6 +256,16 @@ impl State { kalatori_remark: self.remark.clone(), } } +/* + pub fn currency(&self, currency_name: &str) -> Option { + if let Some(currency) = self.currencies.get(currency_name) { + Some(Currency { + chain: currency.chain_name.clone(), + asset: currency.asset_id, + }) + } else { None } + } +*/ // pub fn rpc(&self) -> &str { // &self.rpc // } diff --git a/src/main.rs b/src/main.rs index c005593..d1dc318 100644 --- a/src/main.rs +++ b/src/main.rs @@ -89,8 +89,7 @@ async fn main() -> Result<()> { let recipient = env::var(RECIPIENT).with_context(|| format!("failed to read {RECIPIENT}"))?; let remark = match env::var(REMARK) { - Ok(remark) => Some(remark), - Err(VarError::NotPresent) => None, + Ok(remark) => remark, Err(error) => Err(error).with_context(|| format!("failed to read {REMARK}"))?, }; @@ -104,7 +103,7 @@ async fn main() -> Result<()> { DEFAULT_SOCKET }; - let debug = config.debug.unwrap_or_default(); + let debug = config.debug; let database_path = 'database: { if debug { @@ -408,7 +407,7 @@ struct Config { depth: Option, host: Option, database: Option, - debug: Option, + debug: bool, in_memory_db: Option, chain: Vec, } diff --git a/src/rpc.rs b/src/rpc.rs index 7e150c2..8ab08c8 100644 --- a/src/rpc.rs +++ b/src/rpc.rs @@ -2,7 +2,7 @@ use crate::{ asset::Asset, database::{Invoicee, State}, server::{ - CurrencyInfo, OrderInfo, OrderStatus, PaymentStatus, ServerInfo, TokenKind, + CurrencyInfo, CurrencyProperties, OrderInfo, OrderStatus, PaymentStatus, ServerInfo, TokenKind, WithdrawalStatus, }, AccountId, AssetId, AssetInfo, Balance, BlockHash, BlockNumber, Chain, Decimals, NativeToken, @@ -70,10 +70,11 @@ const BABE: &str = "Babe"; const AURA: &str = "AuraApi"; type ConnectedChainsChannel = ( - Sender, ConnectedChain)>>, - (Arc, ConnectedChain), + Sender>, + (String, ConnectedChain), ); -type CurrenciesChannel = (Sender>, (String, Currency)); + +type CurrenciesChannel = (Sender>, (String, CurrencyProperties)); struct AssetsInfoFetcher<'a> { assets: (AssetInfo, Vec), @@ -177,25 +178,30 @@ impl AssetProperties { impl ChainProperties { async fn fetch( - chain: Arc, + chain: &str, currencies: UnboundedSender, constants: &ConstantsClient, native_token_option: Option, assets_fetcher: Option>, account_lifetime: BlockNumber, depth: Option, - ) -> Result<(Self, Option)> { + ) -> Result { const ADDRESS_PREFIX: (&str, &str) = (SYSTEM, "SS58Prefix"); const EXISTENTIAL_DEPOSIT: (&str, &str) = (BALANCES, "ExistentialDeposit"); const BLOCK_HASH_COUNT: (&str, &str) = (SYSTEM, "BlockHashCount"); - let chain_clone = chain.clone(); - + /* wtf is this? let try_add_currency = |name, asset| async move { let (tx, rx) = oneshot::channel(); currencies - .send((tx, (name, Currency { chain, asset }))) + .send((tx, (name, CurrencyProperties { + chain_name: chain.to_owned(), + kind: , + decimals: , + rpc_url: , + asset_if: , + }))) .unwrap(); if let Some(( @@ -211,7 +217,7 @@ impl ChainProperties { } else { Ok(()) } - }; + };*/ let assets_pallet = if let Some(AssetsInfoFetcher { assets: (last_asset_info, assets_info), @@ -222,7 +228,7 @@ impl ChainProperties { async fn try_add_asset( assets: &mut HashMap, id: AssetId, - chain: Arc, + chain: &str, storage: &Storage, ) -> Result<()> { match assets.entry(id) { @@ -240,15 +246,15 @@ impl ChainProperties { let mut assets = HashMap::with_capacity(assets_info.len().saturating_add(1)); for asset_info in assets_info { - try_add_currency.clone()(asset_info.name, Some(asset_info.id)).await?; - try_add_asset(&mut assets, asset_info.id, chain_clone.clone(), storage).await?; + //try_add_currency.clone()(asset_info.name, Some(asset_info.id)).await?; + try_add_asset(&mut assets, asset_info.id, chain, storage).await?; } - try_add_currency.clone()(last_asset_info.name, Some(last_asset_info.id)).await?; + //try_add_currency.clone()(last_asset_info.name, Some(last_asset_info.id)).await?; try_add_asset( &mut assets, last_asset_info.id, - chain_clone.clone(), + chain, storage, ) .await?; @@ -264,35 +270,22 @@ impl ChainProperties { let address_format = Ss58AddressFormat::custom(fetch_constant(constants, ADDRESS_PREFIX)?); let block_hash_count = fetch_constant(constants, BLOCK_HASH_COUNT)?; - Ok(if let Some(native_token) = native_token_option { - try_add_currency(native_token.native_token, None).await?; + //Some(native_token.decimals), - ( - Self { - address_format, - existential_deposit: Some( - fetch_constant(constants, EXISTENTIAL_DEPOSIT).map(Balance)?, - ), - assets_pallet, - block_hash_count, - account_lifetime, - depth, - }, - Some(native_token.decimals), - ) - } else { - ( - Self { + let existential_deposit = if let Some(native_token) = native_token_option { + Some(fetch_constant(constants, EXISTENTIAL_DEPOSIT).map(Balance)?) + } else { None }; + + let chain = Self { address_format, - existential_deposit: None, + existential_deposit: existential_deposit, assets_pallet, block_hash_count, account_lifetime, depth, - }, - None, - ) - }) + }; + + Ok(chain) } } @@ -375,7 +368,7 @@ pub async fn prepare( chains: Vec, account_lifetime: Timestamp, depth: Option, -) -> Result<(HashMap, ConnectedChain>, HashMap)> { +) -> Result<(HashMap, HashMap)> { let mut connected_chains = HashMap::with_capacity(chains.len()); let mut currencies = HashMap::with_capacity( chains @@ -419,7 +412,7 @@ pub async fn prepare( Entry::Occupied(entry) => Some(entry.remove_entry()), Entry::Vacant(entry) => { tracing::info!( - %currency.chain, ?currency.asset, + %currency.chain_name, ?currency.asset_id, "Registered the currency {:?}.", entry.key(), ); @@ -465,8 +458,7 @@ async fn prepare_chain( account_lifetime: Timestamp, depth_option: Option, ) -> Result> { - let chain_name: Arc = chain.name.into(); - let chain_name_clone = chain_name.clone(); + let chain_name = chain.name; let endpoint = chain .endpoints .first() @@ -548,16 +540,16 @@ async fn prepare_chain( let rpc = endpoint.into(); - let connected_chain = if let Some(assets) = chain + let storage_client = client.storage(); + let storage = storage_client.at(finalized_hash); + + let assets_info_fetcher = if let Some(assets) = chain .asset .and_then(|mut assets| assets.pop().map(|latest| (latest, assets))) { const ASSET_ID: &str = "asset_id"; const SOME: &str = "Some"; - let storage_client = client.storage(); - let storage = storage_client.at(finalized_hash); - let extension = metadata .extrinsic() .signed_extensions() @@ -634,64 +626,46 @@ async fn prepare_chain( } else { Some(metadata.pallet_by_name_err(ASSETS)?.index()) }; - - let (properties, decimals) = ChainProperties::fetch( - chain_name, - currencies, - &constants, - chain.native_token, - Some(AssetsInfoFetcher { + Some(AssetsInfoFetcher { assets, storage: &storage, pallet_index, - }), - account_lifetime_in_blocks, - depth_in_blocks, - ) - .await?; + }) - ConnectedChain { - methods, - genesis, - rpc, - client, - storage: Some(storage_client), - properties, - constants, - decimals, - runtime_api, - backend, - } } else { - let (properties, decimals) = ChainProperties::fetch( - chain_name, + None + }; + + let storage = if assets_info_fetcher.is_some() { Some(storage_client) } else { None }; + + let properties = ChainProperties::fetch( + &chain_name, currencies, &constants, chain.native_token, - None, + assets_info_fetcher, account_lifetime_in_blocks, depth_in_blocks, ) .await?; - ConnectedChain { + let connected_chain = ConnectedChain { methods, genesis, rpc, client, - storage: None, + storage, properties, constants, - decimals, runtime_api, backend, - } - }; + }; + let (tx, rx) = oneshot::channel(); connected_chains - .send((tx, (chain_name_clone, connected_chain))) + .send((tx, (chain_name, connected_chain))) .unwrap(); if let Some((name, _)) = rx.await.unwrap() { @@ -705,7 +679,7 @@ async fn prepare_chain( #[derive(Debug)] pub struct Currency { - chain: Arc, + chain: String, asset: Option, } @@ -719,7 +693,6 @@ pub struct ConnectedChain { constants: ConstantsClient, storage: Option>, runtime_api: Option>, - decimals: Option, } impl Debug for ConnectedChain { @@ -728,7 +701,6 @@ impl Debug for ConnectedChain { .field("rpc", &self.rpc) .field("genesis", &self.genesis) .field("properties", &self.properties) - .field("decimals", &self.decimals) .finish_non_exhaustive() } } diff --git a/src/server.rs b/src/server.rs index 1cf5554..9338c08 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,5 +1,7 @@ use crate::{ - database::{Invoicee, State}, AccountId, AssetId, Balance, BlockNumber, Decimals, ExtrinsicIndex + database::{Invoicee, State}, + rpc::Currency, + AccountId, AssetId, Balance, BlockNumber, Decimals, ExtrinsicIndex }; use anyhow::{Context, Result}; use axum::{ @@ -60,7 +62,7 @@ pub enum WithdrawalStatus { #[derive(Serialize)] struct ServerStatus { description: ServerInfo, - supported_currencies: Vec, + supported_currencies: HashMap, } #[derive(Serialize)] @@ -96,7 +98,17 @@ pub struct CurrencyInfo { pub asset_id: Option, } -#[derive(Serialize)] +#[derive(Clone, Debug, Serialize)] +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, +} + +#[derive(Clone, Debug, Serialize)] #[serde(rename_all = "lowercase")] pub enum TokenKind { Asset, @@ -107,10 +119,8 @@ pub enum TokenKind { pub struct ServerInfo { pub version: &'static str, pub instance_id: String, - #[serde(skip_serializing_if = "Option::is_none")] - pub debug: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub kalatori_remark: Option, + pub debug: bool, + pub kalatori_remark: String, } #[derive(Serialize)] @@ -213,6 +223,8 @@ async fn process_order( .ok_or_else(|| OrderError::MissingParameter(ORDER_ID.into()))? .to_owned(); + + if query.is_empty() { // TODO: try to query an order from the database. @@ -406,14 +418,7 @@ async fn status( [(header::CACHE_CONTROL, "no-store")], ServerStatus { description: state.server_info(), - supported_currencies: vec![CurrencyInfo { - currency: "USDC".into(), - chain_name: "assethub-polkadot".into(), - kind: TokenKind::Asset, - decimals: 6, - rpc_url: state.rpc.clone(), - asset_id: Some(1337), - }], + supported_currencies: state.currencies.clone(), } .into(), ) From 6349b86cb2e49028004682d7b6925c8e14c5d0f6 Mon Sep 17 00:00:00 2001 From: Slesarev Date: Wed, 17 Apr 2024 11:27:40 +0300 Subject: [PATCH 12/76] chore: fmt --- src/database.rs | 20 +++++----- src/rpc.rs | 98 +++++++++++++++++++++++++------------------------ src/server.rs | 4 +- 3 files changed, 62 insertions(+), 60 deletions(-) diff --git a/src/database.rs b/src/database.rs index 04979a3..110b5b6 100644 --- a/src/database.rs +++ b/src/database.rs @@ -256,16 +256,16 @@ impl State { kalatori_remark: self.remark.clone(), } } -/* - pub fn currency(&self, currency_name: &str) -> Option { - if let Some(currency) = self.currencies.get(currency_name) { - Some(Currency { - chain: currency.chain_name.clone(), - asset: currency.asset_id, - }) - } else { None } - } -*/ + /* + pub fn currency(&self, currency_name: &str) -> Option { + if let Some(currency) = self.currencies.get(currency_name) { + Some(Currency { + chain: currency.chain_name.clone(), + asset: currency.asset_id, + }) + } else { None } + } + */ // pub fn rpc(&self) -> &str { // &self.rpc // } diff --git a/src/rpc.rs b/src/rpc.rs index 8ab08c8..9cb67f3 100644 --- a/src/rpc.rs +++ b/src/rpc.rs @@ -2,8 +2,8 @@ use crate::{ asset::Asset, database::{Invoicee, State}, server::{ - CurrencyInfo, CurrencyProperties, OrderInfo, OrderStatus, PaymentStatus, ServerInfo, TokenKind, - WithdrawalStatus, + CurrencyInfo, CurrencyProperties, OrderInfo, OrderStatus, PaymentStatus, ServerInfo, + TokenKind, WithdrawalStatus, }, AccountId, AssetId, AssetInfo, Balance, BlockHash, BlockNumber, Chain, Decimals, NativeToken, Nonce, OnlineClient, PalletIndex, RuntimeConfig, TaskTracker, Timestamp, @@ -74,7 +74,10 @@ type ConnectedChainsChannel = ( (String, ConnectedChain), ); -type CurrenciesChannel = (Sender>, (String, CurrencyProperties)); +type CurrenciesChannel = ( + Sender>, + (String, CurrencyProperties), +); struct AssetsInfoFetcher<'a> { assets: (AssetInfo, Vec), @@ -195,8 +198,8 @@ impl ChainProperties { let (tx, rx) = oneshot::channel(); currencies - .send((tx, (name, CurrencyProperties { - chain_name: chain.to_owned(), + .send((tx, (name, CurrencyProperties { + chain_name: chain.to_owned(), kind: , decimals: , rpc_url: , @@ -251,13 +254,7 @@ impl ChainProperties { } //try_add_currency.clone()(last_asset_info.name, Some(last_asset_info.id)).await?; - try_add_asset( - &mut assets, - last_asset_info.id, - chain, - storage, - ) - .await?; + try_add_asset(&mut assets, last_asset_info.id, chain, storage).await?; Some(AssetsPallet { assets, @@ -274,16 +271,18 @@ impl ChainProperties { let existential_deposit = if let Some(native_token) = native_token_option { Some(fetch_constant(constants, EXISTENTIAL_DEPOSIT).map(Balance)?) - } else { None }; + } else { + None + }; let chain = Self { - address_format, - existential_deposit: existential_deposit, - assets_pallet, - block_hash_count, - account_lifetime, - depth, - }; + address_format, + existential_deposit, + assets_pallet, + block_hash_count, + account_lifetime, + depth, + }; Ok(chain) } @@ -368,7 +367,10 @@ pub async fn prepare( chains: Vec, account_lifetime: Timestamp, depth: Option, -) -> Result<(HashMap, HashMap)> { +) -> Result<( + HashMap, + HashMap, +)> { let mut connected_chains = HashMap::with_capacity(chains.len()); let mut currencies = HashMap::with_capacity( chains @@ -627,40 +629,42 @@ async fn prepare_chain( Some(metadata.pallet_by_name_err(ASSETS)?.index()) }; Some(AssetsInfoFetcher { - assets, - storage: &storage, - pallet_index, - }) - + assets, + storage: &storage, + pallet_index, + }) } else { None }; - let storage = if assets_info_fetcher.is_some() { Some(storage_client) } else { None }; + let storage = if assets_info_fetcher.is_some() { + Some(storage_client) + } else { + None + }; let properties = ChainProperties::fetch( - &chain_name, - currencies, - &constants, - chain.native_token, - assets_info_fetcher, - account_lifetime_in_blocks, - depth_in_blocks, - ) - .await?; + &chain_name, + currencies, + &constants, + chain.native_token, + assets_info_fetcher, + account_lifetime_in_blocks, + depth_in_blocks, + ) + .await?; let connected_chain = ConnectedChain { - methods, - genesis, - rpc, - client, - storage, - properties, - constants, - runtime_api, - backend, - }; - + methods, + genesis, + rpc, + client, + storage, + properties, + constants, + runtime_api, + backend, + }; let (tx, rx) = oneshot::channel(); diff --git a/src/server.rs b/src/server.rs index 9338c08..433188c 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,7 +1,7 @@ use crate::{ database::{Invoicee, State}, rpc::Currency, - AccountId, AssetId, Balance, BlockNumber, Decimals, ExtrinsicIndex + AccountId, AssetId, Balance, BlockNumber, Decimals, ExtrinsicIndex, }; use anyhow::{Context, Result}; use axum::{ @@ -223,8 +223,6 @@ async fn process_order( .ok_or_else(|| OrderError::MissingParameter(ORDER_ID.into()))? .to_owned(); - - if query.is_empty() { // TODO: try to query an order from the database. From 1bcfd2af3e5ca4d17dcea4fc37c6c84591d16a5c Mon Sep 17 00:00:00 2001 From: Slesarev Date: Thu, 18 Apr 2024 11:54:21 +0300 Subject: [PATCH 13/76] fix: make remark mandatory --- configs/polkadot.toml | 1 + src/database.rs | 73 ++++++++++++++++++++++++++----------------- src/server.rs | 2 +- start.sh | 1 + 4 files changed, 47 insertions(+), 30 deletions(-) diff --git a/configs/polkadot.toml b/configs/polkadot.toml index 3a80a58..bab6cd4 100644 --- a/configs/polkadot.toml +++ b/configs/polkadot.toml @@ -1,4 +1,5 @@ account-lifetime = 604800000 # 1 week. +debug = true depth = 86400000 # 1 day. [[chain]] diff --git a/src/database.rs b/src/database.rs index 110b5b6..d4c89e2 100644 --- a/src/database.rs +++ b/src/database.rs @@ -1,12 +1,12 @@ use crate::{ rpc::{ConnectedChain, Currency}, - server::{CurrencyProperties, ServerInfo}, + server::{CurrencyInfo, CurrencyProperties, ServerInfo}, AccountId, AssetId, Balance, BlockNumber, Config, Nonce, Timestamp, Version, }; use anyhow::{Context, Result}; use redb::{ backends::{FileBackend, InMemoryBackend}, - Database, ReadableTable, Table, TableDefinition, TableHandle, TypeName, Value, + Database, ReadableTable, ReadOnlyTable, Table, TableDefinition, TableHandle, TypeName, Value, }; use serde::Deserialize; use std::{collections::HashMap, fs::File, io::ErrorKind, sync::Arc}; @@ -21,6 +21,11 @@ use tokio::sync::RwLock; pub const MODULE: &str = module_path!(); +#[derive(Debug)] +pub enum DbError { + CurrencyKeyNotFound, +} + // Tables const ROOT: TableDefinition<'_, &str, &[u8]> = TableDefinition::new("root"); @@ -256,16 +261,25 @@ impl State { kalatori_remark: self.remark.clone(), } } - /* - pub fn currency(&self, currency_name: &str) -> Option { - if let Some(currency) = self.currencies.get(currency_name) { - Some(Currency { - chain: currency.chain_name.clone(), - asset: currency.asset_id, - }) - } else { None } - } - */ + + pub fn currency_properties(&self, currency_name: &str) -> Result<&CurrencyProperties, DbError> { + self.currencies + .get(currency_name) + .ok_or(DbError::CurrencyKeyNotFound) + } + + 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 // } @@ -297,26 +311,27 @@ impl State { // } } -// pub struct ReadTransaction<'db>(redb::ReadTransaction<'db>); +/* +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<'tx>(ReadOnlyTable<'tx, &'static [u8; 32], Invoice>); +impl ReadTransaction { + pub fn invoices(&self) -> Result { + self.0 + .open_table(INVOICES) + .map(ReadInvoices) + .with_context(|| format!("failed to open the `{}` table", INVOICES.name())) + } +} -// 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 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>)>>> diff --git a/src/server.rs b/src/server.rs index 433188c..fc3038f 100644 --- a/src/server.rs +++ b/src/server.rs @@ -108,7 +108,7 @@ pub struct CurrencyProperties { pub asset_id: Option, } -#[derive(Clone, Debug, Serialize)] +#[derive(Clone, Copy, Debug, Serialize)] #[serde(rename_all = "lowercase")] pub enum TokenKind { Asset, diff --git a/start.sh b/start.sh index fa626b4..26ee7e9 100755 --- a/start.sh +++ b/start.sh @@ -4,5 +4,6 @@ KALATORI_DATABASE="database-ah-usdc.redb" \ KALATORI_RPC="wss://polkadot-asset-hub-rpc.polkadot.io" \ KALATORI_RECIPIENT="5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY" \ KALATORI_USD_ASSET="USDC" \ +KALATORI_REMARK="test" \ cargo r From 6601215f9f0271ee28b51e60f6d9ac725f8f3363 Mon Sep 17 00:00:00 2001 From: Slesarev Date: Fri, 19 Apr 2024 18:09:03 +0300 Subject: [PATCH 14/76] feat: db access thread mockup --- Cargo.lock | 13 +++ Cargo.toml | 1 + src/database.rs | 209 +++++++++++++++++++++++++++++++++++++++++------- src/main.rs | 8 +- src/rpc.rs | 15 ++-- src/server.rs | 153 ++++++++--------------------------- 6 files changed, 242 insertions(+), 157 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 420bd96..6dbdf27 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -485,6 +485,18 @@ dependencies = [ "tower-service", ] +[[package]] +name = "axum-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00c055ee2d014ae5981ce1016374e8213682aa14d9bf40e48ab48b5f3ef20eaa" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.58", +] + [[package]] name = "backtrace" version = "0.3.71" @@ -2159,6 +2171,7 @@ version = "0.2.0-rc3" dependencies = [ "anyhow", "axum", + "axum-macros", "hex-simd", "names", "redb", diff --git a/Cargo.toml b/Cargo.toml index 442ddaa..06bcfb7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,7 @@ anyhow = "1" redb = "2" tracing = "0.1" scale-info = "2" +axum-macros = "0.4.1" [profile.release] strip = true diff --git a/src/database.rs b/src/database.rs index d4c89e2..4b11c15 100644 --- a/src/database.rs +++ b/src/database.rs @@ -1,6 +1,6 @@ use crate::{ rpc::{ConnectedChain, Currency}, - server::{CurrencyInfo, CurrencyProperties, ServerInfo}, + server::{CurrencyInfo, CurrencyProperties, OrderQuery, OrderStatus, ServerInfo, ServerStatus}, AccountId, AssetId, Balance, BlockNumber, Config, Nonce, Timestamp, Version, }; use anyhow::{Context, Result}; @@ -17,13 +17,14 @@ use subxt::ext::{ sr25519::{Pair, Public}, }, }; -use tokio::sync::RwLock; +use tokio::sync::{mpsc, oneshot, RwLock}; pub const MODULE: &str = module_path!(); #[derive(Debug)] pub enum DbError { CurrencyKeyNotFound, + DbEngineDown, } // Tables @@ -164,7 +165,7 @@ impl Value for Invoice { } pub struct ConfigWoChains { - pub recipient: String, + pub recipient: AccountId, pub debug: bool, pub remark: String, pub depth: Option, @@ -172,16 +173,131 @@ pub struct ConfigWoChains { pub rpc: String, } +enum StateAccessRequest { + GetInvoiceStatus(GetInvoiceStatus), + CreateInvoice(CreateInvoice), + ServerStatus(oneshot::Sender), +} + +struct GetInvoiceStatus { + pub order: String, + pub res: oneshot::Sender, +} + +struct CreateInvoice { + pub order_query: OrderQuery, + pub res: oneshot::Sender, +} + +//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, + ))*/ + +/* + ServerStatus { + description: state.server_info(), + supported_currencies: state.currencies.clone(), + } +*/ + +#[derive(Clone, Debug)] pub struct State { - pub currencies: HashMap, - pub recipient: AccountId, - pub pair: Pair, - pub depth: Option, - pub account_lifetime: Timestamp, - pub debug: bool, - pub remark: String, - pub invoices: RwLock>, - pub rpc: String, + pub tx: tokio::sync::mpsc::Sender, } #[derive(Deserialize, Debug)] @@ -206,7 +322,7 @@ impl State { account_lifetime, rpc, }: ConfigWoChains, - ) -> Result> { + ) -> Result { let builder = Database::builder(); let is_new; @@ -236,23 +352,62 @@ impl State { builder.create_with_backend(InMemoryBackend::new()) }.context("failed to create/open the database")?; - // +/* + 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); + tokio::spawn(async move { + while let Some(request) = rx.recv().await { + //database; + } + }); + + Ok(Self { + tx, + }) + } - Ok(Arc::new(Self { - currencies, - recipient: AccountId::from_string(&recipient) - .context("failed to convert \"recipient\" from the config to an account address")?, - pair: current_pair, - depth, - account_lifetime, - debug, - remark, + pub async fn order_status(&self, order: &str) -> Result { + let (res, mut rx) = oneshot::channel(); + self.tx.send(StateAccessRequest::GetInvoiceStatus(GetInvoiceStatus {order: order.to_string(), res})).await; + rx.await.map_err(|_| DbError::DbEngineDown) + } - invoices: RwLock::new(HashMap::new()), - rpc, - })) + pub async fn server_status(&self) -> Result { + let (res, mut rx) = oneshot::channel(); + self.tx.send(StateAccessRequest::ServerStatus(res)).await; + rx.await.map_err(|_| DbError::DbEngineDown) + } + + pub async fn create_order(&self, order_query: OrderQuery) -> Result { + let (res, mut 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; + rx.await.map_err(|_| DbError::DbEngineDown) } + pub fn interface(&self) -> Self { + State{tx: self.tx.clone()} + } +/* pub fn server_info(&self) -> ServerInfo { ServerInfo { version: env!("CARGO_PKG_VERSION"), @@ -279,7 +434,7 @@ impl State { asset_id: currency.asset_id, }) } - +*/ // pub fn rpc(&self) -> &str { // &self.rpc // } diff --git a/src/main.rs b/src/main.rs index d1dc318..cd22c3b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -151,25 +151,27 @@ async fn main() -> Result<()> { let rpc = env::var("KALATORI_RPC").unwrap(); + let recipient = AccountId::from_string(&recipient)?; + let state = State::initialise( database_path, currencies, pair, old_pairs, ConfigWoChains { - recipient, + recipient: recipient.clone(), debug: config.debug, remark, depth: config.depth, account_lifetime: config.account_lifetime, - rpc, + rpc: rpc.clone(), }, ) .context("failed to initialise the database module")?; task_tracker.spawn( "proc", - Processor::ignite(state.clone(), shutdown_notification.clone()), + Processor::ignite(rpc, recipient.into(), state.clone(), shutdown_notification.clone()), ); let server = server::new(shutdown_notification.clone(), host, state) diff --git a/src/rpc.rs b/src/rpc.rs index 9cb67f3..ed4c9b9 100644 --- a/src/rpc.rs +++ b/src/rpc.rs @@ -722,7 +722,8 @@ impl Display for Shutdown { } pub struct Processor { - state: Arc, + state: State, + recipient: AccountId, backend: Arc>, shutdown_notification: CancellationToken, methods: LegacyRpcMethods, @@ -731,16 +732,18 @@ pub struct Processor { } impl Processor { - pub async fn ignite(state: Arc, notif: CancellationToken) -> Result> { - let client = Client::builder().build(state.rpc.clone()).await.unwrap(); + pub async fn ignite(rpc: String, recipient: AccountId, state: State, notif: CancellationToken) -> Result> { + let client = Client::builder().build(rpc.clone()).await.unwrap(); let rpc_c = RpcClient::new(client); let methods = LegacyRpcMethods::new(rpc_c.clone()); let backend = Arc::new(LegacyBackend::builder().build(rpc_c)); let onl = OnlineClient::from_backend(backend.clone()).await.unwrap(); let st = onl.storage(); + Processor { state, + recipient, backend, shutdown_notification: notif, methods, @@ -879,7 +882,7 @@ impl Processor { .await .context("failed to obtain block events")?; - let invoices = &mut *self.state.invoices.write().await; + //let invoices = &mut *self.state.invoices.write().await; // let mut update = false; // let mut invoices_changes = HashMap::new(); @@ -902,7 +905,7 @@ impl Processor { .context("failed to deserialize a transfer event")?; tracing::info!("{tr:?}"); - +/* TODO process using cache and db access #[allow(clippy::unnecessary_find_map)] if let Some(invoic) = invoices.iter().find_map(|invoic| { tracing::info!("{tr:?} {invoic:?}"); @@ -942,7 +945,7 @@ impl Processor { paym_acc: invoic.1.paym_acc.clone(), }, ); - } + }*/ } _ => {} } diff --git a/src/server.rs b/src/server.rs index fc3038f..80b00b2 100644 --- a/src/server.rs +++ b/src/server.rs @@ -10,6 +10,7 @@ use axum::{ response::{IntoResponse, Response}, routing, Json, Router, }; +use axum_macros::debug_handler; use serde::{Serialize, Serializer}; use std::{borrow::Cow, collections::HashMap, future::Future, net::SocketAddr, sync::Arc}; use subxt::ext::sp_core::{crypto::Ss58Codec, DeriveJunction, Pair}; @@ -22,6 +23,13 @@ const AMOUNT: &str = "amount"; const CURRENCY: &str = "currency"; const CALLBACK: &str = "callback"; +pub struct OrderQuery { + pub order: String, + pub amount: f64, + pub callback: String, + pub currency: String, +} + #[derive(Serialize)] pub struct OrderStatus { pub order: String, @@ -60,7 +68,7 @@ pub enum WithdrawalStatus { } #[derive(Serialize)] -struct ServerStatus { +pub struct ServerStatus { description: ServerInfo, supported_currencies: HashMap, } @@ -166,9 +174,9 @@ enum TxStatus { pub async fn new( shutdown_notification: CancellationToken, host: SocketAddr, - state: Arc, + state: State, ) -> Result>>> { - let v2 = Router::new() + let v2: Router = Router::new() .route("/order/:order_id", routing::post(order)) .route("/status", routing::get(status)); let app = Router::new().nest("/v2", v2).with_state(state); @@ -206,9 +214,8 @@ struct InvalidParameter { message: String, } -#[allow(clippy::too_many_lines)] async fn process_order( - state: extract::State>, + state: State, matched_path: &MatchedPath, path_result: Result, query: &HashMap, @@ -224,67 +231,8 @@ async fn process_order( .to_owned(); if query.is_empty() { - // TODO: try to query an order from the database. - - let invoices = state.0.invoices.read().await; - - if let Some(invoice) = invoices.get(&order) { - 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 order_status = state.order_status(&order).await.map_err(|_|OrderError::InternalError)?; + Ok((order_status, OrderSuccess::Found)) } else { let get_parameter = |parameter: &str| { query @@ -298,8 +246,6 @@ async fn process_order( .parse() .map_err(|_| OrderError::InvalidParameter(AMOUNT.into()))?; - // TODO: try to query & update or create an order in the database. - if currency != "USDC" { return Err(OrderError::UnknownCurrency); } @@ -308,56 +254,22 @@ async fn process_order( return Err(OrderError::LessThanExistentialDeposit(0.07)); } - let mut invoices = state.0.invoices.write().await; - let pay_acc: AccountId = state - .0 - .pair - .derive(vec![DeriveJunction::hard(order.clone())].into_iter(), None) - .unwrap() - .0 - .public() - .into(); - - invoices.insert( - order.clone(), - Invoicee { - callback: callback.clone(), - amount: Balance::parse(amount, 6), - paid: false, - paym_acc: pay_acc.clone(), - }, - ); - - Ok(( - OrderStatus { + + let order_status = state.create_order( + OrderQuery{ 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, - )) + amount, + callback, + currency, + }).await.map_err(|_|OrderError::InternalError)?; + + Ok((order_status, OrderSuccess::Created)) } } +#[debug_handler] async fn order( - state: extract::State>, + extract::State(state): extract::State, matched_path: MatchedPath, path_result: Result, query: Query>, @@ -410,14 +322,13 @@ async fn order( } async fn status( - state: extract::State>, + extract::State(state): extract::State, ) -> ([(HeaderName, &'static str); 1], Json) { - ( + match state.server_status().await { + Ok(status) => ( [(header::CACHE_CONTROL, "no-store")], - ServerStatus { - description: state.server_info(), - supported_currencies: state.currencies.clone(), - } - .into(), - ) + status.into(), + ), + Err(e) => panic!("db connection is down, state is lost"), //TODO tell this to client + } } From 1da2326853a8fa668c8bad0b502d6f91000a6408 Mon Sep 17 00:00:00 2001 From: Slesarev Date: Fri, 19 Apr 2024 18:09:53 +0300 Subject: [PATCH 15/76] chore: fmt --- src/database.rs | 300 ++++++++++++++++++++++++------------------------ src/main.rs | 7 +- src/rpc.rs | 10 +- src/server.rs | 19 +-- 4 files changed, 176 insertions(+), 160 deletions(-) diff --git a/src/database.rs b/src/database.rs index 4b11c15..2fab9ee 100644 --- a/src/database.rs +++ b/src/database.rs @@ -6,7 +6,7 @@ use crate::{ use anyhow::{Context, Result}; use redb::{ backends::{FileBackend, InMemoryBackend}, - Database, ReadableTable, ReadOnlyTable, Table, TableDefinition, TableHandle, TypeName, Value, + Database, ReadOnlyTable, ReadableTable, Table, TableDefinition, TableHandle, TypeName, Value, }; use serde::Deserialize; use std::{collections::HashMap, fs::File, io::ErrorKind, sync::Arc}; @@ -190,103 +190,102 @@ struct CreateInvoice { } //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(), - }, +/* + 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), }, - 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(), - }, + 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), }, - OrderSuccess::Found, - )) - }*/ - + 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(), - }, +/* + * +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), }, - OrderSuccess::Created, - ))*/ + callback, + transactions: vec![], + payment_account: pay_acc.to_ss58check(), + }, + }, + OrderSuccess::Created, +))*/ /* ServerStatus { @@ -352,17 +351,17 @@ impl State { builder.create_with_backend(InMemoryBackend::new()) }.context("failed to create/open the database")?; -/* - currencies: HashMap, - recipient: AccountId, - pair: Pair, - depth: Option, - account_lifetime: Timestamp, - debug: bool, - remark: String, - invoices: RwLock>, - rpc: String, -*/ + /* + 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); tokio::spawn(async move { while let Some(request) = rx.recv().await { @@ -370,14 +369,17 @@ impl State { } }); - Ok(Self { - tx, - }) + Ok(Self { tx }) } pub async fn order_status(&self, order: &str) -> Result { let (res, mut rx) = oneshot::channel(); - self.tx.send(StateAccessRequest::GetInvoiceStatus(GetInvoiceStatus {order: order.to_string(), res})).await; + self.tx + .send(StateAccessRequest::GetInvoiceStatus(GetInvoiceStatus { + order: order.to_string(), + res, + })) + .await; rx.await.map_err(|_| DbError::DbEngineDown) } @@ -387,54 +389,58 @@ impl State { rx.await.map_err(|_| DbError::DbEngineDown) } - pub async fn create_order(&self, order_query: OrderQuery) -> Result { + pub async fn create_order(&self, order_query: OrderQuery) -> Result { let (res, mut 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; + 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; rx.await.map_err(|_| DbError::DbEngineDown) } pub fn interface(&self) -> Self { - State{tx: self.tx.clone()} - } -/* - pub fn server_info(&self) -> ServerInfo { - ServerInfo { - version: env!("CARGO_PKG_VERSION"), - instance_id: String::new(), - debug: self.debug, - kalatori_remark: self.remark.clone(), + State { + tx: self.tx.clone(), } } + /* + 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, DbError> { - self.currencies - .get(currency_name) - .ok_or(DbError::CurrencyKeyNotFound) - } + pub fn currency_properties(&self, currency_name: &str) -> Result<&CurrencyProperties, DbError> { + self.currencies + .get(currency_name) + .ok_or(DbError::CurrencyKeyNotFound) + } - 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 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 // } diff --git a/src/main.rs b/src/main.rs index cd22c3b..5b3c53e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -171,7 +171,12 @@ async fn main() -> Result<()> { task_tracker.spawn( "proc", - Processor::ignite(rpc, recipient.into(), state.clone(), shutdown_notification.clone()), + Processor::ignite( + rpc, + recipient.into(), + state.clone(), + shutdown_notification.clone(), + ), ); let server = server::new(shutdown_notification.clone(), host, state) diff --git a/src/rpc.rs b/src/rpc.rs index ed4c9b9..836c627 100644 --- a/src/rpc.rs +++ b/src/rpc.rs @@ -732,7 +732,12 @@ pub struct Processor { } impl Processor { - pub async fn ignite(rpc: String, recipient: AccountId, state: State, notif: CancellationToken) -> Result> { + pub async fn ignite( + rpc: String, + recipient: AccountId, + state: State, + notif: CancellationToken, + ) -> Result> { let client = Client::builder().build(rpc.clone()).await.unwrap(); let rpc_c = RpcClient::new(client); let methods = LegacyRpcMethods::new(rpc_c.clone()); @@ -740,7 +745,6 @@ impl Processor { let onl = OnlineClient::from_backend(backend.clone()).await.unwrap(); let st = onl.storage(); - Processor { state, recipient, @@ -905,7 +909,7 @@ impl Processor { .context("failed to deserialize a transfer event")?; tracing::info!("{tr:?}"); -/* TODO process using cache and db access + /* TODO process using cache and db access #[allow(clippy::unnecessary_find_map)] if let Some(invoic) = invoices.iter().find_map(|invoic| { tracing::info!("{tr:?} {invoic:?}"); diff --git a/src/server.rs b/src/server.rs index 80b00b2..4b26fe3 100644 --- a/src/server.rs +++ b/src/server.rs @@ -231,7 +231,10 @@ async fn process_order( .to_owned(); if query.is_empty() { - let order_status = state.order_status(&order).await.map_err(|_|OrderError::InternalError)?; + let order_status = state + .order_status(&order) + .await + .map_err(|_| OrderError::InternalError)?; Ok((order_status, OrderSuccess::Found)) } else { let get_parameter = |parameter: &str| { @@ -254,14 +257,15 @@ async fn process_order( return Err(OrderError::LessThanExistentialDeposit(0.07)); } - - let order_status = state.create_order( - OrderQuery{ + let order_status = state + .create_order(OrderQuery { order, amount, callback, currency, - }).await.map_err(|_|OrderError::InternalError)?; + }) + .await + .map_err(|_| OrderError::InternalError)?; Ok((order_status, OrderSuccess::Created)) } @@ -325,10 +329,7 @@ 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(), - ), + Ok(status) => ([(header::CACHE_CONTROL, "no-store")], status.into()), Err(e) => panic!("db connection is down, state is lost"), //TODO tell this to client } } From 9f070031d87093e127a11d3dcd3b35db32ce46d8 Mon Sep 17 00:00:00 2001 From: Slesarev Date: Fri, 19 Apr 2024 18:10:41 +0300 Subject: [PATCH 16/76] chore: cargo autofix --- src/callback.rs | 2 +- src/database.rs | 42 ++++++++++++++++++++---------------------- src/main.rs | 4 ++-- src/rpc.rs | 16 +++++++--------- src/server.rs | 10 ++++------ 5 files changed, 34 insertions(+), 40 deletions(-) diff --git a/src/callback.rs b/src/callback.rs index 8afa627..a2aff59 100644 --- a/src/callback.rs +++ b/src/callback.rs @@ -23,7 +23,7 @@ pub async fn callback( let req = ureq::post(&path); task::spawn_blocking(move || { - let d = req + let _d = req .send_json(OrderStatus { order, payment_status: PaymentStatus::Paid, diff --git a/src/database.rs b/src/database.rs index 2fab9ee..3bf8fb3 100644 --- a/src/database.rs +++ b/src/database.rs @@ -1,23 +1,21 @@ use crate::{ - rpc::{ConnectedChain, Currency}, - server::{CurrencyInfo, CurrencyProperties, OrderQuery, OrderStatus, ServerInfo, ServerStatus}, - AccountId, AssetId, Balance, BlockNumber, Config, Nonce, Timestamp, Version, + server::{CurrencyProperties, OrderQuery, OrderStatus, ServerStatus}, + AccountId, AssetId, Balance, BlockNumber, Nonce, Timestamp, }; use anyhow::{Context, Result}; use redb::{ backends::{FileBackend, InMemoryBackend}, - Database, ReadOnlyTable, ReadableTable, Table, TableDefinition, TableHandle, TypeName, Value, + Database, ReadableTable, TableDefinition, TypeName, Value, }; use serde::Deserialize; -use std::{collections::HashMap, fs::File, io::ErrorKind, sync::Arc}; +use std::{collections::HashMap, fs::File, io::ErrorKind}; use subxt::ext::{ codec::{Compact, Decode, Encode}, sp_core::{ - crypto::Ss58Codec, - sr25519::{Pair, Public}, + sr25519::{Pair}, }, }; -use tokio::sync::{mpsc, oneshot, RwLock}; +use tokio::sync::{oneshot}; pub const MODULE: &str = module_path!(); @@ -310,22 +308,22 @@ pub struct Invoicee { impl State { pub fn initialise( path_option: Option, - currencies: HashMap, - current_pair: Pair, - old_pairs: HashMap, + _currencies: HashMap, + _current_pair: Pair, + _old_pairs: HashMap, ConfigWoChains { - recipient, - debug, - remark, - depth, - account_lifetime, - rpc, + recipient: _, + debug: _, + remark: _, + depth: _, + account_lifetime: _, + rpc: _, }: ConfigWoChains, ) -> Result { let builder = Database::builder(); let is_new; - let database = if let Some(path) = path_option { + let _database = if let Some(path) = path_option { tracing::info!("Creating/Opening the database at {path:?}."); match File::create_new(&path) { @@ -364,7 +362,7 @@ impl State { */ let (tx, mut rx) = tokio::sync::mpsc::channel(1024); tokio::spawn(async move { - while let Some(request) = rx.recv().await { + while let Some(_request) = rx.recv().await { //database; } }); @@ -373,7 +371,7 @@ impl State { } pub async fn order_status(&self, order: &str) -> Result { - let (res, mut rx) = oneshot::channel(); + let (res, rx) = oneshot::channel(); self.tx .send(StateAccessRequest::GetInvoiceStatus(GetInvoiceStatus { order: order.to_string(), @@ -384,13 +382,13 @@ impl State { } pub async fn server_status(&self) -> Result { - let (res, mut rx) = oneshot::channel(); + let (res, rx) = oneshot::channel(); self.tx.send(StateAccessRequest::ServerStatus(res)).await; rx.await.map_err(|_| DbError::DbEngineDown) } pub async fn create_order(&self, order_query: OrderQuery) -> Result { - let (res, mut rx) = oneshot::channel(); + let (res, rx) = oneshot::channel(); /* Invoicee { callback: callback.clone(), diff --git a/src/main.rs b/src/main.rs index 5b3c53e..d09a229 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,7 +15,7 @@ use subxt::{ config::{substrate::SubstrateHeader, PolkadotExtrinsicParams}, ext::sp_core::{ crypto::{AccountId32, Ss58Codec}, - sr25519::{Pair, Public}, + sr25519::{Pair}, Pair as _, }, PolkadotConfig, @@ -145,7 +145,7 @@ async fn main() -> Result<()> { shutdown_listener(shutdown_notification.clone()), ); - let (chains, currencies) = rpc::prepare(config.chain, config.account_lifetime, config.depth) + let (_chains, currencies) = rpc::prepare(config.chain, config.account_lifetime, config.depth) .await .context("failed while preparing the RPC module")?; diff --git a/src/rpc.rs b/src/rpc.rs index 836c627..361a0c2 100644 --- a/src/rpc.rs +++ b/src/rpc.rs @@ -2,8 +2,7 @@ use crate::{ asset::Asset, database::{Invoicee, State}, server::{ - CurrencyInfo, CurrencyProperties, OrderInfo, OrderStatus, PaymentStatus, ServerInfo, - TokenKind, WithdrawalStatus, + CurrencyProperties, }, AccountId, AssetId, AssetInfo, Balance, BlockHash, BlockNumber, Chain, Decimals, NativeToken, Nonce, OnlineClient, PalletIndex, RuntimeConfig, TaskTracker, Timestamp, @@ -25,7 +24,6 @@ use subxt::{ rpc::{reconnecting_rpc_client::Client, RpcClient, RpcSubscription}, Backend, BackendExt, RuntimeVersion, }, - blocks::Block, config::{DefaultExtrinsicParamsBuilder, Header}, constants::ConstantsClient, dynamic::{self, At, Value}, @@ -35,7 +33,7 @@ use subxt::{ scale_decode::DecodeAsType, scale_value, sp_core::{ - crypto::{Ss58AddressFormat, Ss58Codec}, + crypto::{Ss58AddressFormat}, sr25519::Pair, }, }, @@ -182,7 +180,7 @@ impl AssetProperties { impl ChainProperties { async fn fetch( chain: &str, - currencies: UnboundedSender, + _currencies: UnboundedSender, constants: &ConstantsClient, native_token_option: Option, assets_fetcher: Option>, @@ -269,7 +267,7 @@ impl ChainProperties { //Some(native_token.decimals), - let existential_deposit = if let Some(native_token) = native_token_option { + let existential_deposit = if let Some(_native_token) = native_token_option { Some(fetch_constant(constants, EXISTENTIAL_DEPOSIT).map(Balance)?) } else { None @@ -479,7 +477,7 @@ async fn prepare_chain( .genesis_hash() .await .context("failed to fetch the genesis hash")?; - let (finalized_number, finalized_hash) = fetch_finalized_head_number_and_hash(&methods).await?; + let (_finalized_number, finalized_hash) = fetch_finalized_head_number_and_hash(&methods).await?; let (metadata, runtime_version) = fetch_runtime(&methods, &*backend, finalized_hash).await?; let client = OnlineClient::from_backend_with( @@ -764,7 +762,7 @@ impl Processor { } async fn execute(mut self) -> Result> { - let (head_number, head_hash) = self + let (head_number, _head_hash) = self .finalized_head_number_and_hash() .await .context("failed to get the chain head")?; @@ -1003,7 +1001,7 @@ impl Processor { async fn batch_transfer( &self, - nonce: Nonce, + _nonce: Nonce, block_hash_count: BlockNumber, signer: &PairSigner, transfers: Vec, diff --git a/src/server.rs b/src/server.rs index 4b26fe3..3e13866 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,7 +1,5 @@ use crate::{ - database::{Invoicee, State}, - rpc::Currency, - AccountId, AssetId, Balance, BlockNumber, Decimals, ExtrinsicIndex, + database::{State}, AssetId, BlockNumber, Decimals, ExtrinsicIndex, }; use anyhow::{Context, Result}; use axum::{ @@ -12,8 +10,8 @@ use axum::{ }; use axum_macros::debug_handler; use serde::{Serialize, Serializer}; -use std::{borrow::Cow, collections::HashMap, future::Future, net::SocketAddr, sync::Arc}; -use subxt::ext::sp_core::{crypto::Ss58Codec, DeriveJunction, Pair}; +use std::{borrow::Cow, collections::HashMap, future::Future, net::SocketAddr}; + use tokio::net::TcpListener; use tokio_util::sync::CancellationToken; @@ -330,6 +328,6 @@ async fn status( ) -> ([(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 + Err(_e) => panic!("db connection is down, state is lost"), //TODO tell this to client } } From 9ed3bde98df5e00b381df1cc4c617209cd355342 Mon Sep 17 00:00:00 2001 From: Slesarev Date: Fri, 19 Apr 2024 21:36:22 +0300 Subject: [PATCH 17/76] fix: restore server status message --- src/database.rs | 45 ++++++++++++++++++++++++++++++--------------- src/server.rs | 4 ++-- 2 files changed, 32 insertions(+), 17 deletions(-) diff --git a/src/database.rs b/src/database.rs index 3bf8fb3..fc10879 100644 --- a/src/database.rs +++ b/src/database.rs @@ -1,5 +1,5 @@ use crate::{ - server::{CurrencyProperties, OrderQuery, OrderStatus, ServerStatus}, + server::{CurrencyProperties, OrderQuery, OrderStatus, ServerInfo, ServerStatus}, AccountId, AssetId, Balance, BlockNumber, Nonce, Timestamp, }; use anyhow::{Context, Result}; @@ -308,22 +308,22 @@ pub struct Invoicee { impl State { pub fn initialise( path_option: Option, - _currencies: HashMap, - _current_pair: Pair, - _old_pairs: HashMap, + currencies: HashMap, + current_pair: Pair, + old_pairs: HashMap, ConfigWoChains { - recipient: _, - debug: _, - remark: _, - depth: _, - account_lifetime: _, - rpc: _, + recipient, + debug, + remark, + depth, + account_lifetime, + rpc, }: ConfigWoChains, ) -> Result { let builder = Database::builder(); let is_new; - let _database = if let Some(path) = path_option { + let database = if let Some(path) = path_option { tracing::info!("Creating/Opening the database at {path:?}."); match File::create_new(&path) { @@ -362,8 +362,22 @@ impl State { */ let (tx, mut rx) = tokio::sync::mpsc::channel(1024); tokio::spawn(async move { - while let Some(_request) = rx.recv().await { - //database; + while let Some(request) = rx.recv().await { + &database; + match request { + StateAccessRequest::GetInvoiceStatus(a) => {}, + StateAccessRequest::CreateInvoice(a) => {}, + StateAccessRequest::ServerStatus(res) => { + let description = ServerInfo { + version: env!("CARGO_PKG_VERSION"), + instance_id: String::new(), + debug, + kalatori_remark: remark.clone(), + }; + let server_status = ServerStatus {description, supported_currencies: currencies.clone()}; + res.send(server_status); + }, + }; } }); @@ -411,7 +425,7 @@ impl State { tx: self.tx.clone(), } } - /* + /* pub fn server_info(&self) -> ServerInfo { ServerInfo { version: env!("CARGO_PKG_VERSION"), @@ -420,7 +434,8 @@ impl State { kalatori_remark: self.remark.clone(), } } - +*/ + /* pub fn currency_properties(&self, currency_name: &str) -> Result<&CurrencyProperties, DbError> { self.currencies .get(currency_name) diff --git a/src/server.rs b/src/server.rs index 3e13866..fbe117e 100644 --- a/src/server.rs +++ b/src/server.rs @@ -67,8 +67,8 @@ pub enum WithdrawalStatus { #[derive(Serialize)] pub struct ServerStatus { - description: ServerInfo, - supported_currencies: HashMap, + pub description: ServerInfo, + pub supported_currencies: HashMap, } #[derive(Serialize)] From 1d99b0b4b3545974f9b8eebc97d561b5321947c8 Mon Sep 17 00:00:00 2001 From: Slesarev Date: Tue, 23 Apr 2024 17:31:13 +0300 Subject: [PATCH 18/76] major: remove subxt; all broken --- Cargo.lock | 3380 +++++------------------------------------------ Cargo.toml | 21 +- src/asset.rs | 24 +- src/callback.rs | 9 +- src/chain.rs | 357 +++++ src/database.rs | 74 +- src/error.rs | 307 +++++ src/main.rs | 117 +- src/rpc.rs | 524 ++++---- src/server.rs | 78 +- src/utils.rs | 10 + 11 files changed, 1418 insertions(+), 3483 deletions(-) create mode 100644 src/chain.rs create mode 100644 src/error.rs create mode 100644 src/utils.rs diff --git a/Cargo.lock b/Cargo.lock index 6dbdf27..f0ec8bc 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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" dependencies = [ - "gimli 0.28.1", + "gimli", ] [[package]] @@ -36,27 +17,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" -[[package]] -name = "aead" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" -dependencies = [ - "crypto-common", - "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" @@ -64,7 +24,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", - "getrandom", "once_cell", "version_check", "zerocopy", @@ -85,30 +44,6 @@ 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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" -dependencies = [ - "winapi", -] - [[package]] name = "anyhow" version = "1.0.82" @@ -116,140 +51,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519" [[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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae3281bc6d0fd7e549af32b52511e1302185bd688fd3359fa36423346ff682ea" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[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" @@ -271,138 +80,6 @@ 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.3.0", - "event-listener-strategy 0.5.1", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-executor" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f98c37cf288e302c16ef6c8472aad1e034c6c84ce5ea7b8101c98eb4a802fee" -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.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d999d925640d51b662b7b4e404224dd81de70f4aa4a199383c2c5e5b86885fa3" -dependencies = [ - "async-channel", - "async-io", - "async-lock 3.3.0", - "async-signal", - "async-task", - "blocking", - "cfg-if", - "event-listener 5.3.0", - "futures-lite", - "rustix 0.38.32", - "tracing", - "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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbb36e985947064623dbd357f727af08ffd077f93d696782f3c56365fa2e2799" - [[package]] name = "async-trait" version = "0.1.79" @@ -414,18 +91,6 @@ dependencies = [ "syn 2.0.58", ] -[[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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" - [[package]] name = "autocfg" version = "1.2.0" @@ -443,9 +108,9 @@ dependencies = [ "bytes", "futures-util", "http 1.1.0", - "http-body 1.0.0", + "http-body", "http-body-util", - "hyper 1.2.0", + "hyper", "hyper-util", "itoa", "matchit", @@ -475,7 +140,7 @@ dependencies = [ "bytes", "futures-util", "http 1.1.0", - "http-body 1.0.0", + "http-body", "http-body-util", "mime", "pin-project-lite", @@ -503,12 +168,12 @@ version = "0.3.71" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" dependencies = [ - "addr2line 0.21.0", + "addr2line", "cc", "cfg-if", "libc", "miniz_oxide", - "object 0.32.2", + "object", "rustc-demangle", ] @@ -557,58 +222,12 @@ 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 0.11.0", -] - -[[package]] -name = "bitcoin-internals" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9425c3bf7089c983facbae04de54513cce73b41c7f9ff8c845b54e7bc64ebbfb" - -[[package]] -name = "bitcoin_hashes" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90064b8dee6815a6470d60bad07bbbaee885c0e12d04177138fa3291a01b7bc4" - -[[package]] -name = "bitcoin_hashes" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1930a4dabfebb8d7d9992db18ebe3ae2876f0a305fab206fd168df931ede293b" -dependencies = [ - "bitcoin-internals", - "hex-conservative", -] - [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" -[[package]] -name = "bitflags" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" - [[package]] name = "bitvec" version = "1.0.1" @@ -670,69 +289,32 @@ dependencies = [ ] [[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", -] +checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" [[package]] -name = "bounded-collections" -version = "0.2.0" +name = "bytemuck" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d32385ecb91a31bddaf908e8dcf4a15aef1bcd3913cc03ebfad02ff6d568abc1" -dependencies = [ - "log", - "parity-scale-codec", - "scale-info", - "serde", -] +checksum = "5d6d68c57235a3a081186990eca2867354726650f42f7516ca50c28d6281fd15" [[package]] -name = "bs58" -version = "0.5.1" +name = "byteorder" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" -dependencies = [ - "tinyvec", -] +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] -name = "bumpalo" -version = "3.16.0" +name = "bytes" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" [[package]] -name = "byte-slice-cast" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" - -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - -[[package]] -name = "bytes" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" - -[[package]] -name = "cc" -version = "1.0.92" +name = "cc" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2678b2e3449475e95b0aa6f9b506a28e61b3dc8996592b983695e8ebb58a8b41" @@ -743,52 +325,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] -name = "chacha20" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" -dependencies = [ - "cfg-if", - "cipher", - "cpufeatures", -] - -[[package]] -name = "chrono" -version = "0.4.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a0d04d43504c61aa6c7531f1871dd0d418d91130162063b789da00fd7057a5e" -dependencies = [ - "android-tzdata", - "iana-time-zone", - "num-traits", - "windows-targets 0.52.4", -] - -[[package]] -name = "cipher" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" -dependencies = [ - "crypto-common", - "inout", -] - -[[package]] -name = "common-path" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2382f75942f4b3be3690fe4f86365e9c853c1587d6ee58212cebf6e2a9ccd101" - -[[package]] -name = "concurrent-queue" -version = "2.4.0" +name = "color_quant" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d16048cd947b08fa32c24458a22f5dc5e835264f689f4f5653210c69fd107363" -dependencies = [ - "crossbeam-utils", -] +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" [[package]] name = "const-oid" @@ -808,18 +348,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" -[[package]] -name = "constcat" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd7e35aee659887cbfb97aaf227ac12cad1a9d7c71e55ff3376839ed4e282d08" - -[[package]] -name = "convert_case" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" - [[package]] name = "core-foundation" version = "0.9.4" @@ -836,15 +364,6 @@ version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" -[[package]] -name = "cpp_demangle" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eeaa953eaad386a53111e47172c2fedba671e5684c8dd601a5f474f4f118710f" -dependencies = [ - "cfg-if", -] - [[package]] name = "cpufeatures" version = "0.2.12" @@ -854,15 +373,6 @@ 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" @@ -872,21 +382,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "crossbeam-queue" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" - [[package]] name = "crunchy" version = "0.2.2" @@ -900,7 +395,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" dependencies = [ "generic-array", - "rand_core 0.6.4", + "rand_core", "subtle", "zeroize", ] @@ -912,33 +407,9 @@ 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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" -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", -] - [[package]] name = "curve25519-dalek" version = "4.1.2" @@ -967,76 +438,6 @@ dependencies = [ "syn 2.0.58", ] -[[package]] -name = "darling" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850" -dependencies = [ - "darling_core 0.14.4", - "darling_macro 0.14.4", -] - -[[package]] -name = "darling" -version = "0.20.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54e36fcd13ed84ffdfda6f5be89b31287cbb80c439841fe69e04841435464391" -dependencies = [ - "darling_core 0.20.8", - "darling_macro 0.20.8", -] - -[[package]] -name = "darling_core" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn 1.0.109", -] - -[[package]] -name = "darling_core" -version = "0.20.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c2cf1c23a687a1feeb728783b993c4e1ad83d99f351801977dd809b48d0a70f" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn 2.0.58", -] - -[[package]] -name = "darling_macro" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" -dependencies = [ - "darling_core 0.14.4", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "darling_macro" -version = "0.20.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f" -dependencies = [ - "darling_core 0.20.8", - "quote", - "syn 2.0.58", -] - [[package]] name = "der" version = "0.7.9" @@ -1056,38 +457,14 @@ dependencies = [ "powerfmt", ] -[[package]] -name = "derivative" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "derive-syn-parse" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d65d7ce8132b7c0e54497a4d9a55a1c2a0912a0d786cf894472ba818fba45762" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.58", -] - [[package]] name = "derive_more" version = "0.99.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" dependencies = [ - "convert_case", "proc-macro2", "quote", - "rustc_version", "syn 1.0.109", ] @@ -1112,66 +489,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "docify" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a2f138ad521dc4a2ced1a4576148a6a610b4c5923933b062a263130a6802ce" -dependencies = [ - "docify_macros", -] - -[[package]] -name = "docify_macros" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a081e51fb188742f5a7a1164ad752121abcb22874b21e2c3b0dd040c515fdad" -dependencies = [ - "common-path", - "derive-syn-parse", - "once_cell", - "proc-macro2", - "quote", - "regex", - "syn 2.0.58", - "termcolor", - "toml", - "walkdir", -] - -[[package]] -name = "downcast-rs" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" - -[[package]] -name = "dyn-clonable" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e9232f0e607a262ceb9bd5141a3dfb3e4db6994b31989bbfd845878cba59fd4" -dependencies = [ - "dyn-clonable-impl", - "dyn-clone", -] - -[[package]] -name = "dyn-clonable-impl" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "558e40ea573c374cf53507fd240b7ee2f5477df7cfebdb97323ec61c719399c5" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "dyn-clone" -version = "1.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" - [[package]] name = "ecdsa" version = "0.16.9" @@ -1182,7 +499,6 @@ dependencies = [ "digest 0.10.7", "elliptic-curve", "rfc6979", - "serdect", "signature", "spki", ] @@ -1193,59 +509,24 @@ version = "2.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" dependencies = [ - "pkcs8", "signature", ] -[[package]] -name = "ed25519-dalek" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" -dependencies = [ - "curve25519-dalek 4.1.2", - "ed25519", - "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", + "curve25519-dalek", "ed25519", - "hashbrown 0.14.3", + "hashbrown", "hex", - "rand_core 0.6.4", - "sha2 0.10.8", + "rand_core", + "sha2", "zeroize", ] -[[package]] -name = "either" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" - [[package]] name = "elliptic-curve" version = "0.13.8" @@ -1259,19 +540,12 @@ dependencies = [ "generic-array", "group", "pkcs8", - "rand_core 0.6.4", + "rand_core", "sec1", - "serdect", "subtle", "zeroize", ] -[[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" @@ -1279,115 +553,65 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] -name = "errno" -version = "0.3.8" +name = "external-memory-tools" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] +checksum = "cf172ba7bfe5412e03c4dfd7d8e4b5f1e6cd0b7087fd61fa274b73f87ad94854" [[package]] -name = "event-listener" -version = "2.5.3" +name = "fdeflate" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" +checksum = "4f9bfee30e4dedf0ab8b422f03af778d9612b63f502710fc500a334ebe2de645" +dependencies = [ + "simd-adler32", +] [[package]] -name = "event-listener" -version = "4.0.3" +name = "ff" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b215c49b2b248c855fb73579eb1f4f26c38ffdc12973e20e07b91d78d5646e" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" dependencies = [ - "concurrent-queue", - "parking", - "pin-project-lite", + "rand_core", + "subtle", ] [[package]] -name = "event-listener" -version = "5.3.0" +name = "fiat-crypto" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d9944b8ca13534cdfb2800775f8dd4902ff3fc75a50101466decadfdf322a24" -dependencies = [ - "concurrent-queue", - "parking", - "pin-project-lite", -] +checksum = "c007b1ae3abe1cb6f85a16305acd418b7ca6343b953633fee2b76d8f108b830f" [[package]] -name = "event-listener-strategy" -version = "0.4.0" +name = "find-crate" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "958e4d70b6d5e81971bebec42271ec641e7ff4e170a6fa605f2b8a8b65cb97d3" +checksum = "59a98bbaacea1c0eb6a0876280051b892eb73594fd90cf3b20e9c817029c57d2" dependencies = [ - "event-listener 4.0.3", - "pin-project-lite", + "toml 0.5.11", ] [[package]] -name = "event-listener-strategy" -version = "0.5.1" +name = "fixed-hash" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "332f51cb23d20b0de8458b86580878211da09bcd4503cb579c225b3d124cabb3" +checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" dependencies = [ - "event-listener 5.3.0", - "pin-project-lite", + "byteorder", + "rand", + "rustc-hex", + "static_assertions", ] [[package]] -name = "expander" -version = "2.1.0" +name = "flate2" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00e83c02035136f1592a47964ea60c05a50e4ed8b5892cfac197063850898d4d" +checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" dependencies = [ - "blake2", - "fs-err", - "prettier-please", - "proc-macro2", - "quote", - "syn 2.0.58", -] - -[[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 = "ff" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" -dependencies = [ - "rand_core 0.6.4", - "subtle", -] - -[[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", + "crc32fast", + "miniz_oxide", ] [[package]] @@ -1405,17 +629,6 @@ 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" @@ -1428,15 +641,6 @@ dependencies = [ "serde", ] -[[package]] -name = "fs-err" -version = "2.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88a41f105fe1d5b6b34b2055e3dc59bb79b46b48b2040b9e6c7b4b5de097aa41" -dependencies = [ - "autocfg", -] - [[package]] name = "funty" version = "2.0.0" @@ -1451,7 +655,6 @@ checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" dependencies = [ "futures-channel", "futures-core", - "futures-executor", "futures-io", "futures-sink", "futures-task", @@ -1474,37 +677,12 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" -[[package]] -name = "futures-executor" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", - "num_cpus", -] - [[package]] name = "futures-io" 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" @@ -1580,19 +758,7 @@ version = "0.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ea1015b5a70616b688dc230cfe50c8af89d972cb132d5a622814d29773b10b9" dependencies = [ - "rand", - "rand_core 0.6.4", -] - -[[package]] -name = "gimli" -version = "0.27.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" -dependencies = [ - "fallible-iterator", - "indexmap 1.9.3", - "stable_deref_trait", + "rand_core", ] [[package]] @@ -1608,71 +774,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ "ff", - "rand_core 0.6.4", + "rand_core", "subtle", ] -[[package]] -name = "h2" -version = "0.3.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" -dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http 0.2.12", - "indexmap 2.2.6", - "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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" dependencies = [ - "ahash 0.8.11", + "ahash", "allocator-api2", - "serde", ] [[package]] @@ -1693,32 +806,6 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -[[package]] -name = "hex-conservative" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30ed443af458ccb6d81c1e7e661545f94d3176752fb1df2f543b902a1e0f51e2" - -[[package]] -name = "hex-simd" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f7685beb53fc20efc2605f32f5d51e9ba18b8ef237961d1760169d2290d3bee" -dependencies = [ - "outref", - "vsimd", -] - -[[package]] -name = "hmac" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "126888268dcc288495a26bf004b38c5fdbb31682f992c84ceb046a1f0fe38840" -dependencies = [ - "crypto-mac", - "digest 0.9.0", -] - [[package]] name = "hmac" version = "0.12.1" @@ -1728,17 +815,6 @@ 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", -] - [[package]] name = "http" version = "0.2.12" @@ -1761,17 +837,6 @@ dependencies = [ "itoa", ] -[[package]] -name = "http-body" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" -dependencies = [ - "bytes", - "http 0.2.12", - "pin-project-lite", -] - [[package]] name = "http-body" version = "1.0.0" @@ -1791,7 +856,7 @@ dependencies = [ "bytes", "futures-core", "http 1.1.0", - "http-body 1.0.0", + "http-body", "pin-project-lite", ] @@ -1807,30 +872,6 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" -[[package]] -name = "hyper" -version = "0.14.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" -dependencies = [ - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "h2", - "http 0.2.12", - "http-body 0.4.6", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", - "want", -] - [[package]] name = "hyper" version = "1.2.0" @@ -1841,7 +882,7 @@ dependencies = [ "futures-channel", "futures-util", "http 1.1.0", - "http-body 1.0.0", + "http-body", "httparse", "httpdate", "itoa", @@ -1850,22 +891,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "hyper-rustls" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" -dependencies = [ - "futures-util", - "http 0.2.12", - "hyper 0.14.28", - "log", - "rustls 0.21.10", - "rustls-native-certs 0.6.3", - "tokio", - "tokio-rustls 0.24.1", -] - [[package]] name = "hyper-util" version = "0.1.3" @@ -1875,42 +900,13 @@ dependencies = [ "bytes", "futures-util", "http 1.1.0", - "http-body 1.0.0", - "hyper 1.2.0", + "http-body", + "hyper", "pin-project-lite", "socket2", "tokio", ] -[[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" @@ -1922,21 +918,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]] @@ -1950,17 +950,6 @@ dependencies = [ "syn 1.0.109", ] -[[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" @@ -1968,31 +957,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", - "hashbrown 0.14.3", -] - -[[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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" -dependencies = [ - "generic-array", -] - -[[package]] -name = "instant" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" -dependencies = [ - "cfg-if", + "hashbrown", ] [[package]] @@ -2004,59 +969,19 @@ dependencies = [ "num-traits", ] -[[package]] -name = "io-lifetimes" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" -dependencies = [ - "hermit-abi", - "libc", - "windows-sys 0.48.0", -] - -[[package]] -name = "itertools" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" -dependencies = [ - "either", -] - -[[package]] -name = "itertools" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" -dependencies = [ - "either", -] - [[package]] name = "itoa" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" -[[package]] -name = "js-sys" -version = "0.3.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" -dependencies = [ - "wasm-bindgen", -] - [[package]] name = "jsonrpsee" version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4b0e68d9af1f066c06d6e2397583795b912d78537d7d907c561e82c13d69fa1" dependencies = [ - "jsonrpsee-client-transport", "jsonrpsee-core", - "jsonrpsee-http-client", "jsonrpsee-types", "jsonrpsee-ws-client", ] @@ -2071,12 +996,12 @@ dependencies = [ "http 0.2.12", "jsonrpsee-core", "pin-project", - "rustls-native-certs 0.7.0", + "rustls-native-certs", "rustls-pki-types", "soketto", "thiserror", "tokio", - "tokio-rustls 0.25.0", + "tokio-rustls", "tokio-util", "tracing", "url", @@ -2093,7 +1018,6 @@ dependencies = [ "beef", "futures-timer", "futures-util", - "hyper 0.14.28", "jsonrpsee-types", "pin-project", "rustc-hash", @@ -2106,30 +1030,10 @@ dependencies = [ ] [[package]] -name = "jsonrpsee-http-client" +name = "jsonrpsee-types" version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac13bc1e44cd00448a5ff485824a128629c945f02077804cb659c07a0ba41395" -dependencies = [ - "async-trait", - "hyper 0.14.28", - "hyper-rustls", - "jsonrpsee-core", - "jsonrpsee-types", - "serde", - "serde_json", - "thiserror", - "tokio", - "tower", - "tracing", - "url", -] - -[[package]] -name = "jsonrpsee-types" -version = "0.22.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dc828e537868d6b12bbb07ec20324909a22ced6efca0057c825c3e1126b2c6d" +checksum = "3dc828e537868d6b12bbb07ec20324909a22ced6efca0057c825c3e1126b2c6d" dependencies = [ "anyhow", "beef", @@ -2160,29 +1064,36 @@ dependencies = [ "cfg-if", "ecdsa", "elliptic-curve", - "once_cell", - "serdect", - "sha2 0.10.8", + "sha2", ] [[package]] name = "kalatori" version = "0.2.0-rc3" dependencies = [ - "anyhow", "axum", "axum-macros", - "hex-simd", + "frame-metadata", + "hex", + "jsonrpsee", + "mnemonic-external", "names", + "parity-scale-codec", + "primitive-types", "redb", "scale-info", "serde", - "subxt", + "serde_json", + "sp-crypto-hashing", + "substrate-constructor", + "substrate-crypto-light", + "substrate_parser 0.6.1 (git+https://github.com/Alzymologist/substrate-parser)", + "thiserror", "tokio", "tokio-util", - "toml_edit 0.22.9", + "toml 0.8.12", "tracing", - "tracing-subscriber 0.3.18", + "tracing-subscriber", "ureq", ] @@ -2200,6 +1111,9 @@ name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +dependencies = [ + "spin 0.5.2", +] [[package]] name = "libc" @@ -2207,72 +1121,6 @@ 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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95b09eff1b35ed3b33b877ced3a691fc7a481919c7e29c53c906226fcf55e2a1" -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", -] - -[[package]] -name = "libsecp256k1-core" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5be9b9bb642d8522a44d533eab56c16c738301965504753b03ad1de3425d5451" -dependencies = [ - "crunchy", - "digest 0.9.0", - "subtle", -] - -[[package]] -name = "libsecp256k1-gen-ecmult" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3038c808c55c87e8a172643a7d87187fc6c4174468159cb3090659d55bcb4809" -dependencies = [ - "libsecp256k1-core", -] - -[[package]] -name = "libsecp256k1-gen-genmult" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3db8d6ba2cec9eacc40e6e8ccc98931840301f1006e95647ceb2dd5c3aa06f7c" -dependencies = [ - "libsecp256k1-core", -] - -[[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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" - [[package]] name = "lock_api" version = "0.4.11" @@ -2289,33 +1137,6 @@ 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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa" -dependencies = [ - "libc", -] - -[[package]] -name = "matchers" -version = "0.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f099785f7595cc4b4553a174ce30dd7589ef93391ff414dbb67f62392b9e0ce1" -dependencies = [ - "regex-automata 0.1.10", -] - [[package]] name = "matchers" version = "0.1.0" @@ -2337,33 +1158,6 @@ version = "2.7.2" 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", -] - [[package]] name = "merlin" version = "3.0.0" @@ -2372,7 +1166,7 @@ checksum = "58c38e2799fc0978b65dfff8023ec7843e2330bb462f19198840b34b6582397d" dependencies = [ "byteorder", "keccak", - "rand_core 0.6.4", + "rand_core", "zeroize", ] @@ -2382,12 +1176,6 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - [[package]] name = "miniz_oxide" version = "0.7.2" @@ -2395,6 +1183,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" dependencies = [ "adler", + "simd-adler32", ] [[package]] @@ -2408,6 +1197,14 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "mnemonic-external" +version = "0.1.0" +dependencies = [ + "bitvec", + "sha2", +] + [[package]] name = "names" version = "0.14.0" @@ -2417,34 +1214,12 @@ dependencies = [ "rand", ] -[[package]] -name = "no-std-net" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43794a0ace135be66a25d3ae77d41b91615fb68ae937f904090203e81f755b65" - [[package]] name = "nodrop" version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" -[[package]] -name = "nohash-hasher" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" - -[[package]] -name = "nom" -version = "7.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" -dependencies = [ - "memchr", - "minimal-lexical", -] - [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -2472,16 +1247,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" -[[package]] -name = "num-format" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a652d9771a63711fd3c3deb670acfbe5c30a4072e664d7a3bf5a9e1056ac72c3" -dependencies = [ - "arrayvec 0.7.4", - "itoa", -] - [[package]] name = "num-integer" version = "0.1.46" @@ -2491,18 +1256,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-rational" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" -dependencies = [ - "autocfg", - "num-bigint", - "num-integer", - "num-traits", -] - [[package]] name = "num-traits" version = "0.2.18" @@ -2522,18 +1275,6 @@ dependencies = [ "libc", ] -[[package]] -name = "object" -version = "0.30.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03b4680b86d9cfafba8fc491dc9b6df26b68cf40e9e6cd73909194759a63c385" -dependencies = [ - "crc32fast", - "hashbrown 0.13.2", - "indexmap 1.9.3", - "memchr", -] - [[package]] name = "object" version = "0.32.2" @@ -2561,12 +1302,6 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" -[[package]] -name = "outref" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4030760ffd992bef45b0ae3f10ce1aba99e33464c90d14dd7c039884963ddc7a" - [[package]] name = "overload" version = "0.1.1" @@ -2574,16 +1309,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] -name = "parity-bip39" -version = "2.0.1" +name = "palette" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e69bf016dc406eff7d53a7d3f7cf1c2e72c82b9088aac1118591e36dd2cd3e9" +checksum = "8f9cd68f7112581033f157e56c77ac4a5538ec5836a2e39284e65bd7d7275e49" dependencies = [ - "bitcoin_hashes 0.13.0", - "rand", - "rand_core 0.6.4", - "serde", - "unicode-normalization", + "approx", + "num-traits", + "palette_derive", +] + +[[package]] +name = "palette_derive" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05eedf46a8e7c27f74af0c9cfcdb004ceca158cb1b918c6f68f8d7a549b3e427" +dependencies = [ + "find-crate", + "proc-macro2", + "quote", + "syn 1.0.109", ] [[package]] @@ -2595,7 +1340,6 @@ dependencies = [ "arrayvec 0.7.4", "bitvec", "byte-slice-cast", - "bytes", "impl-trait-for-tuples", "parity-scale-codec-derive", "serde", @@ -2613,12 +1357,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "parking" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" - [[package]] name = "parking_lot" version = "0.12.1" @@ -2642,23 +1380,6 @@ dependencies = [ "windows-targets 0.48.5", ] -[[package]] -name = "password-hash" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" -dependencies = [ - "base64ct", - "rand_core 0.6.4", - "subtle", -] - -[[package]] -name = "paste" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" - [[package]] name = "pbkdf2" version = "0.12.2" @@ -2666,7 +1387,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" dependencies = [ "digest 0.10.7", - "password-hash", ] [[package]] @@ -2707,17 +1427,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" @@ -2735,217 +1444,88 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db23d408679286588f4d4644f965003d056e3dd5abcaaa938116871d7ce2fee7" [[package]] -name = "polkavm-common" -version = "0.8.0" +name = "plot_icon" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92c99f7eee94e7be43ba37eef65ad0ee8cbaf89b7c00001c3f6d2be985cb1817" +checksum = "6b92a0e6ac9a5007559362f0f41a4feaa3021a2ac07c54b8788fad354137b5fa" +dependencies = [ + "blake2-rfc", + "image", + "palette", + "png", +] [[package]] -name = "polkavm-common" -version = "0.9.0" +name = "png" +version = "0.17.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d9428a5cfcc85c5d7b9fc4b6a18c4b802d0173d768182a51cc7751640f08b92" +checksum = "06e4b0d3d1312775e782c86c91a111aa1f910cbb65e1337f9975b5f9a554b5e1" +dependencies = [ + "bitflags", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] [[package]] -name = "polkavm-derive" -version = "0.8.0" +name = "powerfmt" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79fa916f7962348bd1bb1a65a83401675e6fc86c51a0fdbcf92a3108e58e6125" -dependencies = [ - "polkavm-derive-impl-macro 0.8.0", -] +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] -name = "polkavm-derive" -version = "0.9.1" +name = "ppv-lite86" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae8c4bea6f3e11cd89bb18bcdddac10bd9a24015399bd1c485ad68a985a19606" -dependencies = [ - "polkavm-derive-impl-macro 0.9.0", -] +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] -name = "polkavm-derive-impl" -version = "0.8.0" +name = "primitive-types" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c10b2654a8a10a83c260bfb93e97b262cf0017494ab94a65d389e0eda6de6c9c" +checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2" dependencies = [ - "polkavm-common 0.8.0", - "proc-macro2", - "quote", - "syn 2.0.58", + "fixed-hash", + "impl-codec", + "uint", ] [[package]] -name = "polkavm-derive-impl" -version = "0.9.0" +name = "proc-macro-crate" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c4fdfc49717fb9a196e74a5d28e0bc764eb394a2c803eb11133a31ac996c60c" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" dependencies = [ - "polkavm-common 0.9.0", - "proc-macro2", - "quote", - "syn 2.0.58", + "once_cell", + "toml_edit 0.19.15", ] [[package]] -name = "polkavm-derive-impl-macro" -version = "0.8.0" +name = "proc-macro-crate" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15e85319a0d5129dc9f021c62607e0804f5fb777a05cdda44d750ac0732def66" +checksum = "7e8366a6159044a37876a2b9817124296703c586a5c92e2c53751fa06d8d43e8" dependencies = [ - "polkavm-derive-impl 0.8.0", - "syn 2.0.58", + "toml_edit 0.20.7", ] [[package]] -name = "polkavm-derive-impl-macro" -version = "0.9.0" +name = "proc-macro2" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ba81f7b5faac81e528eb6158a6f3c9e0bb1008e0ffa19653bc8dea925ecb429" +checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" dependencies = [ - "polkavm-derive-impl 0.9.0", - "syn 2.0.58", + "unicode-ident", ] [[package]] -name = "polling" -version = "3.6.0" +name = "quote" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0c976a60b2d7e99d6f229e414670a9b85d13ac305cc6d1e9c134de58c5aaaf6" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ - "cfg-if", - "concurrent-queue", - "hermit-abi", - "pin-project-lite", - "rustix 0.38.32", - "tracing", - "windows-sys 0.52.0", -] - -[[package]] -name = "poly1305" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" -dependencies = [ - "cpufeatures", - "opaque-debug", - "universal-hash", -] - -[[package]] -name = "powerfmt" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" - -[[package]] -name = "ppv-lite86" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" - -[[package]] -name = "prettier-please" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22020dfcf177fcc7bf5deaf7440af371400c67c0de14c399938d8ed4fb4645d3" -dependencies = [ - "proc-macro2", - "syn 2.0.58", -] - -[[package]] -name = "primitive-types" -version = "0.12.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", -] - -[[package]] -name = "proc-macro2" -version = "1.0.79" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" -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.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" -dependencies = [ - "proc-macro2", + "proc-macro2", ] [[package]] @@ -2962,7 +1542,7 @@ checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha", - "rand_core 0.6.4", + "rand_core", ] [[package]] @@ -2972,15 +1552,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" @@ -2990,22 +1564,6 @@ 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", - "serde_json", - "thiserror", - "tokio", - "tokio-retry", - "tokio-stream", - "tracing", -] - [[package]] name = "redb" version = "2.0.0" @@ -3021,27 +1579,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" 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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fddb4f8d99b0a2ebafc65a87a69a7b9875e4b1ae1f00db265d300ef7f28bccc" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.58", + "bitflags", ] [[package]] @@ -3094,7 +1632,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" dependencies = [ - "hmac 0.12.1", + "hmac", "subtle", ] @@ -3108,7 +1646,7 @@ dependencies = [ "cfg-if", "getrandom", "libc", - "spin", + "spin 0.9.8", "untrusted", "windows-sys 0.52.0", ] @@ -3140,45 +1678,6 @@ 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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89" -dependencies = [ - "bitflags 2.5.0", - "errno", - "libc", - "linux-raw-sys 0.4.13", - "windows-sys 0.52.0", -] - -[[package]] -name = "rustls" -version = "0.21.10" -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" @@ -3188,23 +1687,11 @@ dependencies = [ "log", "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" @@ -3212,21 +1699,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f1fb85efa936c42c6d5fc28d2629bb51e4b2f4b8a5211e297d599cc5a093792" dependencies = [ "openssl-probe", - "rustls-pemfile 2.1.2", + "rustls-pemfile", "rustls-pki-types", "schannel", "security-framework", ] -[[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.2" @@ -3243,16 +1721,6 @@ version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ecd36cc4259e3e4514335c4a138c6b43171a8d61d8f5c9348f9fc7529416f247" -[[package]] -name = "rustls-webpki" -version = "0.101.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" -dependencies = [ - "ring", - "untrusted", -] - [[package]] name = "rustls-webpki" version = "0.102.2" @@ -3270,99 +1738,12 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "80af6f9131f277a45a3fba6ce8e2258037bb0477a67e610d3c1fe046ab31de47" -[[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", -] - [[package]] name = "ryu" version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "scale-bits" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "662d10dcd57b1c2a3c41c9cf68f71fb09747ada1ea932ad961aca7e2ca28315f" -dependencies = [ - "parity-scale-codec", - "scale-info", - "scale-type-resolver", - "serde", -] - -[[package]] -name = "scale-decode" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afc79ba56a1c742f5aeeed1f1801f3edf51f7e818f0a54582cac6f131364ea7b" -dependencies = [ - "derive_more", - "parity-scale-codec", - "primitive-types", - "scale-bits", - "scale-decode-derive", - "scale-type-resolver", - "smallvec", -] - -[[package]] -name = "scale-decode-derive" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5398fdb3c7bea3cb419bac4983aadacae93fe1a7b5f693f4ebd98c3821aad7a5" -dependencies = [ - "darling 0.14.4", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "scale-encode" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "628800925a33794fb5387781b883b5e14d130fece9af5a63613867b8de07c5c7" -dependencies = [ - "derive_more", - "parity-scale-codec", - "primitive-types", - "scale-bits", - "scale-encode-derive", - "scale-type-resolver", - "smallvec", -] - -[[package]] -name = "scale-encode-derive" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a304e1af7cdfbe7a24e08b012721456cc8cecdedadc14b3d10513eada63233c" -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.2" @@ -3390,84 +1771,27 @@ dependencies = [ ] [[package]] -name = "scale-type-resolver" -version = "0.1.1" +name = "schannel" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10b800069bfd43374e0f96f653e0d46882a2cb16d6d961ac43bea80f26c76843" +checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" dependencies = [ - "scale-info", - "smallvec", + "windows-sys 0.52.0", ] [[package]] -name = "scale-typegen" -version = "0.2.1" +name = "schnorrkel" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d470fa75e71b12b3244a4113adc4bc49891f3daba2054703cacd06256066397e" +checksum = "8de18f6d8ba0aad7045f5feae07ec29899c1112584a38509a84ad7b04451eaa0" dependencies = [ - "proc-macro2", - "quote", - "scale-info", - "syn 2.0.58", - "thiserror", -] - -[[package]] -name = "scale-value" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c07ccfee963104335c971aaf8b7b0e749be8569116322df23f1f75c4ca9e4a28" -dependencies = [ - "base58", - "blake2", - "derive_more", - "either", - "frame-metadata 15.1.0", - "parity-scale-codec", - "scale-bits", - "scale-decode", - "scale-encode", - "scale-info", - "scale-type-resolver", - "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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "772575a524feeb803e5b0fcbc6dd9f367e579488197c94c6e4023aad2305774d" -dependencies = [ - "ahash 0.8.11", - "cfg-if", - "hashbrown 0.13.2", -] - -[[package]] -name = "schnorrkel" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de18f6d8ba0aad7045f5feae07ec29899c1112584a38509a84ad7b04451eaa0" -dependencies = [ - "aead", "arrayref", "arrayvec 0.7.4", - "curve25519-dalek 4.1.2", + "curve25519-dalek", "getrandom_or_panic", "merlin", - "rand_core 0.6.4", - "serde_bytes", - "sha2 0.10.8", + "rand_core", + "sha2", "subtle", "zeroize", ] @@ -3478,16 +1802,6 @@ version = "1.2.0" 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 = "sec1" version = "0.7.3" @@ -3498,45 +1812,17 @@ dependencies = [ "der", "generic-array", "pkcs8", - "serdect", "subtle", "zeroize", ] -[[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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bd1c54ea06cfd2f6b63219704de0b9b4f72dcc2b8fdef820be6cd799780e91e" -dependencies = [ - "zeroize", -] - [[package]] name = "security-framework" version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "770452e37cad93e0a50d5abc3990d2bc351c36d0328f86cefec2f2fb206eaef6" dependencies = [ - "bitflags 1.3.2", + "bitflags", "core-foundation", "core-foundation-sys", "libc", @@ -3568,15 +1854,6 @@ dependencies = [ "serde_derive", ] -[[package]] -name = "serde_bytes" -version = "0.11.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b8497c313fd43ab992087548117643f6fcd935cbf36f176ffda0aacf9591734" -dependencies = [ - "serde", -] - [[package]] name = "serde_derive" version = "1.0.197" @@ -3590,9 +1867,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.115" +version = "1.0.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12dc5c46daa8e9fdf4f5e71b6cf9a53f2487da0e86e55808e2d35539666497dd" +checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" dependencies = [ "itoa", "ryu", @@ -3630,16 +1907,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serdect" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a84f14a19e9a014bb9f4512488d9829a68e04ecabffb0f9904cd1ace94598177" -dependencies = [ - "base16ct", - "serde", -] - [[package]] name = "sha-1" version = "0.9.8" @@ -3653,19 +1920,6 @@ dependencies = [ "opaque-debug", ] -[[package]] -name = "sha2" -version = "0.9.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" -dependencies = [ - "block-buffer 0.9.0", - "cfg-if", - "cpufeatures", - "digest 0.9.0", - "opaque-debug", -] - [[package]] name = "sha2" version = "0.10.8" @@ -3712,20 +1966,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ "digest 0.10.7", - "rand_core 0.6.4", + "rand_core", ] [[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" +name = "simd-adler32" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" [[package]] name = "slab" @@ -3739,473 +1987,96 @@ dependencies = [ [[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", - "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 = "33.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13ca6121c22c8bd3d1dce1f05c479101fd0d7b159bef2a3e8c834138d839c75c" -dependencies = [ - "parity-scale-codec", - "scale-info", - "serde", - "sp-core", - "sp-io", - "sp-std", -] - -[[package]] -name = "sp-arithmetic" -version = "25.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "910c07fa263b20bf7271fdd4adcb5d3217dfdac14270592e0780223542e7e114" -dependencies = [ - "integer-sqrt", - "num-traits", - "parity-scale-codec", - "scale-info", - "serde", - "sp-std", - "static_assertions", -] - -[[package]] -name = "sp-core" -version = "31.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d7a0fd8f16dcc3761198fc83be12872f823b37b749bc72a3a6a1f702509366" -dependencies = [ - "array-bytes", - "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", - "k256", - "libsecp256k1", - "log", - "merlin", - "parity-bip39", - "parity-scale-codec", - "parking_lot", - "paste", - "primitive-types", - "rand", - "scale-info", - "schnorrkel", - "secp256k1", - "secrecy", - "serde", - "sp-crypto-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-crypto-hashing" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc9927a7f81334ed5b8a98a4a978c81324d12bd9713ec76b5c68fd410174c5eb" -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.58", -] - -[[package]] -name = "sp-externalities" -version = "0.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d6a4572eadd4a63cff92509a210bf425501a0c5e76574b30a366ac77653787" -dependencies = [ - "environmental", - "parity-scale-codec", - "sp-std", - "sp-storage", -] - -[[package]] -name = "sp-io" -version = "33.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e09bba780b55bd9e67979cd8f654a31e4a6cf45426ff371394a65953d2177f2" -dependencies = [ - "bytes", - "ed25519-dalek", - "libsecp256k1", - "log", - "parity-scale-codec", - "polkavm-derive 0.9.1", - "rustversion", - "secp256k1", - "sp-core", - "sp-crypto-hashing", - "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.37.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdbab8b61bd61d5f8625a0c75753b5d5a23be55d3445419acd42caf59cf6236b" -dependencies = [ - "parity-scale-codec", - "parking_lot", - "sp-core", - "sp-externalities", -] - -[[package]] -name = "sp-panic-handler" -version = "13.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8f5a17a0a11de029a8b811cb6e8b32ce7e02183cc04a3e965c383246798c416" -dependencies = [ - "backtrace", - "lazy_static", - "regex", -] - -[[package]] -name = "sp-runtime" -version = "34.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec3cb126971e7db2f0fcf8053dce740684c438c7180cfca1959598230f342c58" -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", -] - -[[package]] -name = "sp-runtime-interface" -version = "26.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e48a675ea4858333d4d755899ed5ed780174aa34fec15953428d516af5452295" -dependencies = [ - "bytes", - "impl-trait-for-tuples", - "parity-scale-codec", - "polkavm-derive 0.8.0", - "primitive-types", - "sp-externalities", - "sp-runtime-interface-proc-macro", - "sp-std", - "sp-storage", - "sp-tracing", - "sp-wasm-interface", - "static_assertions", -] +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] -name = "sp-runtime-interface-proc-macro" -version = "18.0.0" +name = "socket2" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0195f32c628fee3ce1dfbbf2e7e52a30ea85f3589da9fe62a8b816d70fc06294" +checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" dependencies = [ - "Inflector", - "expander", - "proc-macro-crate 3.1.0", - "proc-macro2", - "quote", - "syn 2.0.58", + "libc", + "windows-sys 0.52.0", ] [[package]] -name = "sp-state-machine" -version = "0.38.0" +name = "soketto" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1eae0eac8034ba14437e772366336f579398a46d101de13dbb781ab1e35e67c5" +checksum = "41d1c5305e39e09653383c2c7244f2f78b3bcae37cf50c64cb4789c9f5096ec2" dependencies = [ - "hash-db", + "base64 0.13.1", + "bytes", + "futures", + "httparse", "log", - "parity-scale-codec", - "parking_lot", "rand", - "smallvec", - "sp-core", - "sp-externalities", - "sp-panic-handler", - "sp-std", - "sp-trie", - "thiserror", - "tracing", - "trie-db", + "sha-1", ] [[package]] -name = "sp-std" -version = "14.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12f8ee986414b0a9ad741776762f4083cd3a5128449b982a3919c4df36874834" - -[[package]] -name = "sp-storage" -version = "20.0.0" +name = "sp-arithmetic" +version = "24.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8dba5791cb3978e95daf99dad919ecb3ec35565604e88cd38d805d9d4981e8bd" +checksum = "afa823ca5adc490d47dccb41d69ad482bc57a317bd341de275868378f48f131c" dependencies = [ - "impl-serde", + "integer-sqrt", + "num-traits", "parity-scale-codec", - "ref-cast", - "serde", - "sp-debug-derive", + "scale-info", "sp-std", + "static_assertions", ] [[package]] -name = "sp-tracing" -version = "16.0.0" +name = "sp-arithmetic" +version = "25.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0351810b9d074df71c4514c5228ed05c250607cba131c1c9d1526760ab69c05c" +checksum = "910c07fa263b20bf7271fdd4adcb5d3217dfdac14270592e0780223542e7e114" dependencies = [ + "integer-sqrt", + "num-traits", "parity-scale-codec", + "scale-info", "sp-std", - "tracing", - "tracing-core", - "tracing-subscriber 0.2.25", + "static_assertions", ] [[package]] -name = "sp-trie" -version = "32.0.0" +name = "sp-core-hashing" +version = "16.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1aa91ad26c62b93d73e65f9ce7ebd04459c4bad086599348846a81988d6faa4" +checksum = "7f812cb2dff962eb378c507612a50f1c59f52d92eb97b710f35be3c2346a3cd7" dependencies = [ - "ahash 0.8.11", - "hash-db", - "lazy_static", - "memory-db", - "nohash-hasher", - "parity-scale-codec", - "parking_lot", - "rand", - "scale-info", - "schnellru", - "sp-core", - "sp-externalities", - "sp-std", - "thiserror", - "tracing", - "trie-db", - "trie-root", + "sp-crypto-hashing", ] [[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 0.10.7", + "sha2", + "sha3", + "twox-hash", ] [[package]] -name = "sp-weights" -version = "30.0.0" +name = "sp-std" +version = "14.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9af6c661fe3066b29f9e1d258000f402ff5cc2529a9191972d214e5871d0ba87" -dependencies = [ - "bounded-collections", - "parity-scale-codec", - "scale-info", - "serde", - "smallvec", - "sp-arithmetic", - "sp-debug-derive", - "sp-std", -] +checksum = "12f8ee986414b0a9ad741776762f4083cd3a5128449b982a3919c4df36874834" + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" [[package]] name = "spin" @@ -4223,27 +2094,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" @@ -4251,135 +2101,88 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] -name = "strsim" -version = "0.10.0" +name = "substrate-constructor" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +checksum = "73855d033fd5a8e24342d11d43aa3657f030a7d00c064d63988577b173c17922" +dependencies = [ + "bitvec", + "external-memory-tools", + "frame-metadata", + "hex", + "num-bigint", + "parity-scale-codec", + "primitive-types", + "scale-info", + "sp-arithmetic 24.0.0", + "substrate_parser 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] -name = "substrate-bip39" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2b564c293e6194e8b222e52436bcb99f60de72043c7f845cf6c4406db4df121" +name = "substrate-crypto-light" +version = "0.1.0" +source = "git+https://github.com/Alzymologist/substrate-crypto-light#7d933807f0ffaf150a069266083e8b2fc9cadf69" dependencies = [ - "hmac 0.12.1", + "base58", + "blake2b_simd", + "ed25519-zebra", + "hmac", + "k256", + "lazy_static", + "parity-scale-codec", "pbkdf2", + "rand_core", + "regex", "schnorrkel", - "sha2 0.10.8", + "sha2", "zeroize", ] [[package]] -name = "subtle" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" - -[[package]] -name = "subxt" -version = "0.35.2" +name = "substrate_parser" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "388162448313740aabe675bff00698e72f876b1c6ec85d4d2c34783cfa32a0f7" +checksum = "3692eaf5785c67a07d884a3308c783916307300085fe160b8d9277b389505fb9" dependencies = [ - "async-trait", "base58", + "bitvec", "blake2", - "derivative", - "either", - "frame-metadata 16.0.0", - "futures", + "external-memory-tools", + "frame-metadata", "hex", - "impl-serde", - "instant", - "jsonrpsee", + "num-bigint", "parity-scale-codec", "primitive-types", - "reconnecting-jsonrpsee-ws-client", - "scale-bits", - "scale-decode", - "scale-encode", "scale-info", - "scale-value", - "serde", - "serde_json", - "sp-core", - "sp-crypto-hashing", - "sp-runtime", - "subxt-lightclient", - "subxt-macro", - "subxt-metadata", - "thiserror", - "tokio-util", - "tracing", - "url", + "sp-arithmetic 24.0.0", + "sp-core-hashing", ] [[package]] -name = "subxt-codegen" -version = "0.35.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd94344feea939a37b919b7381e038dededfd1a88e01bedda67fe40847abfc78" +name = "substrate_parser" +version = "0.6.1" +source = "git+https://github.com/Alzymologist/substrate-parser#65de6a4fe207a64f9857247af4e9f7509fa6de4f" dependencies = [ - "frame-metadata 16.0.0", - "heck", + "base58", + "bitvec", + "blake2", + "external-memory-tools", + "frame-metadata", "hex", - "jsonrpsee", + "num-bigint", "parity-scale-codec", - "proc-macro2", - "quote", + "plot_icon", + "primitive-types", "scale-info", - "scale-typegen", - "subxt-metadata", - "syn 2.0.58", - "thiserror", - "tokio", -] - -[[package]] -name = "subxt-lightclient" -version = "0.35.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0dbc6ed49c3c5607fc7423d7ebda4dae858eb3040cdaec602105a240d4f412f" -dependencies = [ - "futures", - "futures-util", - "serde", - "serde_json", - "smoldot-light", - "thiserror", - "tokio", - "tokio-stream", - "tracing", -] - -[[package]] -name = "subxt-macro" -version = "0.35.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4707a920898a7c653210bc946d26904e81ae6fcbb4f91e3a56101d5979f72fe9" -dependencies = [ - "darling 0.20.8", - "parity-scale-codec", - "proc-macro-error", - "quote", - "scale-typegen", - "subxt-codegen", - "syn 2.0.58", + "sp-arithmetic 25.0.0", + "sp-crypto-hashing", ] [[package]] -name = "subxt-metadata" -version = "0.35.2" +name = "subtle" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65ffc8b7d246ebd38611f818547ee8e09fd69717cb79aae22e3a54fc423e6e14" -dependencies = [ - "derive_more", - "frame-metadata 16.0.0", - "hashbrown 0.14.3", - "parity-scale-codec", - "scale-info", - "sp-crypto-hashing", -] +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" [[package]] name = "syn" @@ -4421,21 +2224,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" -[[package]] -name = "target-lexicon" -version = "0.12.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1fc403891a21bcfb7c37834ba66a547a8f402146eba7265b5a6d88059c9ff2f" - -[[package]] -name = "termcolor" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" -dependencies = [ - "winapi-util", -] - [[package]] name = "thiserror" version = "1.0.58" @@ -4523,6 +2311,7 @@ dependencies = [ "libc", "mio", "num_cpus", + "parking_lot", "pin-project-lite", "signal-hook-registry", "socket2", @@ -4541,34 +2330,13 @@ dependencies = [ "syn 2.0.58", ] -[[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", -] - -[[package]] -name = "tokio-rustls" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" -dependencies = [ - "rustls 0.21.10", - "tokio", -] - [[package]] name = "tokio-rustls" version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" dependencies = [ - "rustls 0.22.3", + "rustls", "rustls-pki-types", "tokio", ] @@ -4595,10 +2363,18 @@ dependencies = [ "futures-io", "futures-sink", "futures-util", - "hashbrown 0.14.3", + "hashbrown", "pin-project-lite", "tokio", - "tracing", +] + +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", ] [[package]] @@ -4628,7 +2404,7 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.2.6", + "indexmap", "toml_datetime", "winnow 0.5.40", ] @@ -4639,18 +2415,7 @@ 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", + "indexmap", "toml_datetime", "winnow 0.5.40", ] @@ -4661,7 +2426,7 @@ version = "0.22.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e40bb779c5187258fd7aad0eb68cb8706a0a81fa712fbea808ab43c4b8374c4" dependencies = [ - "indexmap 2.2.6", + "indexmap", "serde", "serde_spanned", "toml_datetime", @@ -4681,7 +2446,6 @@ dependencies = [ "tokio", "tower-layer", "tower-service", - "tracing", ] [[package]] @@ -4702,7 +2466,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", @@ -4722,54 +2485,10 @@ dependencies = [ [[package]] name = "tracing-core" version = "0.1.32" -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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e0d2eaa99c3c2e41547cfa109e910a68ea03823cccad4a0525dcbc9b01e8c71" -dependencies = [ - "ansi_term", - "chrono", - "lazy_static", - "matchers 0.0.1", - "regex", - "serde", - "serde_json", - "sharded-slab", - "smallvec", - "thread_local", - "tracing", - "tracing-core", - "tracing-log", - "tracing-serde", +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", ] [[package]] @@ -4778,7 +2497,7 @@ version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" dependencies = [ - "matchers 0.1.0", + "matchers", "nu-ansi-term", "once_cell", "regex", @@ -4790,34 +2509,6 @@ dependencies = [ "tracing-core", ] -[[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]] -name = "try-lock" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" - [[package]] name = "twox-hash" version = "1.6.3" @@ -4869,22 +2560,6 @@ 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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" -dependencies = [ - "crypto-common", - "subtle", -] - [[package]] name = "untrusted" version = "0.9.0" @@ -4916,309 +2591,18 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "valuable" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" - [[package]] name = "version_check" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" -[[package]] -name = "vsimd" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" - -[[package]] -name = "w3f-bls" -version = "0.1.3" -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", -] - -[[package]] -name = "walkdir" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" -dependencies = [ - "same-file", - "winapi-util", -] - -[[package]] -name = "want" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" -dependencies = [ - "try-lock", -] - [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" -[[package]] -name = "wasm-bindgen" -version = "0.2.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" -dependencies = [ - "cfg-if", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" -dependencies = [ - "bumpalo", - "log", - "once_cell", - "proc-macro2", - "quote", - "syn 2.0.58", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.58", - "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" -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", -] - -[[package]] -name = "wasmtime-jit-debug" -version = "8.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e0554b84c15a27d76281d06838aed94e13a77d7bf604bbbaf548aa20eb93846" -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", -] - -[[package]] -name = "wasmtime-types" -version = "8.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4f6fffd2a1011887d57f07654dd112791e872e3ff4a2e626aee8059ee17f06f" -dependencies = [ - "cranelift-entity", - "serde", - "thiserror", - "wasmparser", -] - [[package]] name = "winapi" version = "0.3.9" @@ -5235,39 +2619,12 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" -[[package]] -name = "winapi-util" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" -dependencies = [ - "winapi", -] - [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows-core" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" -dependencies = [ - "windows-targets 0.52.4", -] - -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", -] - [[package]] name = "windows-sys" version = "0.48.0" @@ -5286,21 +2643,6 @@ 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", -] - [[package]] name = "windows-targets" version = "0.48.5" @@ -5331,12 +2673,6 @@ dependencies = [ "windows_x86_64_msvc 0.52.4", ] -[[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" @@ -5349,12 +2685,6 @@ 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" @@ -5367,12 +2697,6 @@ 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" @@ -5385,12 +2709,6 @@ 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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -5403,12 +2721,6 @@ 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" @@ -5421,12 +2733,6 @@ 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" @@ -5439,12 +2745,6 @@ 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" @@ -5484,24 +2784,6 @@ 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" diff --git a/Cargo.toml b/Cargo.toml index 06bcfb7..4569110 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,24 +11,33 @@ keywords = ["substrate", "blockchain", "finance", "service", "middleware"] categories = ["finance"] [dependencies] -toml_edit = { version = "0.22", default-features = false, features = ["parse", "serde"] } 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 } -subxt = { version = "=0.35", features = ["substrate-compat", "unstable-reconnecting-rpc-client"] } tokio-util = { version = "0.7", features = ["rt"] } -tokio = { version = "1", features = ["signal"] } +tokio = { version = "1", features = ["full"] } -hex-simd = "0.8" -serde = "1" -anyhow = "1" +serde = { version = "1", features = ["derive"] } redb = "2" tracing = "0.1" scale-info = "2" axum-macros = "0.4.1" +primitive-types = { version = "0.12.2", features = ["codec"] } +substrate-crypto-light = { git = "https://github.com/Alzymologist/substrate-crypto-light" } +jsonrpsee = { version = "0.22.4", features = ["ws-client"] } +mnemonic-external = { path = "../mnemonic-external" }#{ git = "https://github.com/varovainen/mnemonic-external", branch = "va-2024-04-23-try-word-ref" } #"https://github.com/Alzymologist/mnemonic-external" } +thiserror = "1.0.58" +substrate_parser = { git = "https://github.com/Alzymologist/substrate-parser" } +substrate-constructor = "0.1.0" +frame-metadata = "16.0.0" +hex = "0.4.3" +parity-scale-codec = "3.6.9" +serde_json = "1.0.116" +sp-crypto-hashing = "0.1.0" +toml = "0.8.12" [profile.release] strip = true diff --git a/src/asset.rs b/src/asset.rs index 54fe210..809e857 100644 --- a/src/asset.rs +++ b/src/asset.rs @@ -1,18 +1,5 @@ use crate::{AssetId, PalletIndex}; use std::marker::PhantomData; -use subxt::ext::{ - codec::{Encode, Output}, - scale_decode::{ - self, - error::ErrorKind, - visitor::{ - types::{Composite, Tuple}, - TypeIdFor, - }, - DecodeAsType, IntoVisitor, TypeResolver, Visitor, - }, - scale_encode::{self, EncodeAsType}, -}; #[derive(Clone, Debug)] pub enum Asset { @@ -25,6 +12,7 @@ impl Asset { const MULTI_LOCATION: &'static str = "MultiLocation"; } +/* pub struct AssetVisitor(PhantomData); fn try_into_asset_id( @@ -191,9 +179,6 @@ impl Encode for Asset { } #[derive(EncodeAsType, DecodeAsType, Encode)] -#[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 { parents: u8, interior: Junctions, @@ -212,18 +197,12 @@ impl MultiLocation { } #[derive(EncodeAsType, DecodeAsType, Encode)] -#[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 { #[codec(index = 2)] X2(Junction, Junction), } #[derive(EncodeAsType, DecodeAsType, Encode)] -#[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 { #[codec(index = 4)] PalletInstance(PalletIndex), @@ -242,3 +221,4 @@ impl Junction { } } } +*/ diff --git a/src/callback.rs b/src/callback.rs index a2aff59..1c84a6c 100644 --- a/src/callback.rs +++ b/src/callback.rs @@ -3,9 +3,8 @@ use crate::{ CurrencyInfo, OrderInfo, OrderStatus, PaymentStatus, ServerInfo, TokenKind, WithdrawalStatus, }, - AccountId, Balance, + AccountId32, Balance, }; -use subxt::ext::sp_core::crypto::Ss58Codec; use tokio::task; pub const MODULE: &str = module_path!(); @@ -13,13 +12,14 @@ pub const MODULE: &str = module_path!(); pub async fn callback( path: String, order: String, - recipient: AccountId, + recipient: AccountId32, debug: bool, remark: String, amount: Balance, rpc_url: String, - paym_acc: AccountId, + paym_acc: AccountId32, ) { + /* let req = ureq::post(&path); task::spawn_blocking(move || { @@ -55,4 +55,5 @@ pub async fn callback( }) .await .unwrap(); + */ } diff --git a/src/chain.rs b/src/chain.rs new file mode 100644 index 0000000..67c706a --- /dev/null +++ b/src/chain.rs @@ -0,0 +1,357 @@ +//! Utils to process chain data + +//TransactionToFill::init(&mut (), metadata, genesis_hash).unwrap(); +use crate::error::ErrorChain; +use frame_metadata::{v14::StorageHasher, v15::{RuntimeMetadataV15, StorageEntryType}}; +use parity_scale_codec::{Decode, Encode}; +use scale_info::{TypeDef, TypeDefPrimitive}; +use serde_json::{Map, Number, Value}; +use sp_crypto_hashing::{blake2_128, blake2_256, twox_128, twox_256, twox_64}; +use substrate_parser::{AsMetadata, cards::{ExtendedData, ParsedData}, decode_all_as_type, ShortSpecs}; + +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) -> String { + 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(_) => panic!("expected map with single entry, got plain"), + StorageEntryType::Map { + hashers, + key: key_ty, + value: _, + } => { + if hashers.len() == 1 { + let hasher = &hashers[0]; + match metadata_v15.types.resolve(key_ty.id).unwrap().type_def { + TypeDef::Primitive(TypeDefPrimitive::U32) => return 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)) + ), + _ => panic!("wrong data type") + } + } + else {panic!("expected map with single entry, got multiple entries")} + } + } + } + } + panic!("have not found entry with proper name"); + } + } + } + panic!("have not found pallet"); +} + + +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(ErrorChain::DecimalsFormatNotSupported { + value: a.to_string(), + }), + }, + + // number could not be represented as `u64`, this is an error + None => Err(ErrorChain::DecimalsFormatNotSupported { + value: 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(ErrorChain::DecimalsFormatNotSupported { + value: a.to_string(), + }), + }, + + // number could not be represented as `u64`, this is + // an error + None => Err(ErrorChain::DecimalsFormatNotSupported { + value: a.to_string(), + }), + } + } else { + // element is not a number, this is an error + Err(ErrorChain::DecimalsFormatNotSupported { + value: a.to_string(), + }) + } + } else { + // decimals are an array with more than one element + Err(ErrorChain::DecimalsFormatNotSupported { + value: a.to_string(), + }) + } + } + + // unexpected decimals format + _ => Err(ErrorChain::DecimalsFormatNotSupported { + value: a.to_string(), + }), + }, + + // decimals are missing + None => Err(ErrorChain::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(ErrorChain::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(ErrorChain::Base58PrefixFormatNotSupported { + value: a.to_string(), + }) + } + }, + + // base58 prefix value could not be presented as `u64` number, + // this is an error + None => { + return Err(ErrorChain::Base58PrefixFormatNotSupported { + value: a.to_string(), + }) + } + }, + + // base58 prefix value is not a number, this is an error + _ => { + return Err(ErrorChain::Base58PrefixFormatNotSupported { + value: 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(ErrorChain::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(ErrorChain::UnitFormatNotSupported { + value: a.to_string(), + }) + } + } else { + // units are an array with more than one element + Err(ErrorChain::UnitFormatNotSupported { + value: a.to_string(), + }) + } + } + + // unexpected unit format + _ => Err(ErrorChain::UnitFormatNotSupported { + value: a.to_string(), + }), + }, + + // unit missing + None => Err(ErrorChain::NoUnit), + } +} + diff --git a/src/database.rs b/src/database.rs index fc10879..3a4a5a9 100644 --- a/src/database.rs +++ b/src/database.rs @@ -1,30 +1,20 @@ use crate::{ + error::ErrorDb, server::{CurrencyProperties, OrderQuery, OrderStatus, ServerInfo, ServerStatus}, - AccountId, AssetId, Balance, BlockNumber, Nonce, Timestamp, + AccountId32, AssetId, Balance, BlockNumber, Nonce, Timestamp, }; -use anyhow::{Context, Result}; +use parity_scale_codec::{Compact, Encode, Decode}; use redb::{ backends::{FileBackend, InMemoryBackend}, Database, ReadableTable, TableDefinition, TypeName, Value, }; use serde::Deserialize; use std::{collections::HashMap, fs::File, io::ErrorKind}; -use subxt::ext::{ - codec::{Compact, Decode, Encode}, - sp_core::{ - sr25519::{Pair}, - }, -}; +use substrate_crypto_light::sr25519::Pair; use tokio::sync::{oneshot}; pub const MODULE: &str = module_path!(); -#[derive(Debug)] -pub enum DbError { - CurrencyKeyNotFound, - DbEngineDown, -} - // Tables const ROOT: TableDefinition<'_, &str, &[u8]> = TableDefinition::new("root"); @@ -34,18 +24,18 @@ const INVOICES: TableDefinition<'_, InvoiceKey, Invoice> = TableDefinition::new( const ACCOUNTS: &str = "accounts"; -type ACCOUNTS_KEY = (Option, Account); -type ACCOUNTS_VALUE = InvoiceKey; +//type ACCOUNTS_KEY = (Option, Account); +//type ACCOUNTS_VALUE = InvoiceKey; const TRANSACTIONS: &str = "transactions"; -type TRANSACTIONS_KEY = BlockNumber; -type TRANSACTIONS_VALUE = (Account, Nonce, Transfer); +//type TRANSACTIONS_KEY = BlockNumber; +//type TRANSACTIONS_VALUE = (Account, Nonce, Transfer); const HIT_LIST: &str = "hit_list"; -type HIT_LIST_KEY = BlockNumber; -type HIT_LIST_VALUE = (Option, Account); +//type HIT_LIST_KEY = BlockNumber; +//type HIT_LIST_VALUE = (Option, Account); // `ROOT` keys @@ -63,17 +53,15 @@ type ChainHash = [u8; 32]; type PublicSlot = [u8; 32]; type BalanceSlot = u128; type Derivation = [u8; 32]; -type Account = [u8; 32]; +pub type Account = [u8; 32]; #[derive(Encode, Decode)] -#[codec(crate = subxt::ext::codec)] enum ChainKind { Id(Vec>), MultiLocation(Vec>), } #[derive(Encode, Decode)] -#[codec(crate = subxt::ext::codec)] struct DaemonInfo { chains: Vec<(String, ChainProperties)>, current_key: PublicSlot, @@ -81,7 +69,6 @@ struct DaemonInfo { } #[derive(Encode, Decode)] -#[codec(crate = subxt::ext::codec)] struct ChainProperties { genesis: BlockHash, hash: ChainHash, @@ -89,11 +76,9 @@ struct ChainProperties { } #[derive(Encode, Decode)] -#[codec(crate = subxt::ext::codec)] struct Transfer(Option>, #[codec(compact)] BalanceSlot); #[derive(Encode, Decode, Debug)] -#[codec(crate = subxt::ext::codec)] struct Invoice { derivation: (PublicSlot, Derivation), paid: bool, @@ -107,7 +92,6 @@ struct Invoice { } #[derive(Encode, Decode, Debug)] -#[codec(crate = subxt::ext::codec)] enum TransferTxs { Asset { #[codec(compact)] @@ -122,7 +106,6 @@ enum TransferTxs { } // #[derive(Encode, Decode, Debug)] -// #[codec(crate = subxt::ext::codec)] // struct TransferTxsAsset { // recipient: Account, // encoded: Vec, @@ -131,7 +114,6 @@ enum TransferTxs { // } #[derive(Encode, Decode, Debug)] -#[codec(crate = subxt::ext::codec)] struct TransferTx { recipient: Account, exact_amount: Option>, @@ -163,7 +145,7 @@ impl Value for Invoice { } pub struct ConfigWoChains { - pub recipient: AccountId, + pub recipient: AccountId32, pub debug: bool, pub remark: String, pub depth: Option, @@ -302,7 +284,7 @@ pub struct Invoicee { pub callback: String, pub amount: Balance, pub paid: bool, - pub paym_acc: AccountId, + pub paym_acc: Account, } impl State { @@ -319,7 +301,7 @@ impl State { account_lifetime, rpc, }: ConfigWoChains, - ) -> Result { + ) -> Result { let builder = Database::builder(); let is_new; @@ -330,14 +312,14 @@ impl State { Ok(file) => { is_new = true; - FileBackend::new(file).and_then(|backend| builder.create_with_backend(backend)) + FileBackend::new(file).and_then(|backend| builder.create_with_backend(backend)).map_err(ErrorDb::DbStartError)? } Err(error) if error.kind() == ErrorKind::AlreadyExists => { is_new = false; - builder.create(path) + builder.create(path).map_err(ErrorDb::DbStartError)? } - Err(error) => Err(error.into()) + Err(error) => return Err(error.into()) } } else { tracing::warn!( @@ -346,8 +328,8 @@ impl State { is_new = true; - builder.create_with_backend(InMemoryBackend::new()) - }.context("failed to create/open the database")?; + builder.create_with_backend(InMemoryBackend::new()).map_err(ErrorDb::DbStartError)? + };//.context("failed to create/open the database")?; /* currencies: HashMap, @@ -384,7 +366,7 @@ impl State { Ok(Self { tx }) } - pub async fn order_status(&self, order: &str) -> Result { + pub async fn order_status(&self, order: &str) -> Result { let (res, rx) = oneshot::channel(); self.tx .send(StateAccessRequest::GetInvoiceStatus(GetInvoiceStatus { @@ -392,16 +374,16 @@ impl State { res, })) .await; - rx.await.map_err(|_| DbError::DbEngineDown) + rx.await.map_err(|_| ErrorDb::DbEngineDown) } - pub async fn server_status(&self) -> Result { + pub async fn server_status(&self) -> Result { let (res, rx) = oneshot::channel(); self.tx.send(StateAccessRequest::ServerStatus(res)).await; - rx.await.map_err(|_| DbError::DbEngineDown) + rx.await.map_err(|_| ErrorDb::DbEngineDown) } - pub async fn create_order(&self, order_query: OrderQuery) -> Result { + pub async fn create_order(&self, order_query: OrderQuery) -> Result { let (res, rx) = oneshot::channel(); /* Invoicee { @@ -417,7 +399,7 @@ impl State { res, })) .await; - rx.await.map_err(|_| DbError::DbEngineDown) + rx.await.map_err(|_| ErrorDb::DbEngineDown) } pub fn interface(&self) -> Self { @@ -436,13 +418,13 @@ impl State { } */ /* - pub fn currency_properties(&self, currency_name: &str) -> Result<&CurrencyProperties, DbError> { + pub fn currency_properties(&self, currency_name: &str) -> Result<&CurrencyProperties, ErrorDb> { self.currencies .get(currency_name) - .ok_or(DbError::CurrencyKeyNotFound) + .ok_or(ErrorDb::CurrencyKeyNotFound) } - pub fn currency_info(&self, currency_name: &str) -> Result { + pub fn currency_info(&self, currency_name: &str) -> Result { let currency = self.currency_properties(currency_name)?; Ok(CurrencyInfo { currency: currency_name.to_string(), diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..aa9bf1d --- /dev/null +++ b/src/error.rs @@ -0,0 +1,307 @@ +use crate::{ + server::OrderStatus, + SocketAddr, +}; +use frame_metadata::v15::RuntimeMetadataV15; +use jsonrpsee::core::client::error::Error as ClientError; +use mnemonic_external::error::ErrorWordList; +use primitive_types::H256; +use redb::DatabaseError; +use serde_json::Value; +use substrate_parser::error::*; +use tokio::task::JoinError; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("failed to read `{0}`")] + Env(String), + + #[error("failed to read a config file at {0:?}")] + ConfigFileRead(String), + + #[error("failed to parse the config at {0:?}")] + ConfigFileParse(String), + + #[error("failed to parse the config parameter {0}")] + ConfigParse(String), + + #[error("RPC server error: {0:?}")] + ErrorChain(ErrorChain), + + #[error("Database error: {0:?}")] + ErrorDb(ErrorDb), + + #[error("Daemon server error: {0:?}")] + ErrorServer(ErrorServer), + + #[error("Failed to listen to shutdown signal")] + ShutdownSignal, + + #[error("Receiver account could not be parsed: {0:?}")] + RecipientAccount(substrate_crypto_light::error::Error), + + #[error("Seed phrase invalid: {0:?}")] + InvalidSeed(ErrorWordList), +} + +impl From for Error { + fn from(e: ErrorDb) -> Error { + Error::ErrorDb(e) + } +} + +impl From for Error { + fn from(e: ErrorChain) -> Error { + Error::ErrorChain(e) + } +} + +impl From for Error { + fn from(e: ErrorServer) -> Error { + Error::ErrorServer(e) + } +} + +impl From for Error { + fn from(e: ErrorWordList) -> Self { + Error::InvalidSeed(e) + } +} + +#[derive(Debug, thiserror::Error)] +pub enum ErrorChain { + #[error("Format of fetched base58 prefix {value} is not supported.")] + Base58PrefixFormatNotSupported { value: String }, + + #[error("Base58 prefixes in metadata {meta} and specs {specs} do not match.")] + Base58PrefixMismatch { specs: u16, meta: u16 }, + + #[error("Unexpected block hash format.")] + BlockHashFormat, + + #[error("Unexpected block hash length.")] + BlockHashLength, + + #[error("Ws client error. {0}")] + Client(ClientError), + + #[error("Threading error. {0}")] + Tokio(JoinError), + +// #[error("Internal database error. {0}")] +// DbInternal(sled::Error), + +// #[error("Database error recording transaction. {0}")] +// DbTransaction(sled::transaction::TransactionError), + + #[error("Format of fetched decimals {value} is not supported.")] + DecimalsFormatNotSupported { value: String }, + + #[error("Fetch address in the database for genesis hash {} got damaged, and could not be decoded.", hex::encode(.0))] + DecodeDbAddress(H256), + + //#[error("Key in the database {} is damaged, and could not be decoded.", hex::encode(.0))] + //DecodeDbKey(IVec), + + #[error("MetadataSpecs in the database for genesis hash {} got damaged, and could not be decoded.", hex::encode(.0))] + DecodeDbMetadataSpecs(H256), + + #[error("Unexpected genesis hash format.")] + GenesisHashFormat, + + #[error("Unexpected genesis hash length.")] + GenesisHashLength, + + #[error("Key {0} got damaged on the interface.")] + InterfaceKey(String), + + #[error( + "No metadata and specs for chain with genesis hash {} in the database.", + hex::encode(genesis_hash) + )] + LoadSpecsMetadata { genesis_hash: H256 }, + + #[error("Address for chain with genesis hash {} is not in the database.", hex::encode(.0))] + LostAddress(H256), + + #[error("Metadata fetch was somehow done with no pre-existing entry for genesis hash {}. This is a bug, please report it.", hex::encode(.0))] + MetadataFetchWithoutExistingEntry(H256), + + #[error("...")] + MetadataFormat, + + #[error("...")] + MetadataNotDecodeable, + + #[error("{0}")] + MetadataVersion(MetaVersionErrorPallets), + + #[error("No base58 prefix is fetched as system properties or found in the metadata.")] + NoBase58Prefix, + + #[error("No decimals value is fetched.")] + NoDecimals, + + #[error("No existing metadata and specs entry before metadata update for hash {}. Remove entry and start over.", hex::encode(.0))] + NoExistingEntryMetadataUpdate(H256), + + #[error("Metadata v15 not available through rpc.")] + NoMetadataV15, + + #[error("Metadata must start with `meta` prefix.")] + NoMetaPrefix, + + #[error("{0}")] + NotHex(NotHex), + + #[error("Fetched values were not sent through successfully.")] + NotSent, + + #[error("Received QR payload is not a Substrate one.")] + NotSubstrate, + + #[error("No unit value is fetched.")] + NoUnit, + + #[error("Only Sr25519 encryption, 0x01, is supported. Received transaction has encoded encryption 0x{}", hex::encode([*.0]))] + OnlySr25519(u8), + + #[error("...")] + PoisonedLockSelector, + + #[error("...")] + PropertiesFormat, + + #[error("...")] + RawMetadataNotDecodeable, + + #[error("Can't read data through the interface. Receiver closed.")] + ReceiverClosed, + + #[error("Can't read data through the interface. Receiver guard is poisoned.")] + ReceiverGuardPoisoned, + + #[error("Received QR payload is too short.")] + TooShort, + + #[error("Received transaction could not be parsed. {0}.")] + TransactionNotParsable(SignableError<(), RuntimeMetadataV15>), + + #[error("Unexpected payload type, 0x{}", hex::encode([*.0]))] + UnknownPayloadType(u8), + + #[error("Format of fetched unit {value} is not supported.")] + UnitFormatNotSupported { value: String }, + + #[error("Try updating metadata. Metadata version in transaction {as_decoded} does not match the version of the available metadata entry {in_metadata}.")] + UpdateMetadata { + as_decoded: String, + in_metadata: String, + }, + + #[error("Unexpected storage value format for key {0}")] + StorageValueFormat(String), + + #[error("Chain returned zero for block time")] + ZeroBlockTime, + + #[error("chain doesn't have any `endpoints` in the config")] + EmptyEndpoints, + + #[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, +} + +impl From for ErrorChain { + fn from(e: ClientError) -> Self { + ErrorChain::Client(e) + } +} + +impl From for ErrorChain { + fn from(e: JoinError) -> Self { + ErrorChain::Tokio(e) + } +} + +#[derive(Debug, thiserror::Error)] +pub enum ErrorDb { + #[error("Currency key is not found")] + CurrencyKeyNotFound, + + #[error("Database engine is not running")] + DbEngineDown, + + #[error("Database internal error: {0:?}")] + DbInternalError(DatabaseError), + + #[error("Could not start database service: {0:?}")] + DbStartError(DatabaseError), + + #[error("Operating system related I/O error {0:?}")] + IoError(std::io::Error), +} + +impl From for ErrorDb { + fn from(e: std::io::Error) -> Self { + ErrorDb::IoError(e) + } +} + +#[derive(Debug, thiserror::Error)] +pub enum ErrorOrder { + #[error("Invoice amount is less than existential deposit")] + LessThanExistentialDeposit(f64), + + #[error("Unknown currency")] + UnknownCurrency, + + #[error("Order parameter missing: {0}")] + MissingParameter(String), + + #[error("Order parameter invalid: {0}")] + InvalidParameter(String), + + #[error("Order already processed: {0:?}")] + AlreadyProcessed(Box), + + #[error("Internal error")] + InternalError, +} + +#[derive(Debug, thiserror::Error)] +pub enum ErrorServer { + #[error("failed to bind the TCP listener to {0:?}")] + TcpListenerBind(SocketAddr), + + #[error("Internal threading error")] + ThreadError, +} + +#[derive(Debug, thiserror::Error)] +pub enum ErrorUtil { + #[error("{0}")] + NotHex(NotHex), +} + +#[derive(Debug, Eq, PartialEq, thiserror::Error)] +pub enum NotHex { + #[error("Block hash string is not a valid hexadecimal.")] + BlockHash, + + #[error("Genesis hash string is not a valid hexadecimal.")] + GenesisHash, + + #[error("Encoded metadata string is not a valid hexadecimal.")] + Metadata, + + #[error("Encoded storage value string is not a valid hexadecimal.")] + StorageValue, +} + diff --git a/src/main.rs b/src/main.rs index d09a229..9d5b005 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,6 @@ -use anyhow::{Context, Error, Result}; +use mnemonic_external::{regular::InternalWordList, WordSet}; +use substrate_crypto_light::{common::cut_path, sr25519::Pair}; + use serde::Deserialize; use std::{ borrow::Cow, @@ -11,32 +13,26 @@ use std::{ ops::Deref, panic, str, }; -use subxt::{ - config::{substrate::SubstrateHeader, PolkadotExtrinsicParams}, - ext::sp_core::{ - crypto::{AccountId32, Ss58Codec}, - sr25519::{Pair}, - Pair as _, - }, - PolkadotConfig, -}; +use substrate_crypto_light::common::{AccountId32, AsBase58}; use tokio::{ signal, sync::mpsc::{self, UnboundedReceiver, UnboundedSender}, task::JoinHandle, }; use tokio_util::{sync::CancellationToken, task}; -use toml_edit::de; use tracing_subscriber::{fmt::time::UtcTime, EnvFilter}; mod asset; mod callback; +mod chain; mod database; +mod error; mod rpc; mod server; +mod utils; -use asset::Asset; use database::{ConfigWoChains, State}; +use error::Error; use rpc::Processor; const CONFIG: &str = "KALATORI_CONFIG"; @@ -61,44 +57,25 @@ type Nonce = u32; type Timestamp = u64; type PalletIndex = u8; -type BlockHash = ::Hash; -type AccountId = ::AccountId; -type OnlineClient = subxt::OnlineClient; - -struct RuntimeConfig; - -impl subxt::Config for RuntimeConfig { - type Hash = ::Hash; - type AccountId = AccountId32; - type Address = ::Address; - type Signature = ::Signature; - type Hasher = ::Hasher; - type Header = SubstrateHeader; - type ExtrinsicParams = PolkadotExtrinsicParams; - type AssetId = Asset; -} +type BlockHash = primitive_types::H256; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Error> { let shutdown_notification = CancellationToken::new(); set_panic_hook(shutdown_notification.clone()); initialize_logger()?; let (pair, old_pairs) = parse_seeds()?; - let recipient = env::var(RECIPIENT).with_context(|| format!("failed to read {RECIPIENT}"))?; + let recipient = env::var(RECIPIENT).map_err(|_| Error::Env(RECIPIENT.to_string()))?; - let remark = match env::var(REMARK) { - Ok(remark) => remark, - Err(error) => Err(error).with_context(|| format!("failed to read {REMARK}"))?, - }; + let remark = env::var(REMARK).map_err(|_| Error::Env(REMARK.to_string()))?; - let config = Config::parse()?; + let config = Config::load()?; let host = if let Some(unparsed_host) = config.host { unparsed_host - .parse() - .context("failed to convert `host` from the config to a socket address")? + .parse().map_err(|_| Error::ConfigParse("host to define a socket address".to_string()))? } else { DEFAULT_SOCKET }; @@ -145,13 +122,12 @@ async fn main() -> Result<()> { shutdown_listener(shutdown_notification.clone()), ); - let (_chains, currencies) = rpc::prepare(config.chain, config.account_lifetime, config.depth) - .await - .context("failed while preparing the RPC module")?; + //let (chains, currencies) = rpc::prepare(config.chain, config.account_lifetime, config.depth).await + let currencies = HashMap::new(); let rpc = env::var("KALATORI_RPC").unwrap(); - let recipient = AccountId::from_string(&recipient)?; + let recipient = AccountId32::from_base58_string(&recipient).map_err(Error::RecipientAccount)?.0; let state = State::initialise( database_path, @@ -166,8 +142,7 @@ async fn main() -> Result<()> { account_lifetime: config.account_lifetime, rpc: rpc.clone(), }, - ) - .context("failed to initialise the database module")?; + )?; task_tracker.spawn( "proc", @@ -180,8 +155,7 @@ async fn main() -> Result<()> { ); let server = server::new(shutdown_notification.clone(), host, state) - .await - .context("failed to initialise the server module")?; + .await?; // task_tracker.spawn(shutdown( // processor.ignite(last_saved_block, task_tracker.clone(), error_tx.clone()), @@ -224,7 +198,7 @@ fn set_panic_hook(shutdown_notification: CancellationToken) { })); } -fn initialize_logger() -> Result<()> { +fn initialize_logger() -> Result<(), Error> { let filter = match EnvFilter::try_from_env(LOG) { Err(error) => { let Some(VarError::NotPresent) = error @@ -232,7 +206,7 @@ fn initialize_logger() -> Result<()> { .expect("should always be `Some`") .downcast_ref() else { - return Err(error).with_context(|| format!("failed to parse `{LOG}`")); + return Err(Error::Env(LOG.to_string())); }; if cfg!(debug_assertions) { @@ -290,15 +264,13 @@ fn default_filter() -> String { filter } -fn parse_seeds() -> Result<(Pair, HashMap)> { - let pair = Pair::from_string( - &env::var(SEED).with_context(|| format!("failed to read `{SEED}`"))?, - None, - ) - .with_context(|| format!("failed to generate a key pair from `{SEED}`"))?; +fn parse_seeds() -> Result<(Pair, HashMap), Error> { + let pair = seed_from_phrase( + &env::var(SEED).map_err(|_| Error::Env(SEED.to_string()))?, + )?; let mut old_pairs = HashMap::new(); - +/* TODO: add this at least when you do something about these for (raw_key, raw_value) in env::vars_os() { let raw_key_bytes = raw_key.as_encoded_bytes(); @@ -308,16 +280,27 @@ fn parse_seeds() -> Result<(Pair, HashMap)> { let value = raw_value .to_str() .with_context(|| format!("failed to read a seed phrase from `{OLD_SEED}{key}`"))?; - let old_pair = Pair::from_string(value, None) - .with_context(|| format!("failed to generate a key pair from `{OLD_SEED}{key}`"))?; + let old_pair = seed_from_phrase(value)?; old_pairs.insert(key.to_owned(), old_pair); } } - +*/ Ok((pair, old_pairs)) } + pub fn seed_from_phrase(seed: &str) -> Result { + let mut word_set = WordSet::new(); + for word in seed.split(' ') { + word_set + .add_word(&word, &InternalWordList)?; + } + let entropy = word_set.to_entropy()?; + let derivation = cut_path("").expect("empty derivation is hardcoded"); + Ok(Pair::from_entropy_and_full_derivation(&entropy, derivation).expect("empty derivation and password are hardcoded")) + } + + #[derive(Clone)] struct TaskTracker { inner: task::TaskTracker, @@ -337,7 +320,7 @@ impl TaskTracker { fn spawn( &self, name: impl Into> + Send + 'static, - task: impl Future>> + Send + 'static, + task: impl Future, Error>> + Send + 'static, ) -> JoinHandle<()> { let error_tx = self.error_tx.clone(); @@ -375,11 +358,11 @@ impl TaskTracker { async fn try_wait( self, mut error_rx: UnboundedReceiver<(Cow<'static, str>, Error)>, - ) -> Result<()> { + ) -> Result<(), Error> { drop(self.error_tx); if let Some((from, error)) = error_rx.recv().await { - return Err(error).with_context(|| format!("received a fatal error from {from}")); + return Err(error)?; } self.inner.wait().await; @@ -388,11 +371,11 @@ impl TaskTracker { } } -async fn shutdown_listener(shutdown_notification: CancellationToken) -> Result> { +async fn shutdown_listener(shutdown_notification: CancellationToken) -> Result, Error> { tokio::select! { biased; signal = signal::ctrl_c() => { - signal.context("failed to listen for the shutdown signal")?; + signal.map_err(|_| Error::ShutdownSignal)?; // Print shutdown log messages on the next line after the Control-C command. println!(); @@ -420,10 +403,10 @@ struct Config { } impl Config { - fn parse() -> Result { + fn load() -> Result { let config_path = env::var(CONFIG).or_else(|error| match error { VarError::NotUnicode(_) => { - Err(error).with_context(|| format!("failed to read `{CONFIG}`")) + Err(Error::Env(CONFIG.to_string())) } VarError::NotPresent => { tracing::debug!( @@ -433,11 +416,9 @@ impl Config { Ok(DEFAULT_CONFIG.into()) } })?; - let unparsed_config = fs::read_to_string(&config_path) - .with_context(|| format!("failed to read a config file at {config_path:?}"))?; + let unparsed_config = fs::read_to_string(&config_path).map_err(|_| Error::ConfigFileRead(config_path.clone()))?; - de::from_str(&unparsed_config) - .with_context(|| format!("failed to parse the config at {config_path:?}")) + toml::from_str(&unparsed_config).map_err(|_| Error::ConfigFileParse(config_path)) } } diff --git a/src/rpc.rs b/src/rpc.rs index 361a0c2..d8a75b9 100644 --- a/src/rpc.rs +++ b/src/rpc.rs @@ -1,51 +1,31 @@ use crate::{ - asset::Asset, + chain::{base58prefix, pallet_index, storage_key, unit}, database::{Invoicee, State}, + error::{Error, ErrorChain, NotHex}, server::{ CurrencyProperties, }, - AccountId, AssetId, AssetInfo, Balance, BlockHash, BlockNumber, Chain, Decimals, NativeToken, - Nonce, OnlineClient, PalletIndex, RuntimeConfig, TaskTracker, Timestamp, + utils::unhex, + AccountId32, AssetId, AssetInfo, Balance, BlockHash, BlockNumber, Chain, Decimals, NativeToken, + Nonce, PalletIndex, TaskTracker, Timestamp, }; -use anyhow::{Context, Result}; +use frame_metadata::{v15::RuntimeMetadataV15, RuntimeMetadata}; +use jsonrpsee::core::client::ClientT; +use jsonrpsee::rpc_params; +use jsonrpsee::ws_client::{WsClient, WsClientBuilder}; +use primitive_types::H256; use scale_info::TypeDef; +use parity_scale_codec::DecodeAll; use serde::{Deserialize, Deserializer}; +use serde_json::{Map, Number, Value}; use std::{ borrow::Cow, collections::{hash_map::Entry, HashMap}, - error::Error, - fmt::{self, Debug, Display, Formatter}, + fmt::Debug, num::NonZeroU64, - sync::Arc, -}; -use subxt::{ - backend::{ - legacy::{LegacyBackend, LegacyRpcMethods}, - rpc::{reconnecting_rpc_client::Client, RpcClient, RpcSubscription}, - Backend, BackendExt, RuntimeVersion, - }, - config::{DefaultExtrinsicParamsBuilder, Header}, - constants::ConstantsClient, - dynamic::{self, At, Value}, - error::RpcError, - ext::{ - futures::TryFutureExt, - scale_decode::DecodeAsType, - scale_value, - sp_core::{ - crypto::{Ss58AddressFormat}, - sr25519::Pair, - }, - }, - runtime_api::RuntimeApiClient, - storage::{Storage, StorageClient}, - tx::{PairSigner, SubmittableExtrinsic}, - Config, Metadata, -}; -use tokio::sync::{ - mpsc::{self, UnboundedSender}, - oneshot::{self, Sender}, }; +use substrate_parser::{AsMetadata, cards::{ExtendedData, ParsedData}, decode_all_as_type, ShortSpecs}; +use tokio::sync::{mpsc, oneshot}; use tokio_util::sync::CancellationToken; pub const MODULE: &str = module_path!(); @@ -67,6 +47,7 @@ const BABE: &str = "Babe"; const AURA: &str = "AuraApi"; +/* type ConnectedChainsChannel = ( Sender>, (String, ConnectedChain), @@ -79,10 +60,10 @@ type CurrenciesChannel = ( struct AssetsInfoFetcher<'a> { assets: (AssetInfo, Vec), - storage: &'a Storage, pallet_index: Option, } - +*/ +/* async fn fetch_finalized_head_number_and_hash( methods: &LegacyRpcMethods, ) -> Result<(BlockNumber, BlockHash)> { @@ -98,57 +79,12 @@ async fn fetch_finalized_head_number_and_hash( Ok((head.block.header.number, head_hash)) } - -async fn fetch_runtime( - methods: &LegacyRpcMethods, - backend: &impl Backend, - at: BlockHash, -) -> Result<(Metadata, RuntimeVersion)> { - Ok(( - fetch_metadata(backend, at) - .await - .context("failed to fetch metadata")?, - methods - .state_get_runtime_version(Some(at)) - .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: BlockHash) -> 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:?}")) -} +*/ #[derive(Debug)] struct ChainProperties { - address_format: Ss58AddressFormat, + specs: ShortSpecs, + metadata: RuntimeMetadataV15, existential_deposit: Option, assets_pallet: Option, block_hash_count: BlockNumber, @@ -167,29 +103,28 @@ struct AssetProperties { min_balance: Balance, decimals: Decimals, } - +/* impl AssetProperties { - async fn fetch(storage: &Storage, asset: AssetId) -> Result { + async fn fetch(asset: AssetId) -> Result { Ok(Self { - min_balance: check_sufficiency_and_fetch_min_balance(storage, asset).await?, - decimals: fetch_asset_decimals(storage, asset).await?, + min_balance: check_sufficiency_and_fetch_min_balance(asset).await?, + decimals: fetch_asset_decimals(asset).await?, }) } -} - +}*/ +/* impl ChainProperties { async fn fetch( chain: &str, - _currencies: UnboundedSender, - constants: &ConstantsClient, + currencies: Sender, native_token_option: Option, assets_fetcher: Option>, account_lifetime: BlockNumber, depth: Option, - ) -> Result { - const ADDRESS_PREFIX: (&str, &str) = (SYSTEM, "SS58Prefix"); - const EXISTENTIAL_DEPOSIT: (&str, &str) = (BALANCES, "ExistentialDeposit"); - const BLOCK_HASH_COUNT: (&str, &str) = (SYSTEM, "BlockHashCount"); + ) -> Result { + //const ADDRESS_PREFIX: (&str, &str) = (SYSTEM, "SS58Prefix"); + const EXISTENTIAL_DEPOSIT: "ExistentialDeposit";//(&str, &str) = (BALANCES, "ExistentialDeposit"); + const BLOCK_HASH_COUNT: "BlockHashCount";//(&str, &str) = (SYSTEM, "BlockHashCount"); /* wtf is this? let try_add_currency = |name, asset| async move { @@ -220,6 +155,8 @@ impl ChainProperties { } };*/ + let specs = specs(); + let assets_pallet = if let Some(AssetsInfoFetcher { assets: (last_asset_info, assets_info), storage, @@ -230,7 +167,6 @@ impl ChainProperties { assets: &mut HashMap, id: AssetId, chain: &str, - storage: &Storage, ) -> Result<()> { match assets.entry(id) { Entry::Occupied(_) => Err(anyhow::anyhow!( @@ -262,19 +198,18 @@ impl ChainProperties { None }; - let address_format = Ss58AddressFormat::custom(fetch_constant(constants, ADDRESS_PREFIX)?); - let block_hash_count = fetch_constant(constants, BLOCK_HASH_COUNT)?; + let block_hash_count = fetch_constant(constants, SYSTEM, BLOCK_HASH_COUNT)?; //Some(native_token.decimals), let existential_deposit = if let Some(_native_token) = native_token_option { - Some(fetch_constant(constants, EXISTENTIAL_DEPOSIT).map(Balance)?) + Some(fetch_constant(metadata, BALANCES, ).map(Balance)?) } else { None }; let chain = Self { - address_format, + specs, existential_deposit, assets_pallet, block_hash_count, @@ -284,17 +219,19 @@ impl ChainProperties { Ok(chain) } -} - +}*/ +/* async fn check_sufficiency_and_fetch_min_balance( - storage: &Storage, + client: &WsClient, asset: AssetId, -) -> Result { + block: BlockHash, +) -> Result { const ASSET: &str = "Asset"; const MIN_BALANCE: &str = "min_balance"; const IS_SUFFICIENT: &str = "is_sufficient"; - let asset_info = storage + let asset_info = storage_fetch(client, ASSETS, ASSET, block); + /* .fetch(&dynamic::storage(ASSETS, ASSET, vec![asset.into()])) .await .with_context(|| format!("failed to fetch asset {asset} info from a chain"))? @@ -303,6 +240,7 @@ async fn check_sufficiency_and_fetch_min_balance( })? .to_value() .with_context(|| format!("failed to decode asset {asset} info"))?; + */ let encoded_is_sufficient = asset_info .at(IS_SUFFICIENT) @@ -327,16 +265,39 @@ async fn check_sufficiency_and_fetch_min_balance( encoded_min_balance.value ) }) +}*/ + +pub async fn storage_fetch(client: &WsClient, prefix: &str, storage_name: &str, block: &BlockHash) -> Result { + let key = storage_key(prefix, storage_name); + value_by_key_from_storage(client, &key, &hex::encode(block)).await +} + +pub async fn value_by_key_from_storage( + client: &WsClient, + whole_key: &str, + block_hash: &str, +) -> Result { + let storage_value = client + .request("state_getStorage", rpc_params![whole_key, block_hash]) + .await?; + if let Value::String(a) = storage_value { + Ok(a) + } else { + Err(ErrorChain::StorageValueFormat(whole_key.to_string())) + } } +/* async fn fetch_asset_decimals( - storage: &Storage, + client: &WsClient, asset: AssetId, -) -> Result { + block: &BlockHash, +) -> Result { const METADATA: &str = "Metadata"; const DECIMALS: &str = "decimals"; - let asset_metadata = storage + let asset_metadata = storage_fetch(client, ASSETS, METADATA, block); + /*storage .fetch(&dynamic::storage(ASSETS, METADATA, vec![asset.into()])) .await .with_context(|| format!("failed to fetch asset {asset} metadata from a chain"))? @@ -344,7 +305,7 @@ async fn fetch_asset_decimals( format!("received nothing after fetching asset {asset} metadata from a chain") })? .to_value() - .with_context(|| format!("failed to decode asset {asset} metadata"))?; + .with_context(|| format!("failed to decode asset {asset} metadata"))?;*/ let encoded_decimals = asset_metadata .at(DECIMALS) .with_context(|| format!("{DECIMALS} field wasn't found in asset {asset} metadata"))?; @@ -359,8 +320,8 @@ async fn fetch_asset_decimals( decimals.try_into().with_context(|| { format!("asset {asset} {DECIMALS:?} must be less than `u8`, got {decimals}") }) -} - +}*/ +/* pub async fn prepare( chains: Vec, account_lifetime: Timestamp, @@ -368,7 +329,7 @@ pub async fn prepare( ) -> Result<( HashMap, HashMap, -)> { +), ErrorChain> { let mut connected_chains = HashMap::with_capacity(chains.len()); let mut currencies = HashMap::with_capacity( chains @@ -385,8 +346,8 @@ pub async fn prepare( ); let (connected_chains_tx, mut connected_chains_rx) = - mpsc::unbounded_channel::(); - let (currencies_tx, mut currencies_rx) = mpsc::unbounded_channel::(); + mpsc::channel::(1024); + let (currencies_tx, mut currencies_rx) = mpsc::channel::(1024); let connected_chains_jh = tokio::spawn(async move { while let Some((tx, (name, chain))) = connected_chains_rx.recv().await { @@ -450,54 +411,158 @@ pub async fn prepare( Ok((connected_chains_jh.await?, currencies_jh.await?)) } +/// fetch genesis hash, must be a hexadecimal string transformable into +/// H256 format +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(ErrorChain::Client)?; + match genesis_hash_request { + Value::String(x) => { + let genesis_hash_raw = unhex(&x, NotHex::GenesisHash)?; + Ok(H256( + genesis_hash_raw + .try_into() + .map_err(|_| ErrorChain::GenesisHashLength)?, + )) + }, + _ => return Err(ErrorChain::GenesisHashFormat), + } + +} + +/// fetch current block hash, to request later the metadata and specs for +/// the same block +async fn block_hash(client: &WsClient) -> Result { + let block_hash_request: Value = client + .request("chain_getBlockHash", rpc_params![]) + .await + .map_err(ErrorChain::Client)?; + match block_hash_request { + Value::String(x) => { + let block_hash_raw = unhex(&x, NotHex::BlockHash)?; + Ok(H256( + block_hash_raw + .try_into() + .map_err(|_| ErrorChain::BlockHashLength)?, + )) + } + _ => return Err(ErrorChain::BlockHashFormat), + } +} + +/// fetch metadata at known block +async fn metadata(client: &WsClient, block: &BlockHash) -> Result { + let block_hash_string = block.to_string(); + let metadata_request: Value = client + .request( + "state_call", + rpc_params![ + "Metadata_metadata_at_version", + "0f000000", + &block_hash_string + ], + ) + .await + .map_err(ErrorChain::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(|_| ErrorChain::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(ErrorChain::NoMetadataV15), + Err(_) => return Err(ErrorChain::MetadataNotDecodeable), + } + } else { + return Err(ErrorChain::NoMetaPrefix); + } + } else { + return Err(ErrorChain::NoMetadataV15); + } + } + _ => return Err(ErrorChain::MetadataFormat), + }; +} + +// fetch specs at known block +async fn specs(client: &WsClient, metadata: &RuntimeMetadataV15, block_hash: &BlockHash) -> Result { + let specs_request: Value = client + .request("system_properties", rpc_params![hex::encode(&block_hash.0)]) + .await?; + //.map_err(ErrorChain::Client)?; + match specs_request { + Value::Object(properties) => system_properties_to_short_specs(&properties, &metadata), + _ => return Err(ErrorChain::PropertiesFormat), + } +} + + +async fn state_call(client: &WsClient, params: RpcParams) -> Result { + let res = client + .request( + "state_call", + params, + ) + .await + .map_err(ErrorChain::Client)? + if let Value::String(a) = res { Ok(a) } else { Err(ErrorChain::StateCallResponse(res)) } +} + + + #[tracing::instrument(skip_all, fields(chain = chain.name))] async fn prepare_chain( chain: Chain, - connected_chains: UnboundedSender, - currencies: UnboundedSender, + connected_chains: Sender, + currencies: Sender, account_lifetime: Timestamp, depth_option: Option, -) -> Result> { +) -> Result, ErrorChain> { let chain_name = chain.name; let endpoint = chain .endpoints - .first() - .context("chain doesn't have any `endpoints` in the config")?; - let rpc_client = RpcClient::new( - Client::builder() - .build(endpoint.into()) + .first().ok_or(ErrorChain::EmptyEndpoints)?; + let client = WsClientBuilder::default() + .build(&endpoint) .await - .context("failed to construct the RPC client")?, - ); - - let methods = LegacyRpcMethods::new(rpc_client.clone()); - let backend = Arc::new(LegacyBackend::builder().build(rpc_client.clone())); - - let genesis = methods - .genesis_hash() - .await - .context("failed to fetch the genesis hash")?; - let (_finalized_number, finalized_hash) = fetch_finalized_head_number_and_hash(&methods).await?; - let (metadata, runtime_version) = fetch_runtime(&methods, &*backend, finalized_hash).await?; - - let client = OnlineClient::from_backend_with( - genesis, - runtime_version, - metadata.clone(), - backend.clone(), - ) - .context("failed to construct the API client")?; - let constants = client.constants(); - - let (block_time, runtime_api) = if metadata.pallet_by_name(BABE).is_some() { - const EXPECTED_BLOCK_TIME: (&str, &str) = (BABE, "ExpectedBlockTime"); - - (fetch_constant(&constants, EXPECTED_BLOCK_TIME)?, None) + .map_err(ErrorChain::Client)?; + + let genesis = genesis_hash(&client).await?; + + let finalized_hash = block_hash(&client).await?; + let block_hash_string = finalized_hash.to_string(); + let metadata = metadata(&client, &finalized_hash).await?; + let specs = specs(&client, &metadata, &finalized_hash).await?; + + //TODO: we don't have to require this mechanism at all + let block_time = if pallet_index(&metadata, BABE).is_some() { + match fetch_constant(&metadata, BABE, "ExpectedBlockTime").ok_or(ErrorChain::BabeExpectedBlockTime)?.data { + + } } else { - const SLOT_DURATION: &str = "slot_duration"; + //const SLOT_DURATION: &str = "slot_duration"; - let runtime_api = client.runtime_api(); + state_call( + &client, + rpc_params![ + "Aura_slot_duration", + &block_hash_string + ], + ) + .await? + .parse::() + .map_err(|_| ErrorChain::AuraSlotDurationFormat)? + /* ( runtime_api .at(finalized_hash) @@ -511,18 +576,19 @@ async fn prepare_chain( .as_type() .context("failed to decode Aura's slot duration")?, Some(runtime_api), - ) + )*/ }; let block_time_non_zero = - NonZeroU64::new(block_time).context("block interval can't equal 0")?; + NonZeroU64::new(block_time).ok_or(ErrorChain::ZeroBlockTime)?;//.context("block interval can't equal 0")?; let account_lifetime_in_blocks = account_lifetime / block_time_non_zero; - +/* TODO if account_lifetime_in_blocks == 0 { anyhow::bail!("block interval is longer than the given `account-lifetime`"); } - +*/ +/* let depth_in_blocks = if let Some(depth) = depth_option { let depth_in_blocks = depth / block_time_non_zero; @@ -537,12 +603,9 @@ async fn prepare_chain( } else { None }; - +*/ let rpc = endpoint.into(); - - let storage_client = client.storage(); - let storage = storage_client.at(finalized_hash); - +/* let assets_info_fetcher = if let Some(assets) = chain .asset .and_then(|mut assets| assets.pop().map(|latest| (latest, assets))) @@ -562,14 +625,14 @@ async fn prepare_chain( let types = metadata.types(); let TypeDef::Composite(ref extension_type) = types - .resolve(extension) + .resolve(extension);/* .with_context(|| { format!("failed to resolve the type of the {CHARGE_ASSET_TX_PAYMENT:?} extension") })? .type_def else { anyhow::bail!("{CHARGE_ASSET_TX_PAYMENT:?} extension has an unexpected type"); - }; + };*/ let asset_id_field = extension_type .fields @@ -579,14 +642,15 @@ async fn prepare_chain( .name .as_ref() .and_then(|name| (name == ASSET_ID).then_some(field.ty.id)) - }) + });/* .with_context(|| { format!( "failed to find the field {ASSET_ID:?} in the {CHARGE_ASSET_TX_PAYMENT:?} extension" ) - })?; + })?;*/ - let TypeDef::Variant(ref option) = types.resolve(asset_id_field).with_context(|| { + let TypeDef::Variant(ref option) = types.resolve(asset_id_field); + /*.with_context(|| { format!( "failed to resolve the type of the field {ASSET_ID:?} in the {CHARGE_ASSET_TX_PAYMENT:?} extension" ) @@ -594,7 +658,7 @@ async fn prepare_chain( anyhow::bail!( "field {ASSET_ID:?} in the {CHARGE_ASSET_TX_PAYMENT:?} extension has an unexpected type" ); - }; + };*/ let asset_id_some = option.variants.iter().find_map(|variant| { if variant.name == SOME { @@ -611,24 +675,23 @@ async fn prepare_chain( } else { None } - }).with_context(|| format!( + })/*.with_context(|| format!( "field {ASSET_ID:?} in the {CHARGE_ASSET_TX_PAYMENT:?} extension doesn't contain the {SOME:?} variant" - ))?; + ))?*/; - let asset_id = &types.resolve(asset_id_some).with_context(|| { + let asset_id = &types.resolve(asset_id_some);/*.with_context(|| { format!( "failed to resolve the type of the {SOME:?} variant of the field {ASSET_ID:?} in the {CHARGE_ASSET_TX_PAYMENT:?} extension" ) - })?.type_def; + })?.type_def;*/ let pallet_index = if let TypeDef::Primitive(_) = asset_id { None } else { - Some(metadata.pallet_by_name_err(ASSETS)?.index()) + pallet_index(metadata, ASSETS) }; Some(AssetsInfoFetcher { assets, - storage: &storage, pallet_index, }) } else { @@ -640,11 +703,11 @@ async fn prepare_chain( } else { None }; - +*/ + /* let properties = ChainProperties::fetch( &chain_name, currencies, - &constants, chain.native_token, assets_info_fetcher, account_lifetime_in_blocks, @@ -653,15 +716,11 @@ async fn prepare_chain( .await?; let connected_chain = ConnectedChain { - methods, + //methods, genesis, rpc, client, - storage, properties, - constants, - runtime_api, - backend, }; let (tx, rx) = oneshot::channel(); @@ -675,92 +734,61 @@ async fn prepare_chain( "found `[chain]`s with the same name ({name:?}) in the config, all chain names must be unique", ); } - +*/ Ok("".into()) } - +*/ #[derive(Debug)] pub struct Currency { chain: String, asset: Option, } +#[derive(Debug)] pub struct ConnectedChain { rpc: String, - methods: LegacyRpcMethods, - backend: Arc>, - client: OnlineClient, + client: WsClient, genesis: BlockHash, properties: ChainProperties, - constants: ConstantsClient, - storage: Option>, - runtime_api: Option>, -} - -impl Debug for ConnectedChain { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.debug_struct(stringify!(ConnectedChain)) - .field("rpc", &self.rpc) - .field("genesis", &self.genesis) - .field("properties", &self.properties) - .finish_non_exhaustive() - } } -#[derive(Debug)] -struct Shutdown; -impl Error for Shutdown {} +//#[derive(Debug)] +//struct Shutdown; -// Not used, but required for the `anyhow::Context` trait. -impl Display for Shutdown { - fn fmt(&self, _: &mut Formatter<'_>) -> fmt::Result { - unimplemented!() - } -} +//impl Error for Shutdown {} pub struct Processor { state: State, - recipient: AccountId, - backend: Arc>, + recipient: AccountId32, shutdown_notification: CancellationToken, - methods: LegacyRpcMethods, - client: OnlineClient, - storage: StorageClient, } impl Processor { pub async fn ignite( rpc: String, - recipient: AccountId, + recipient: AccountId32, state: State, notif: CancellationToken, - ) -> Result> { - let client = Client::builder().build(rpc.clone()).await.unwrap(); - let rpc_c = RpcClient::new(client); - let methods = LegacyRpcMethods::new(rpc_c.clone()); - let backend = Arc::new(LegacyBackend::builder().build(rpc_c)); - let onl = OnlineClient::from_backend(backend.clone()).await.unwrap(); - let st = onl.storage(); + ) -> Result, Error> { + let client = WsClientBuilder::default().build(rpc.clone()).await.map_err(ErrorChain::Client)?; Processor { state, recipient, - backend, shutdown_notification: notif, - methods, - client: onl, - storage: st, - } + }; + Ok("The RPC module is shut down.".into()) + /* .execute() .await .or_else(|error| { error .downcast() .map(|Shutdown| "The RPC module is shut down.".into()) - }) + })*/ } - +/* async fn execute(mut self) -> Result> { let (head_number, _head_hash) = self .finalized_head_number_and_hash() @@ -807,8 +835,8 @@ impl Processor { .chain_subscribe_finalized_heads() .await .context("failed to subscribe to finalized heads") - } - + }*/ +/* async fn process_skipped( &self, next_unscanned: &mut BlockNumber, @@ -832,8 +860,8 @@ impl Processor { *next_unscanned = head; Ok(()) - } - + }*/ +/* async fn process_finalized_heads( &mut self, mut subscription: RpcSubscription<::Header>, @@ -868,8 +896,8 @@ impl Processor { } Ok(()) - } - + }*/ +/* async fn process_block(&self, number: BlockNumber, hash: BlockHash) -> Result<()> { tracing::debug!("Processing the block: {number}."); @@ -969,7 +997,7 @@ impl Processor { async fn balance(&self, hash: BlockHash, account: &AccountId) -> Result { const ACCOUNT: &str = "Account"; const BALANCE: &str = "balance"; - +/* TODO: this should fetch balance and also considet native-nonnative shit wtf is this? if let Some(account_info) = self .storage .at(hash) @@ -996,34 +1024,38 @@ impl Processor { }) } else { Ok(Balance(0)) - } + }*/ } async fn batch_transfer( &self, - _nonce: Nonce, + 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 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, Asset::Id(1337)); - +*/ + //TODO create tx + /* self.client .tx() .create_signed(&call, signer, extensions.build()) .await .context("failed to create a transfer transaction") + */ } - +*/ // async fn current_nonce(&self, account: &AccountId) -> Result { // self.api // .blocks @@ -1079,7 +1111,7 @@ impl Processor { // Ok(()) // } } - +/* fn construct_transfer(to: &AccountId, amount: u128) -> Value { const TRANSFER_KEEP_ALIVE: &str = "transfer"; @@ -1096,11 +1128,11 @@ fn construct_transfer(to: &AccountId, amount: u128) -> Value { ) .into_value() } - +*/ #[derive(Debug)] struct InvoiceChanges { invoice: Invoicee, - incoming: HashMap, + incoming: HashMap, } #[derive(Deserialize, Debug)] @@ -1108,17 +1140,17 @@ struct Transferred { asset_id: u32, // The implementation of `Deserialize` for `AccountId32` works only with strings. #[serde(deserialize_with = "account_deserializer")] - from: AccountId, + from: AccountId32, #[serde(deserialize_with = "account_deserializer")] - to: AccountId, + to: AccountId32, amount: u128, } -fn account_deserializer<'de, D>(deserializer: D) -> Result +fn account_deserializer<'de, D>(deserializer: D) -> Result where D: Deserializer<'de>, { - <([u8; 32],)>::deserialize(deserializer).map(|address| AccountId::new(address.0)) + <([u8; 32],)>::deserialize(deserializer).map(|address| AccountId32(address.0)) } // impl Transferred { diff --git a/src/server.rs b/src/server.rs index fbe117e..0639690 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,7 +1,7 @@ use crate::{ database::{State}, AssetId, BlockNumber, Decimals, ExtrinsicIndex, + error::{Error, ErrorOrder, ErrorServer}, }; -use anyhow::{Context, Result}; use axum::{ extract::{self, rejection::RawPathParamsRejection, MatchedPath, Query, RawPathParams}, http::{header, HeaderName, StatusCode}, @@ -21,6 +21,7 @@ const AMOUNT: &str = "amount"; const CURRENCY: &str = "currency"; const CALLBACK: &str = "callback"; +#[derive(Debug)] pub struct OrderQuery { pub order: String, pub amount: f64, @@ -28,7 +29,7 @@ pub struct OrderQuery { pub currency: String, } -#[derive(Serialize)] +#[derive(Debug, Serialize)] pub struct OrderStatus { pub order: String, pub payment_status: PaymentStatus, @@ -39,7 +40,7 @@ pub struct OrderStatus { pub order_info: OrderInfo, } -#[derive(Serialize)] +#[derive(Debug, Serialize)] pub struct OrderInfo { pub withdrawal_status: WithdrawalStatus, pub amount: f64, @@ -49,7 +50,7 @@ pub struct OrderInfo { pub payment_account: String, } -#[derive(Serialize)] +#[derive(Debug, Serialize)] #[serde(rename_all = "lowercase")] pub enum PaymentStatus { Pending, @@ -57,7 +58,7 @@ pub enum PaymentStatus { Unknown, } -#[derive(Serialize)] +#[derive(Debug, Serialize)] #[serde(rename_all = "lowercase")] pub enum WithdrawalStatus { Waiting, @@ -65,27 +66,27 @@ pub enum WithdrawalStatus { Completed, } -#[derive(Serialize)] +#[derive(Debug, Serialize)] pub struct ServerStatus { pub description: ServerInfo, pub supported_currencies: HashMap, } -#[derive(Serialize)] +#[derive(Debug, Serialize)] struct ServerHealth { description: ServerInfo, connected_rpcs: Vec, status: Health, } -#[derive(Serialize)] +#[derive(Debug, Serialize)] struct RpcInfo { rpc_url: String, chain_name: String, status: Health, } -#[derive(Serialize)] +#[derive(Debug, Serialize)] #[serde(rename_all = "lowercase")] enum Health { Ok, @@ -93,7 +94,7 @@ enum Health { Critical, } -#[derive(Serialize)] +#[derive(Debug, Serialize)] pub struct CurrencyInfo { pub currency: String, pub chain_name: String, @@ -121,7 +122,7 @@ pub enum TokenKind { Balances, } -#[derive(Serialize)] +#[derive(Debug, Serialize)] pub struct ServerInfo { pub version: &'static str, pub instance_id: String, @@ -129,7 +130,7 @@ pub struct ServerInfo { pub kalatori_remark: String, } -#[derive(Serialize)] +#[derive(Debug, Serialize)] pub struct TransactionInfo { #[serde(skip_serializing_if = "Option::is_none", flatten)] finalized_tx: Option, @@ -142,13 +143,14 @@ pub struct TransactionInfo { status: TxStatus, } -#[derive(Serialize)] +#[derive(Debug, Serialize)] struct FinalizedTx { block_number: BlockNumber, position_in_block: ExtrinsicIndex, timestamp: String, } +#[derive(Debug)] enum Amount { All, Exact(f64), @@ -161,7 +163,7 @@ fn amount_serializer(amount: &Amount, serializer: S) -> Result Result>>> { +) -> Result, Error>>, ErrorServer> { let v2: Router = Router::new() .route("/order/:order_id", routing::post(order)) .route("/status", routing::get(status)); let app = Router::new().nest("/v2", v2).with_state(state); let listener = TcpListener::bind(host) - .await - .with_context(|| format!("failed to bind the TCP listener to {host:?}"))?; + .await.map_err(|_| ErrorServer::TcpListenerBind(host))?; Ok(async { axum::serve(listener, app) .with_graceful_shutdown(shutdown_notification.cancelled_owned()) - .await?; + .await.map_err(|_| ErrorServer::ThreadError)?; Ok("The server module is shut down.".into()) }) } +#[derive(Debug, Serialize)] enum OrderSuccess { Created, Found, } -enum OrderError { - LessThanExistentialDeposit(f64), - UnknownCurrency, - MissingParameter(String), - InvalidParameter(String), - AlreadyProcessed(Box), - InternalError, -} -#[derive(Serialize)] +#[derive(Debug, Serialize)] struct InvalidParameter { parameter: String, message: String, @@ -217,42 +211,42 @@ async fn process_order( matched_path: &MatchedPath, path_result: Result, query: &HashMap, -) -> Result<(OrderStatus, OrderSuccess), OrderError> { +) -> Result<(OrderStatus, OrderSuccess), ErrorOrder> { const ORDER_ID: &str = "order_id"; let path_parameters = - path_result.map_err(|_| OrderError::InvalidParameter(matched_path.as_str().to_owned()))?; + path_result.map_err(|_| ErrorOrder::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()))? + .ok_or_else(|| ErrorOrder::MissingParameter(ORDER_ID.into()))? .to_owned(); if query.is_empty() { let order_status = state .order_status(&order) .await - .map_err(|_| OrderError::InternalError)?; + .map_err(|_| ErrorOrder::InternalError)?; Ok((order_status, OrderSuccess::Found)) } else { let get_parameter = |parameter: &str| { query .get(parameter) - .ok_or_else(|| OrderError::MissingParameter(parameter.into())) + .ok_or_else(|| ErrorOrder::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()))?; + .map_err(|_| ErrorOrder::InvalidParameter(AMOUNT.into()))?; if currency != "USDC" { - return Err(OrderError::UnknownCurrency); + return Err(ErrorOrder::UnknownCurrency); } if amount < 0.07 { - return Err(OrderError::LessThanExistentialDeposit(0.07)); + return Err(ErrorOrder::LessThanExistentialDeposit(0.07)); } let order_status = state @@ -263,7 +257,7 @@ async fn process_order( currency, }) .await - .map_err(|_| OrderError::InternalError)?; + .map_err(|_| ErrorOrder::InternalError)?; Ok((order_status, OrderSuccess::Created)) } @@ -283,7 +277,7 @@ async fn order( } .into_response(), Err(error) => match error { - OrderError::LessThanExistentialDeposit(existential_deposit) => ( + ErrorOrder::LessThanExistentialDeposit(existential_deposit) => ( StatusCode::BAD_REQUEST, Json([InvalidParameter { parameter: AMOUNT.into(), @@ -291,7 +285,7 @@ async fn order( }]), ) .into_response(), - OrderError::UnknownCurrency => ( + ErrorOrder::UnknownCurrency => ( StatusCode::BAD_REQUEST, Json([InvalidParameter { parameter: CURRENCY.into(), @@ -299,7 +293,7 @@ async fn order( }]), ) .into_response(), - OrderError::MissingParameter(parameter) => ( + ErrorOrder::MissingParameter(parameter) => ( StatusCode::BAD_REQUEST, Json([InvalidParameter { parameter, @@ -307,7 +301,7 @@ async fn order( }]), ) .into_response(), - OrderError::InvalidParameter(parameter) => ( + ErrorOrder::InvalidParameter(parameter) => ( StatusCode::BAD_REQUEST, Json([InvalidParameter { parameter, @@ -315,10 +309,10 @@ async fn order( }]), ) .into_response(), - OrderError::AlreadyProcessed(order_status) => { + ErrorOrder::AlreadyProcessed(order_status) => { (StatusCode::CONFLICT, Json(order_status)).into_response() } - OrderError::InternalError => StatusCode::INTERNAL_SERVER_ERROR.into_response(), + ErrorOrder::InternalError => StatusCode::INTERNAL_SERVER_ERROR.into_response(), }, } } diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..6f74145 --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,10 @@ +use crate::error::{ErrorUtil, NotHex}; + + +pub fn unhex(hex_data: &str, what_is_hex: NotHex) -> Result, ErrorUtil> { + if let Some(stripped) = hex_data.strip_prefix("0x") { + hex::decode(stripped).map_err(|_| ErrorUtil::NotHex(what_is_hex)) + } else { + hex::decode(hex_data).map_err(|_| ErrorUtil::NotHex(what_is_hex)) + } +} From 81b249ea32f123734a94a14c79b6853475490d83 Mon Sep 17 00:00:00 2001 From: Slesarev Date: Tue, 23 Apr 2024 19:28:05 +0300 Subject: [PATCH 19/76] refactor: move state to separate module --- Cargo.lock | 1 + Cargo.toml | 2 +- src/chain.rs | 69 +++++++--- src/database.rs | 278 +++++++++++++-------------------------- src/error.rs | 30 +++-- src/main.rs | 88 +++++++------ src/rpc.rs | 343 +++++++++++++++++++++++++----------------------- src/server.rs | 10 +- src/state.rs | 128 ++++++++++++++++++ src/utils.rs | 1 - 10 files changed, 516 insertions(+), 434 deletions(-) create mode 100644 src/state.rs diff --git a/Cargo.lock b/Cargo.lock index f0ec8bc..a6998b9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1200,6 +1200,7 @@ dependencies = [ [[package]] name = "mnemonic-external" version = "0.1.0" +source = "git+https://github.com/Alzymologist/mnemonic-external#56fc336015c97e7ff01b702aeca02e5a21b4ee95" dependencies = [ "bitvec", "sha2", diff --git a/Cargo.toml b/Cargo.toml index 4569110..70e9df3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,7 @@ axum-macros = "0.4.1" primitive-types = { version = "0.12.2", features = ["codec"] } substrate-crypto-light = { git = "https://github.com/Alzymologist/substrate-crypto-light" } jsonrpsee = { version = "0.22.4", features = ["ws-client"] } -mnemonic-external = { path = "../mnemonic-external" }#{ git = "https://github.com/varovainen/mnemonic-external", branch = "va-2024-04-23-try-word-ref" } #"https://github.com/Alzymologist/mnemonic-external" } +mnemonic-external = { git = "https://github.com/Alzymologist/mnemonic-external" } thiserror = "1.0.58" substrate_parser = { git = "https://github.com/Alzymologist/substrate-parser" } substrate-constructor = "0.1.0" diff --git a/src/chain.rs b/src/chain.rs index 67c706a..2008508 100644 --- a/src/chain.rs +++ b/src/chain.rs @@ -2,12 +2,18 @@ //TransactionToFill::init(&mut (), metadata, genesis_hash).unwrap(); use crate::error::ErrorChain; -use frame_metadata::{v14::StorageHasher, v15::{RuntimeMetadataV15, StorageEntryType}}; +use frame_metadata::{ + v14::StorageHasher, + v15::{RuntimeMetadataV15, StorageEntryType}, +}; use parity_scale_codec::{Decode, Encode}; use scale_info::{TypeDef, TypeDefPrimitive}; use serde_json::{Map, Number, Value}; use sp_crypto_hashing::{blake2_128, blake2_256, twox_128, twox_256, twox_64}; -use substrate_parser::{AsMetadata, cards::{ExtendedData, ParsedData}, decode_all_as_type, ShortSpecs}; +use substrate_parser::{ + cards::{ExtendedData, ParsedData}, + decode_all_as_type, AsMetadata, ShortSpecs, +}; pub fn hashed_key_element(data: &[u8], hasher: &StorageHasher) -> Vec { match hasher { @@ -21,14 +27,21 @@ pub fn hashed_key_element(data: &[u8], hasher: &StorageHasher) -> Vec { } } -pub fn whole_key_u32_value(prefix: &str, storage_name: &str, metadata_v15: &RuntimeMetadataV15, entered_data: u32) -> String { +pub fn whole_key_u32_value( + prefix: &str, + storage_name: &str, + metadata_v15: &RuntimeMetadataV15, + entered_data: u32, +) -> String { 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(_) => panic!("expected map with single entry, got plain"), + StorageEntryType::Plain(_) => { + panic!("expected map with single entry, got plain") + } StorageEntryType::Map { hashers, key: key_ty, @@ -37,16 +50,22 @@ pub fn whole_key_u32_value(prefix: &str, storage_name: &str, metadata_v15: &Runt if hashers.len() == 1 { let hasher = &hashers[0]; match metadata_v15.types.resolve(key_ty.id).unwrap().type_def { - TypeDef::Primitive(TypeDefPrimitive::U32) => return 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)) - ), - _ => panic!("wrong data type") + TypeDef::Primitive(TypeDefPrimitive::U32) => { + return 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 + )) + ) + } + _ => panic!("wrong data type"), } + } else { + panic!("expected map with single entry, got multiple entries") } - else {panic!("expected map with single entry, got multiple entries")} } } } @@ -58,7 +77,6 @@ pub fn whole_key_u32_value(prefix: &str, storage_name: &str, metadata_v15: &Runt panic!("have not found pallet"); } - pub fn decimals(x: &Map) -> Result { match x.get("tokenDecimals") { // decimals info is fetched in `system_properties` rpc call @@ -137,7 +155,6 @@ pub fn decimals(x: &Map) -> Result { } } - pub fn optional_prefix_from_meta(metadata: &RuntimeMetadataV15) -> Option { let mut base58_prefix_data = None; for pallet in &metadata.pallets { @@ -188,8 +205,11 @@ pub fn optional_prefix_from_meta(metadata: &RuntimeMetadataV15) -> Option { } } - -pub fn fetch_constant(metadata: &RuntimeMetadataV15, pallet_name: &str, constant_name: &str) -> Option { +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 { @@ -208,7 +228,11 @@ pub fn fetch_constant(metadata: &RuntimeMetadataV15, pallet_name: &str, constant &value.as_ref(), &mut (), &metadata.types, - ).ok() } else {None} + ) + .ok() + } else { + None + } } pub fn system_properties_to_short_specs( @@ -227,11 +251,15 @@ pub fn system_properties_to_short_specs( } pub fn pallet_index(metadata: &RuntimeMetadataV15, name: &str) -> Option { - for pallet in &metadata.pallets { if pallet.name == name {return Some(pallet.index)} }; - return None + 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 { +pub fn storage_key(prefix: &str, storage_name: &str) -> String { format!( "0x{}{}", hex::encode(twox_128(prefix.as_bytes())), @@ -354,4 +382,3 @@ pub fn unit(x: &Map) -> Result { None => Err(ErrorChain::NoUnit), } } - diff --git a/src/database.rs b/src/database.rs index 3a4a5a9..c1aa2da 100644 --- a/src/database.rs +++ b/src/database.rs @@ -1,17 +1,16 @@ use crate::{ - error::ErrorDb, + error::{Error, ErrorDb}, server::{CurrencyProperties, OrderQuery, OrderStatus, ServerInfo, ServerStatus}, AccountId32, AssetId, Balance, BlockNumber, Nonce, Timestamp, }; -use parity_scale_codec::{Compact, Encode, Decode}; +use parity_scale_codec::{Compact, Decode, Encode}; use redb::{ backends::{FileBackend, InMemoryBackend}, Database, ReadableTable, TableDefinition, TypeName, Value, }; use serde::Deserialize; use std::{collections::HashMap, fs::File, io::ErrorKind}; -use substrate_crypto_light::sr25519::Pair; -use tokio::sync::{oneshot}; +use tokio::sync::oneshot; pub const MODULE: &str = module_path!(); @@ -153,20 +152,44 @@ pub struct ConfigWoChains { pub rpc: String, } -enum StateAccessRequest { - GetInvoiceStatus(GetInvoiceStatus), - CreateInvoice(CreateInvoice), - ServerStatus(oneshot::Sender), -} +pub struct DatabaseServer {} -struct GetInvoiceStatus { - pub order: String, - pub res: oneshot::Sender, -} +impl DatabaseServer { + pub fn init(path_option: Option) -> Result { + let builder = Database::builder(); + let is_new; + + let database = if let Some(path) = path_option { + tracing::info!("Creating/Opening the database at {path:?}."); + + match File::create_new(&path) { + Ok(file) => { + is_new = true; + + FileBackend::new(file) + .and_then(|backend| builder.create_with_backend(backend)) + .map_err(ErrorDb::DbStartError)? + } + Err(error) if error.kind() == ErrorKind::AlreadyExists => { + is_new = false; + + builder.create(path).map_err(ErrorDb::DbStartError)? + } + Err(error) => return Err(error.into()), + } + } else { + tracing::warn!( + "The in-memory backend for the database is selected. All saved data will be deleted after the shutdown!" + ); + + is_new = true; -struct CreateInvoice { - pub order_query: OrderQuery, - pub res: oneshot::Sender, + builder + .create_with_backend(InMemoryBackend::new()) + .map_err(ErrorDb::DbStartError)? + }; //.context("failed to create/open the database")?; + Ok(Self {}) + } } //impl StateInterface { @@ -274,11 +297,6 @@ let pay_acc: AccountId = state } */ -#[derive(Clone, Debug)] -pub struct State { - pub tx: tokio::sync::mpsc::Sender, -} - #[derive(Deserialize, Debug)] pub struct Invoicee { pub callback: String, @@ -287,127 +305,10 @@ pub struct Invoicee { pub paym_acc: Account, } -impl State { - pub fn initialise( - path_option: Option, - currencies: HashMap, - current_pair: Pair, - old_pairs: HashMap, - ConfigWoChains { - recipient, - debug, - remark, - depth, - account_lifetime, - rpc, - }: ConfigWoChains, - ) -> Result { - let builder = Database::builder(); - let is_new; - - let database = if let Some(path) = path_option { - tracing::info!("Creating/Opening the database at {path:?}."); - - match File::create_new(&path) { - Ok(file) => { - is_new = true; - - FileBackend::new(file).and_then(|backend| builder.create_with_backend(backend)).map_err(ErrorDb::DbStartError)? - } - Err(error) if error.kind() == ErrorKind::AlreadyExists => { - is_new = false; - - builder.create(path).map_err(ErrorDb::DbStartError)? - } - Err(error) => return Err(error.into()) - } - } else { - tracing::warn!( - "The in-memory backend for the database is selected. All saved data will be deleted after the shutdown!" - ); - - is_new = true; - - builder.create_with_backend(InMemoryBackend::new()).map_err(ErrorDb::DbStartError)? - };//.context("failed to create/open the database")?; - - /* - 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); - tokio::spawn(async move { - while let Some(request) = rx.recv().await { - &database; - match request { - StateAccessRequest::GetInvoiceStatus(a) => {}, - StateAccessRequest::CreateInvoice(a) => {}, - StateAccessRequest::ServerStatus(res) => { - let description = ServerInfo { - version: env!("CARGO_PKG_VERSION"), - instance_id: String::new(), - debug, - kalatori_remark: remark.clone(), - }; - let server_status = ServerStatus {description, supported_currencies: currencies.clone()}; - res.send(server_status); - }, - }; - } - }); - - Ok(Self { tx }) - } - - 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; - rx.await.map_err(|_| ErrorDb::DbEngineDown) - } - - pub async fn server_status(&self) -> Result { - let (res, rx) = oneshot::channel(); - self.tx.send(StateAccessRequest::ServerStatus(res)).await; - rx.await.map_err(|_| ErrorDb::DbEngineDown) - } - - 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; - rx.await.map_err(|_| ErrorDb::DbEngineDown) - } +/* - pub fn interface(&self) -> Self { - State { - tx: self.tx.clone(), - } - } - /* +*/ +/* pub fn server_info(&self) -> ServerInfo { ServerInfo { version: env!("CARGO_PKG_VERSION"), @@ -417,55 +318,54 @@ impl State { } } */ - /* - pub fn currency_properties(&self, currency_name: &str) -> Result<&CurrencyProperties, ErrorDb> { - self.currencies - .get(currency_name) - .ok_or(ErrorDb::CurrencyKeyNotFound) - } +/* + pub fn currency_properties(&self, currency_name: &str) -> Result<&CurrencyProperties, ErrorDb> { + self.currencies + .get(currency_name) + .ok_or(ErrorDb::CurrencyKeyNotFound) + } - 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 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); diff --git a/src/error.rs b/src/error.rs index aa9bf1d..aabd3d7 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,7 +1,4 @@ -use crate::{ - server::OrderStatus, - SocketAddr, -}; +use crate::{server::OrderStatus, SocketAddr}; use frame_metadata::v15::RuntimeMetadataV15; use jsonrpsee::core::client::error::Error as ClientError; use mnemonic_external::error::ErrorWordList; @@ -42,6 +39,12 @@ pub enum Error { #[error("Seed phrase invalid: {0:?}")] InvalidSeed(ErrorWordList), + + #[error("Fatal error. System is shutting down.")] + Fatal, + + #[error("Operating system related I/O error {0:?}")] + IoError(std::io::Error), } impl From for Error { @@ -62,6 +65,12 @@ impl From for Error { } } +impl From for Error { + fn from(e: std::io::Error) -> Self { + Error::IoError(e) + } +} + impl From for Error { fn from(e: ErrorWordList) -> Self { Error::InvalidSeed(e) @@ -88,12 +97,11 @@ pub enum ErrorChain { #[error("Threading error. {0}")] Tokio(JoinError), -// #[error("Internal database error. {0}")] -// DbInternal(sled::Error), - -// #[error("Database error recording transaction. {0}")] -// DbTransaction(sled::transaction::TransactionError), + // #[error("Internal database error. {0}")] + // DbInternal(sled::Error), + // #[error("Database error recording transaction. {0}")] + // DbTransaction(sled::transaction::TransactionError), #[error("Format of fetched decimals {value} is not supported.")] DecimalsFormatNotSupported { value: String }, @@ -102,7 +110,6 @@ pub enum ErrorChain { //#[error("Key in the database {} is damaged, and could not be decoded.", hex::encode(.0))] //DecodeDbKey(IVec), - #[error("MetadataSpecs in the database for genesis hash {} got damaged, and could not be decoded.", hex::encode(.0))] DecodeDbMetadataSpecs(H256), @@ -286,7 +293,7 @@ pub enum ErrorServer { #[derive(Debug, thiserror::Error)] pub enum ErrorUtil { - #[error("{0}")] + #[error("{0}")] NotHex(NotHex), } @@ -304,4 +311,3 @@ pub enum NotHex { #[error("Encoded storage value string is not a valid hexadecimal.")] StorageValue, } - diff --git a/src/main.rs b/src/main.rs index 9d5b005..e595fc6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -29,11 +29,13 @@ mod database; mod error; mod rpc; mod server; +mod state; mod utils; -use database::{ConfigWoChains, State}; +use database::ConfigWoChains; use error::Error; use rpc::Processor; +use state::State; const CONFIG: &str = "KALATORI_CONFIG"; const LOG: &str = "KALATORI_LOG"; @@ -66,6 +68,8 @@ async fn main() -> Result<(), Error> { set_panic_hook(shutdown_notification.clone()); initialize_logger()?; + // Read env + let (pair, old_pairs) = parse_seeds()?; let recipient = env::var(RECIPIENT).map_err(|_| Error::Env(RECIPIENT.to_string()))?; @@ -75,7 +79,8 @@ async fn main() -> Result<(), Error> { let host = if let Some(unparsed_host) = config.host { unparsed_host - .parse().map_err(|_| Error::ConfigParse("host to define a socket address".to_string()))? + .parse() + .map_err(|_| Error::ConfigParse("host to define a socket address".to_string()))? } else { DEFAULT_SOCKET }; @@ -108,6 +113,8 @@ async fn main() -> Result<(), Error> { })) }; + // Start services + tracing::info!( "Kalatori {} by {} is starting on {}...", env!("CARGO_PKG_VERSION"), @@ -127,10 +134,11 @@ async fn main() -> Result<(), Error> { let rpc = env::var("KALATORI_RPC").unwrap(); - let recipient = AccountId32::from_base58_string(&recipient).map_err(Error::RecipientAccount)?.0; + let recipient = AccountId32::from_base58_string(&recipient) + .map_err(Error::RecipientAccount)? + .0; let state = State::initialise( - database_path, currencies, pair, old_pairs, @@ -142,6 +150,7 @@ async fn main() -> Result<(), Error> { account_lifetime: config.account_lifetime, rpc: rpc.clone(), }, + &task_tracker, )?; task_tracker.spawn( @@ -154,8 +163,7 @@ async fn main() -> Result<(), Error> { ), ); - let server = server::new(shutdown_notification.clone(), host, state) - .await?; + let server = server::new(shutdown_notification.clone(), host, state).await?; // task_tracker.spawn(shutdown( // processor.ignite(last_saved_block, task_tracker.clone(), error_tx.clone()), @@ -163,10 +171,14 @@ async fn main() -> Result<(), Error> { // )); task_tracker.spawn("the server module", server); + // Main loop + task_tracker .wait_with_notification(error_rx, shutdown_notification) .await; + // Shutdown + tracing::info!("Goodbye!"); Ok(()) @@ -265,41 +277,38 @@ fn default_filter() -> String { } fn parse_seeds() -> Result<(Pair, HashMap), Error> { - let pair = seed_from_phrase( - &env::var(SEED).map_err(|_| Error::Env(SEED.to_string()))?, - )?; + let pair = seed_from_phrase(&env::var(SEED).map_err(|_| Error::Env(SEED.to_string()))?)?; let mut old_pairs = HashMap::new(); -/* TODO: add this at least when you do something about these - for (raw_key, raw_value) in env::vars_os() { - let raw_key_bytes = raw_key.as_encoded_bytes(); - - if let Some(stripped_raw_key) = raw_key_bytes.strip_prefix(OLD_SEED.as_bytes()) { - let key = str::from_utf8(stripped_raw_key) - .context("failed to read an old seed environment variable name")?; - let value = raw_value - .to_str() - .with_context(|| format!("failed to read a seed phrase from `{OLD_SEED}{key}`"))?; - let old_pair = seed_from_phrase(value)?; - - old_pairs.insert(key.to_owned(), old_pair); + /* TODO: add this at least when you do something about these + for (raw_key, raw_value) in env::vars_os() { + let raw_key_bytes = raw_key.as_encoded_bytes(); + + if let Some(stripped_raw_key) = raw_key_bytes.strip_prefix(OLD_SEED.as_bytes()) { + let key = str::from_utf8(stripped_raw_key) + .context("failed to read an old seed environment variable name")?; + let value = raw_value + .to_str() + .with_context(|| format!("failed to read a seed phrase from `{OLD_SEED}{key}`"))?; + let old_pair = seed_from_phrase(value)?; + + old_pairs.insert(key.to_owned(), old_pair); + } } - } -*/ + */ Ok((pair, old_pairs)) } - pub fn seed_from_phrase(seed: &str) -> Result { - let mut word_set = WordSet::new(); - for word in seed.split(' ') { - word_set - .add_word(&word, &InternalWordList)?; - } - let entropy = word_set.to_entropy()?; - let derivation = cut_path("").expect("empty derivation is hardcoded"); - Ok(Pair::from_entropy_and_full_derivation(&entropy, derivation).expect("empty derivation and password are hardcoded")) +pub fn seed_from_phrase(seed: &str) -> Result { + let mut word_set = WordSet::new(); + for word in seed.split(' ') { + word_set.add_word(&word, &InternalWordList)?; } - + let entropy = word_set.to_entropy()?; + let derivation = cut_path("").expect("empty derivation is hardcoded"); + Ok(Pair::from_entropy_and_full_derivation(&entropy, derivation) + .expect("empty derivation and password are hardcoded")) +} #[derive(Clone)] struct TaskTracker { @@ -371,7 +380,9 @@ impl TaskTracker { } } -async fn shutdown_listener(shutdown_notification: CancellationToken) -> Result, Error> { +async fn shutdown_listener( + shutdown_notification: CancellationToken, +) -> Result, Error> { tokio::select! { biased; signal = signal::ctrl_c() => { @@ -405,9 +416,7 @@ struct Config { impl Config { fn load() -> Result { let config_path = env::var(CONFIG).or_else(|error| match error { - VarError::NotUnicode(_) => { - Err(Error::Env(CONFIG.to_string())) - } + VarError::NotUnicode(_) => Err(Error::Env(CONFIG.to_string())), VarError::NotPresent => { tracing::debug!( "`{CONFIG}` isn't present, using the default value instead: {DEFAULT_CONFIG:?}." @@ -416,7 +425,8 @@ impl Config { Ok(DEFAULT_CONFIG.into()) } })?; - let unparsed_config = fs::read_to_string(&config_path).map_err(|_| Error::ConfigFileRead(config_path.clone()))?; + let unparsed_config = fs::read_to_string(&config_path) + .map_err(|_| Error::ConfigFileRead(config_path.clone()))?; toml::from_str(&unparsed_config).map_err(|_| Error::ConfigFileParse(config_path)) } diff --git a/src/rpc.rs b/src/rpc.rs index d8a75b9..b2eb378 100644 --- a/src/rpc.rs +++ b/src/rpc.rs @@ -1,10 +1,9 @@ use crate::{ chain::{base58prefix, pallet_index, storage_key, unit}, - database::{Invoicee, State}, + database::Invoicee, error::{Error, ErrorChain, NotHex}, - server::{ - CurrencyProperties, - }, + server::CurrencyProperties, + state::State, utils::unhex, AccountId32, AssetId, AssetInfo, Balance, BlockHash, BlockNumber, Chain, Decimals, NativeToken, Nonce, PalletIndex, TaskTracker, Timestamp, @@ -13,9 +12,9 @@ use frame_metadata::{v15::RuntimeMetadataV15, RuntimeMetadata}; use jsonrpsee::core::client::ClientT; use jsonrpsee::rpc_params; use jsonrpsee::ws_client::{WsClient, WsClientBuilder}; +use parity_scale_codec::DecodeAll; use primitive_types::H256; use scale_info::TypeDef; -use parity_scale_codec::DecodeAll; use serde::{Deserialize, Deserializer}; use serde_json::{Map, Number, Value}; use std::{ @@ -24,7 +23,10 @@ use std::{ fmt::Debug, num::NonZeroU64, }; -use substrate_parser::{AsMetadata, cards::{ExtendedData, ParsedData}, decode_all_as_type, ShortSpecs}; +use substrate_parser::{ + cards::{ExtendedData, ParsedData}, + decode_all_as_type, AsMetadata, ShortSpecs, +}; use tokio::sync::{mpsc, oneshot}; use tokio_util::sync::CancellationToken; @@ -267,10 +269,15 @@ async fn check_sufficiency_and_fetch_min_balance( }) }*/ -pub async fn storage_fetch(client: &WsClient, prefix: &str, storage_name: &str, block: &BlockHash) -> Result { +pub async fn storage_fetch( + client: &WsClient, + prefix: &str, + storage_name: &str, + block: &BlockHash, +) -> Result { let key = storage_key(prefix, storage_name); value_by_key_from_storage(client, &key, &hex::encode(block)).await -} +} pub async fn value_by_key_from_storage( client: &WsClient, @@ -535,7 +542,7 @@ async fn prepare_chain( .build(&endpoint) .await .map_err(ErrorChain::Client)?; - + let genesis = genesis_hash(&client).await?; let finalized_hash = block_hash(&client).await?; @@ -546,13 +553,13 @@ async fn prepare_chain( //TODO: we don't have to require this mechanism at all let block_time = if pallet_index(&metadata, BABE).is_some() { match fetch_constant(&metadata, BABE, "ExpectedBlockTime").ok_or(ErrorChain::BabeExpectedBlockTime)?.data { - + } } else { //const SLOT_DURATION: &str = "slot_duration"; state_call( - &client, + &client, rpc_params![ "Aura_slot_duration", &block_hash_string @@ -752,7 +759,6 @@ pub struct ConnectedChain { properties: ChainProperties, } - //#[derive(Debug)] //struct Shutdown; @@ -771,7 +777,10 @@ impl Processor { state: State, notif: CancellationToken, ) -> Result, Error> { - let client = WsClientBuilder::default().build(rpc.clone()).await.map_err(ErrorChain::Client)?; + let client = WsClientBuilder::default() + .build(rpc.clone()) + .await + .map_err(ErrorChain::Client)?; Processor { state, @@ -788,7 +797,7 @@ impl Processor { .map(|Shutdown| "The RPC module is shut down.".into()) })*/ } -/* + /* async fn execute(mut self) -> Result> { let (head_number, _head_hash) = self .finalized_head_number_and_hash() @@ -836,7 +845,7 @@ impl Processor { .await .context("failed to subscribe to finalized heads") }*/ -/* + /* async fn process_skipped( &self, next_unscanned: &mut BlockNumber, @@ -861,7 +870,7 @@ impl Processor { Ok(()) }*/ -/* + /* async fn process_finalized_heads( &mut self, mut subscription: RpcSubscription<::Header>, @@ -897,165 +906,165 @@ impl Processor { Ok(()) }*/ -/* - async fn process_block(&self, number: BlockNumber, hash: BlockHash) -> Result<()> { - tracing::debug!("Processing the 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 invoices = &mut *self.state.invoices.write().await; - - // 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(); - - #[allow(clippy::single_match)] - match (metadata.pallet.name(), &*metadata.variant.name) { - // (SYSTEM, UPDATE) => update = true, - (ASSETS, TRANSFERRED) => { - let tr = Transferred::deserialize( - event - .field_values() - .context("failed to decode event's fields")?, - ) - .context("failed to deserialize a transfer event")?; - - tracing::info!("{tr:?}"); - /* TODO process using cache and db access - #[allow(clippy::unnecessary_find_map)] - if let Some(invoic) = invoices.iter().find_map(|invoic| { - tracing::info!("{tr:?} {invoic:?}"); - tracing::info!("{}", tr.to == invoic.1.paym_acc); - tracing::info!("{}", *invoic.1.amount >= tr.amount); - - if tr.to == invoic.1.paym_acc && *invoic.1.amount <= tr.amount { - Some(invoic) - } else { - None - } - }) { - tracing::info!("{invoic:?}"); - - if !invoic.1.callback.is_empty() { - tracing::info!("{:?}", invoic.1.callback); - - crate::callback::callback( - invoic.1.callback.clone(), - invoic.0.to_string(), - self.state.recipient.clone(), - self.state.debug, - self.state.remark.clone(), - invoic.1.amount, - self.state.rpc.clone(), - invoic.1.paym_acc.clone(), - ) - .await; - } + /* + async fn process_block(&self, number: BlockNumber, hash: BlockHash) -> Result<()> { + tracing::debug!("Processing the block: {number}."); - invoices.insert( - invoic.0.clone(), - Invoicee { - callback: invoic.1.callback.clone(), - amount: Balance(*invoic.1.amount), - paid: true, - paym_acc: invoic.1.paym_acc.clone(), - }, - ); - }*/ + 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 invoices = &mut *self.state.invoices.write().await; + + // 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(); + + #[allow(clippy::single_match)] + match (metadata.pallet.name(), &*metadata.variant.name) { + // (SYSTEM, UPDATE) => update = true, + (ASSETS, TRANSFERRED) => { + let tr = Transferred::deserialize( + event + .field_values() + .context("failed to decode event's fields")?, + ) + .context("failed to deserialize a transfer event")?; + + tracing::info!("{tr:?}"); + /* TODO process using cache and db access + #[allow(clippy::unnecessary_find_map)] + if let Some(invoic) = invoices.iter().find_map(|invoic| { + tracing::info!("{tr:?} {invoic:?}"); + tracing::info!("{}", tr.to == invoic.1.paym_acc); + tracing::info!("{}", *invoic.1.amount >= tr.amount); + + if tr.to == invoic.1.paym_acc && *invoic.1.amount <= tr.amount { + Some(invoic) + } else { + None + } + }) { + tracing::info!("{invoic:?}"); + + if !invoic.1.callback.is_empty() { + tracing::info!("{:?}", invoic.1.callback); + + crate::callback::callback( + invoic.1.callback.clone(), + invoic.0.to_string(), + self.state.recipient.clone(), + self.state.debug, + self.state.remark.clone(), + invoic.1.amount, + self.state.rpc.clone(), + invoic.1.paym_acc.clone(), + ) + .await; + } + + invoices.insert( + invoic.0.clone(), + Invoicee { + callback: invoic.1.callback.clone(), + amount: Balance(*invoic.1.amount), + paid: true, + paym_acc: invoic.1.paym_acc.clone(), + }, + ); + }*/ + } + _ => {} } - _ => {} } - } - // for (invoice, changes) in invoices_changes { - // let price = match changes.invoice.status { - // InvoiceStatus::Unpaid(price) | InvoiceStatus::Paid(price) => price, - // }; + // 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")?; - // } + // self.process_unpaid(&block, changes, hash, invoice, price) + // .await + // .context("failed to process an unpaid invoice")?; + // } - Ok(()) - } + Ok(()) + } - async fn balance(&self, hash: BlockHash, account: &AccountId) -> Result { - const ACCOUNT: &str = "Account"; - const BALANCE: &str = "balance"; -/* TODO: this should fetch balance and also considet native-nonnative shit wtf is this? - if let Some(account_info) = self - .storage - .at(hash) - .fetch(&dynamic::storage( - ASSETS, - ACCOUNT, - vec![ - Value::from(1337u32), - 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().map(Balance).with_context(|| { - format!("expected `u128` as the type of a balance, got {encoded_balance}") - }) - } else { - Ok(Balance(0)) - }*/ - } + async fn balance(&self, hash: BlockHash, account: &AccountId) -> Result { + const ACCOUNT: &str = "Account"; + const BALANCE: &str = "balance"; + /* TODO: this should fetch balance and also considet native-nonnative shit wtf is this? + if let Some(account_info) = self + .storage + .at(hash) + .fetch(&dynamic::storage( + ASSETS, + ACCOUNT, + vec![ + Value::from(1337u32), + 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().map(Balance).with_context(|| { + format!("expected `u128` as the type of a balance, got {encoded_balance}") + }) + } else { + Ok(Balance(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, Asset::Id(1337)); -*/ - //TODO create tx - /* - self.client - .tx() - .create_signed(&call, signer, extensions.build()) - .await - .context("failed to create a transfer transaction") - */ - } -*/ + 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, Asset::Id(1337)); + */ + //TODO create tx + /* + self.client + .tx() + .create_signed(&call, signer, extensions.build()) + .await + .context("failed to create a transfer transaction") + */ + } + */ // async fn current_nonce(&self, account: &AccountId) -> Result { // self.api // .blocks diff --git a/src/server.rs b/src/server.rs index 0639690..5e53a9e 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,6 +1,7 @@ use crate::{ - database::{State}, AssetId, BlockNumber, Decimals, ExtrinsicIndex, error::{Error, ErrorOrder, ErrorServer}, + state::State, + AssetId, BlockNumber, Decimals, ExtrinsicIndex, }; use axum::{ extract::{self, rejection::RawPathParamsRejection, MatchedPath, Query, RawPathParams}, @@ -182,12 +183,14 @@ pub async fn new( let app = Router::new().nest("/v2", v2).with_state(state); let listener = TcpListener::bind(host) - .await.map_err(|_| ErrorServer::TcpListenerBind(host))?; + .await + .map_err(|_| ErrorServer::TcpListenerBind(host))?; Ok(async { axum::serve(listener, app) .with_graceful_shutdown(shutdown_notification.cancelled_owned()) - .await.map_err(|_| ErrorServer::ThreadError)?; + .await + .map_err(|_| ErrorServer::ThreadError)?; Ok("The server module is shut down.".into()) }) @@ -199,7 +202,6 @@ enum OrderSuccess { Found, } - #[derive(Debug, Serialize)] struct InvalidParameter { parameter: String, diff --git a/src/state.rs b/src/state.rs new file mode 100644 index 0000000..01b5f5c --- /dev/null +++ b/src/state.rs @@ -0,0 +1,128 @@ +use crate::{ + error::Error, + server::{CurrencyProperties, OrderQuery, OrderStatus, ServerInfo, ServerStatus}, + ConfigWoChains, TaskTracker, +}; + +use std::collections::HashMap; + +use substrate_crypto_light::sr25519::Pair; +use tokio::sync::oneshot; + +#[derive(Clone, Debug)] +pub struct State { + pub tx: tokio::sync::mpsc::Sender, +} + +impl State { + pub fn initialise( + currencies: HashMap, + current_pair: Pair, + old_pairs: HashMap, + ConfigWoChains { + recipient, + debug, + remark, + depth, + account_lifetime, + rpc, + }: ConfigWoChains, + task_tracker: &TaskTracker, + ) -> 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); + task_tracker.spawn("State Handler", async move { + while let Some(request) = rx.recv().await { + match request { + StateAccessRequest::GetInvoiceStatus(a) => {} + StateAccessRequest::CreateInvoice(a) => {} + StateAccessRequest::ServerStatus(res) => { + let description = ServerInfo { + version: env!("CARGO_PKG_VERSION"), + instance_id: String::new(), + debug, + kalatori_remark: remark.clone(), + }; + let server_status = ServerStatus { + description, + supported_currencies: currencies.clone(), + }; + res.send(server_status); + } + }; + } + + Ok("State handler is shutting down".into()) + }); + + Ok(Self { tx }) + } + + 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; + 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; + 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; + rx.await.map_err(|_| Error::Fatal) + } + + pub fn interface(&self) -> Self { + State { + tx: self.tx.clone(), + } + } +} + +enum StateAccessRequest { + GetInvoiceStatus(GetInvoiceStatus), + CreateInvoice(CreateInvoice), + ServerStatus(oneshot::Sender), +} + +struct GetInvoiceStatus { + pub order: String, + pub res: oneshot::Sender, +} + +struct CreateInvoice { + pub order_query: OrderQuery, + pub res: oneshot::Sender, +} diff --git a/src/utils.rs b/src/utils.rs index 6f74145..48ceef6 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,6 +1,5 @@ use crate::error::{ErrorUtil, NotHex}; - pub fn unhex(hex_data: &str, what_is_hex: NotHex) -> Result, ErrorUtil> { if let Some(stripped) = hex_data.strip_prefix("0x") { hex::decode(stripped).map_err(|_| ErrorUtil::NotHex(what_is_hex)) From 59576dd82f10576f8da2fa4324af6ea3cd240a14 Mon Sep 17 00:00:00 2001 From: Slesarev Date: Wed, 24 Apr 2024 12:28:15 +0300 Subject: [PATCH 20/76] refactor: isolate apiv2 definitions --- Cargo.lock | 100 ++++++++++++++++++++++++--- Cargo.toml | 2 +- src/callback.rs | 2 +- src/database.rs | 85 ++++++++++++----------- src/definitions.rs | 163 +++++++++++++++++++++++++++++++++++++++++++++ src/error.rs | 4 +- src/main.rs | 6 +- src/rpc.rs | 2 +- src/server.rs | 155 +----------------------------------------- src/state.rs | 10 ++- 10 files changed, 321 insertions(+), 208 deletions(-) create mode 100644 src/definitions.rs diff --git a/Cargo.lock b/Cargo.lock index a6998b9..b37ed12 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -382,6 +382,21 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" + [[package]] name = "crunchy" version = "0.2.2" @@ -641,6 +656,16 @@ dependencies = [ "serde", ] +[[package]] +name = "fs2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "funty" version = "2.0.0" @@ -730,6 +755,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" @@ -960,6 +994,15 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + [[package]] name = "integer-sqrt" version = "0.1.5" @@ -1080,10 +1123,10 @@ dependencies = [ "names", "parity-scale-codec", "primitive-types", - "redb", "scale-info", "serde", "serde_json", + "sled", "sp-crypto-hashing", "substrate-constructor", "substrate-crypto-light", @@ -1358,6 +1401,17 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "parking_lot" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core 0.8.6", +] + [[package]] name = "parking_lot" version = "0.12.1" @@ -1365,7 +1419,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ "lock_api", - "parking_lot_core", + "parking_lot_core 0.9.9", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" +dependencies = [ + "cfg-if", + "instant", + "libc", + "redox_syscall 0.2.16", + "smallvec", + "winapi", ] [[package]] @@ -1376,7 +1444,7 @@ checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.4.1", "smallvec", "windows-targets 0.48.5", ] @@ -1566,12 +1634,12 @@ dependencies = [ ] [[package]] -name = "redb" -version = "2.0.0" +name = "redox_syscall" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1100a056c5dcdd4e5513d5333385223b26ef1bf92f31eb38f407e8c20549256" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ - "libc", + "bitflags", ] [[package]] @@ -1985,6 +2053,22 @@ dependencies = [ "autocfg", ] +[[package]] +name = "sled" +version = "0.34.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f96b4737c2ce5987354855aed3797279def4ebf734436c6aa4552cf8e169935" +dependencies = [ + "crc32fast", + "crossbeam-epoch", + "crossbeam-utils", + "fs2", + "fxhash", + "libc", + "log", + "parking_lot 0.11.2", +] + [[package]] name = "smallvec" version = "1.13.2" @@ -2312,7 +2396,7 @@ dependencies = [ "libc", "mio", "num_cpus", - "parking_lot", + "parking_lot 0.12.1", "pin-project-lite", "signal-hook-registry", "socket2", diff --git a/Cargo.toml b/Cargo.toml index 70e9df3..7fd79fe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,6 @@ tokio-util = { version = "0.7", features = ["rt"] } tokio = { version = "1", features = ["full"] } serde = { version = "1", features = ["derive"] } -redb = "2" tracing = "0.1" scale-info = "2" axum-macros = "0.4.1" @@ -38,6 +37,7 @@ parity-scale-codec = "3.6.9" serde_json = "1.0.116" sp-crypto-hashing = "0.1.0" toml = "0.8.12" +sled = "0.34.7" [profile.release] strip = true diff --git a/src/callback.rs b/src/callback.rs index 1c84a6c..77fdfaa 100644 --- a/src/callback.rs +++ b/src/callback.rs @@ -1,5 +1,5 @@ use crate::{ - server::{ + definitions::api_v2::{ CurrencyInfo, OrderInfo, OrderStatus, PaymentStatus, ServerInfo, TokenKind, WithdrawalStatus, }, diff --git a/src/database.rs b/src/database.rs index c1aa2da..bf39a0b 100644 --- a/src/database.rs +++ b/src/database.rs @@ -1,26 +1,28 @@ +//! 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::{ + definitions::api_v2::{CurrencyProperties, OrderQuery, OrderStatus, ServerInfo, ServerStatus}, error::{Error, ErrorDb}, - server::{CurrencyProperties, OrderQuery, OrderStatus, ServerInfo, ServerStatus}, - AccountId32, AssetId, Balance, BlockNumber, Nonce, Timestamp, + AccountId32, AssetId, Balance, BlockNumber, Nonce, TaskTracker, Timestamp, }; use parity_scale_codec::{Compact, Decode, Encode}; -use redb::{ - backends::{FileBackend, InMemoryBackend}, - Database, ReadableTable, TableDefinition, TypeName, Value, -}; use serde::Deserialize; use std::{collections::HashMap, fs::File, io::ErrorKind}; -use tokio::sync::oneshot; +use tokio::sync::{mpsc, oneshot}; pub const MODULE: &str = module_path!(); // 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); @@ -118,6 +120,7 @@ struct TransferTx { exact_amount: Option>, } +/* impl Value for Invoice { type SelfType<'a> = Self; @@ -141,7 +144,7 @@ impl Value for Invoice { fn type_name() -> TypeName { TypeName::new(stringify!(Invoice)) } -} +}*/ pub struct ConfigWoChains { pub recipient: AccountId32, @@ -152,44 +155,50 @@ pub struct ConfigWoChains { pub rpc: String, } -pub struct DatabaseServer {} - -impl DatabaseServer { - pub fn init(path_option: Option) -> Result { - let builder = Database::builder(); - let is_new; +/// Database server handle +#[derive(Clone, Debug)] +pub struct Database { + tx: mpsc::Sender, +} +impl Database { + pub fn init(path_option: Option, task_tracker: TaskTracker) -> 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:?}."); - match File::create_new(&path) { - Ok(file) => { - is_new = true; - - FileBackend::new(file) - .and_then(|backend| builder.create_with_backend(backend)) - .map_err(ErrorDb::DbStartError)? - } - Err(error) if error.kind() == ErrorKind::AlreadyExists => { - is_new = false; - - builder.create(path).map_err(ErrorDb::DbStartError)? - } - Err(error) => return Err(error.into()), - } + sled::open(path).map_err(ErrorDb::DbStartError)?; } else { + // TODO + /* tracing::warn!( "The in-memory backend for the database is selected. All saved data will be deleted after the shutdown!" - ); + );*/ + let db = sled::open("temp.db").map_err(ErrorDb::DbStartError)?; + }; + + task_tracker.spawn("Database server", async move { + // No process forking beyond this point! + while let Some(request) = rx.recv().await { + match request { + DbRequest::CreateOrder => {} + }; + } - is_new = true; + Ok("Database server is shutting down".into()) + }); - builder - .create_with_backend(InMemoryBackend::new()) - .map_err(ErrorDb::DbStartError)? - }; //.context("failed to create/open the database")?; - Ok(Self {}) + Ok(Self { tx }) } + + pub async fn create_order(&self, order_query: OrderQuery) -> Result { + let (res, rx) = oneshot::channel(); + rx.await.map_err(|_| ErrorDb::DbEngineDown) + } +} + +enum DbRequest { + CreateOrder, } //impl StateInterface { diff --git a/src/definitions.rs b/src/definitions.rs new file mode 100644 index 0000000..daf4e00 --- /dev/null +++ b/src/definitions.rs @@ -0,0 +1,163 @@ +//! Deaf and dumb object definitions + +pub mod api_v2 { + use crate::{AssetId, BlockNumber, Decimals, ExtrinsicIndex}; + + use std::collections::HashMap; + + use serde::{Serialize, Serializer}; + + pub const AMOUNT: &str = "amount"; + pub const CURRENCY: &str = "currency"; + pub const CALLBACK: &str = "callback"; + + #[derive(Debug)] + pub struct OrderQuery { + pub order: String, + pub amount: f64, + pub callback: String, + pub currency: String, + } + + #[derive(Debug, Serialize)] + pub struct OrderStatus { + pub order: String, + pub payment_status: PaymentStatus, + pub message: String, + pub recipient: String, + pub server_info: ServerInfo, + #[serde(flatten)] + pub order_info: OrderInfo, + } + + #[derive(Debug, Serialize)] + pub struct OrderInfo { + pub withdrawal_status: WithdrawalStatus, + pub amount: f64, + pub currency: CurrencyInfo, + pub callback: String, + pub transactions: Vec, + pub payment_account: String, + } + + #[derive(Debug, Serialize)] + #[serde(rename_all = "lowercase")] + pub enum PaymentStatus { + Pending, + Paid, + Unknown, + } + + #[derive(Debug, Serialize)] + #[serde(rename_all = "lowercase")] + pub enum WithdrawalStatus { + Waiting, + Failed, + Completed, + } + + #[derive(Debug, Serialize)] + pub struct ServerStatus { + pub description: ServerInfo, + pub supported_currencies: HashMap, + } + + #[derive(Debug, Serialize)] + struct ServerHealth { + description: 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(Debug, Serialize)] + 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, + } + + #[derive(Clone, Debug, Serialize)] + 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, + } + + #[derive(Clone, Copy, Debug, Serialize)] + #[serde(rename_all = "lowercase")] + pub enum TokenKind { + Asset, + Balances, + } + + #[derive(Debug, Serialize)] + pub struct ServerInfo { + pub version: &'static str, + pub instance_id: String, + pub debug: bool, + pub kalatori_remark: String, + } + + #[derive(Debug, Serialize)] + pub struct TransactionInfo { + #[serde(skip_serializing_if = "Option::is_none", flatten)] + finalized_tx: Option, + transaction_bytes: String, + sender: String, + recipient: String, + #[serde(serialize_with = "amount_serializer")] + amount: Amount, + currency: CurrencyInfo, + status: TxStatus, + } + + #[derive(Debug, Serialize)] + struct FinalizedTx { + block_number: BlockNumber, + position_in_block: ExtrinsicIndex, + timestamp: String, + } + + #[derive(Debug)] + 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(Debug, Serialize)] + #[serde(rename_all = "lowercase")] + enum TxStatus { + Pending, + Finalized, + Failed, + } +} diff --git a/src/error.rs b/src/error.rs index aabd3d7..73098a8 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,10 +1,10 @@ -use crate::{server::OrderStatus, SocketAddr}; +use crate::{definitions::api_v2::OrderStatus, SocketAddr}; use frame_metadata::v15::RuntimeMetadataV15; use jsonrpsee::core::client::error::Error as ClientError; use mnemonic_external::error::ErrorWordList; use primitive_types::H256; -use redb::DatabaseError; use serde_json::Value; +use sled::Error as DatabaseError; use substrate_parser::error::*; use tokio::task::JoinError; diff --git a/src/main.rs b/src/main.rs index e595fc6..597ef24 100644 --- a/src/main.rs +++ b/src/main.rs @@ -26,6 +26,7 @@ mod asset; mod callback; mod chain; mod database; +mod definitions; mod error; mod rpc; mod server; @@ -138,6 +139,8 @@ async fn main() -> Result<(), Error> { .map_err(Error::RecipientAccount)? .0; + let db = database::Database::init(database_path, task_tracker.clone())?; + let state = State::initialise( currencies, pair, @@ -150,7 +153,8 @@ async fn main() -> Result<(), Error> { account_lifetime: config.account_lifetime, rpc: rpc.clone(), }, - &task_tracker, + db, + task_tracker.clone(), )?; task_tracker.spawn( diff --git a/src/rpc.rs b/src/rpc.rs index b2eb378..355ff05 100644 --- a/src/rpc.rs +++ b/src/rpc.rs @@ -1,8 +1,8 @@ use crate::{ chain::{base58prefix, pallet_index, storage_key, unit}, database::Invoicee, + definitions::api_v2::CurrencyProperties, error::{Error, ErrorChain, NotHex}, - server::CurrencyProperties, state::State, utils::unhex, AccountId32, AssetId, AssetInfo, Balance, BlockHash, BlockNumber, Chain, Decimals, NativeToken, diff --git a/src/server.rs b/src/server.rs index 5e53a9e..3ad74dc 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,4 +1,5 @@ use crate::{ + definitions::api_v2::*, error::{Error, ErrorOrder, ErrorServer}, state::State, AssetId, BlockNumber, Decimals, ExtrinsicIndex, @@ -18,160 +19,6 @@ use tokio_util::sync::CancellationToken; pub const MODULE: &str = module_path!(); -const AMOUNT: &str = "amount"; -const CURRENCY: &str = "currency"; -const CALLBACK: &str = "callback"; - -#[derive(Debug)] -pub struct OrderQuery { - pub order: String, - pub amount: f64, - pub callback: String, - pub currency: String, -} - -#[derive(Debug, Serialize)] -pub struct OrderStatus { - pub order: String, - pub payment_status: PaymentStatus, - pub message: String, - pub recipient: String, - pub server_info: ServerInfo, - #[serde(flatten)] - pub order_info: OrderInfo, -} - -#[derive(Debug, Serialize)] -pub struct OrderInfo { - pub withdrawal_status: WithdrawalStatus, - pub amount: f64, - pub currency: CurrencyInfo, - pub callback: String, - pub transactions: Vec, - pub payment_account: String, -} - -#[derive(Debug, Serialize)] -#[serde(rename_all = "lowercase")] -pub enum PaymentStatus { - Pending, - Paid, - Unknown, -} - -#[derive(Debug, Serialize)] -#[serde(rename_all = "lowercase")] -pub enum WithdrawalStatus { - Waiting, - Failed, - Completed, -} - -#[derive(Debug, Serialize)] -pub struct ServerStatus { - pub description: ServerInfo, - pub supported_currencies: HashMap, -} - -#[derive(Debug, Serialize)] -struct ServerHealth { - description: 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(Debug, Serialize)] -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, -} - -#[derive(Clone, Debug, Serialize)] -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, -} - -#[derive(Clone, Copy, Debug, Serialize)] -#[serde(rename_all = "lowercase")] -pub enum TokenKind { - Asset, - Balances, -} - -#[derive(Debug, Serialize)] -pub struct ServerInfo { - pub version: &'static str, - pub instance_id: String, - pub debug: bool, - pub kalatori_remark: String, -} - -#[derive(Debug, Serialize)] -pub struct TransactionInfo { - #[serde(skip_serializing_if = "Option::is_none", flatten)] - finalized_tx: Option, - transaction_bytes: String, - sender: String, - recipient: String, - #[serde(serialize_with = "amount_serializer")] - amount: Amount, - currency: CurrencyInfo, - status: TxStatus, -} - -#[derive(Debug, Serialize)] -struct FinalizedTx { - block_number: BlockNumber, - position_in_block: ExtrinsicIndex, - timestamp: String, -} - -#[derive(Debug)] -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(Debug, Serialize)] -#[serde(rename_all = "lowercase")] -enum TxStatus { - Pending, - Finalized, - Failed, -} - pub async fn new( shutdown_notification: CancellationToken, host: SocketAddr, diff --git a/src/state.rs b/src/state.rs index 01b5f5c..f8d3821 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,6 +1,7 @@ use crate::{ + database::Database, + definitions::api_v2::{CurrencyProperties, OrderQuery, OrderStatus, ServerInfo, ServerStatus}, error::Error, - server::{CurrencyProperties, OrderQuery, OrderStatus, ServerInfo, ServerStatus}, ConfigWoChains, TaskTracker, }; @@ -9,6 +10,8 @@ use std::collections::HashMap; use substrate_crypto_light::sr25519::Pair; use tokio::sync::oneshot; +/// 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 { pub tx: tokio::sync::mpsc::Sender, @@ -27,7 +30,8 @@ impl State { account_lifetime, rpc, }: ConfigWoChains, - task_tracker: &TaskTracker, + db: Database, + task_tracker: TaskTracker, ) -> Result { /* currencies: HashMap, @@ -41,6 +45,8 @@ impl State { rpc: String, */ let (tx, mut rx) = tokio::sync::mpsc::channel(1024); + + // Remember to always spawn async here or things might deadlock task_tracker.spawn("State Handler", async move { while let Some(request) = rx.recv().await { match request { From 1e0d32685936ba15eaa6f4a68027678d5f712d80 Mon Sep 17 00:00:00 2001 From: Slesarev Date: Thu, 25 Apr 2024 10:46:40 +0300 Subject: [PATCH 21/76] refactor: definitions module --- src/asset.rs | 2 +- src/callback.rs | 7 +-- src/database.rs | 5 +- src/definitions.rs | 127 +++++++++++++++++++++++++++++++++++++++++---- src/main.rs | 80 +--------------------------- src/rpc.rs | 5 +- src/server.rs | 2 +- src/state.rs | 1 + 8 files changed, 130 insertions(+), 99 deletions(-) diff --git a/src/asset.rs b/src/asset.rs index 809e857..36d5dbe 100644 --- a/src/asset.rs +++ b/src/asset.rs @@ -1,4 +1,4 @@ -use crate::{AssetId, PalletIndex}; +use crate::definitions::{api_v2::AssetId, PalletIndex}; use std::marker::PhantomData; #[derive(Clone, Debug)] diff --git a/src/callback.rs b/src/callback.rs index 77fdfaa..93e1b4d 100644 --- a/src/callback.rs +++ b/src/callback.rs @@ -1,12 +1,13 @@ use crate::{ - definitions::api_v2::{ + definitions::{api_v2::{ CurrencyInfo, OrderInfo, OrderStatus, PaymentStatus, ServerInfo, TokenKind, WithdrawalStatus, - }, - AccountId32, Balance, + }, Balance}, }; +use substrate_crypto_light::common::AccountId32; use tokio::task; + pub const MODULE: &str = module_path!(); pub async fn callback( diff --git a/src/database.rs b/src/database.rs index bf39a0b..fb8dcb5 100644 --- a/src/database.rs +++ b/src/database.rs @@ -5,13 +5,14 @@ //! are spawned here other than main database server thread that does everything in series. use crate::{ - definitions::api_v2::{CurrencyProperties, OrderQuery, OrderStatus, ServerInfo, ServerStatus}, + definitions::{api_v2::{AssetId, BlockNumber, CurrencyProperties, OrderQuery, OrderStatus, ServerInfo, ServerStatus}, Balance, Nonce, Timestamp}, error::{Error, ErrorDb}, - AccountId32, AssetId, Balance, BlockNumber, Nonce, TaskTracker, Timestamp, + TaskTracker, }; use parity_scale_codec::{Compact, Decode, Encode}; use serde::Deserialize; use std::{collections::HashMap, fs::File, io::ErrorKind}; +use substrate_crypto_light::common::AccountId32; use tokio::sync::{mpsc, oneshot}; pub const MODULE: &str = module_path!(); diff --git a/src/definitions.rs b/src/definitions.rs index daf4e00..8ea2986 100644 --- a/src/definitions.rs +++ b/src/definitions.rs @@ -1,15 +1,87 @@ //! Deaf and dumb object definitions +use std::ops::Deref; + +use serde::Deserialize; + +pub type Version = u64; +pub type Nonce = u32; +pub type Timestamp = u64; +pub type PalletIndex = u8; + +pub type BlockHash = primitive_types::H256; + +#[derive(Deserialize)] +#[serde(rename_all = "kebab-case")] +pub struct Chain { + name: String, + endpoints: Vec, + #[serde(flatten)] + native_token: Option, + asset: Option>, +} + +#[derive(Deserialize)] +#[serde(rename_all = "kebab-case")] +pub struct NativeToken { + native_token: String, + decimals: api_v2::Decimals, +} + +#[derive(Deserialize)] +pub struct AssetInfo { + name: String, + id: api_v2::AssetId, +} + +#[derive(Deserialize, Debug, Clone, Copy)] +pub struct Balance(u128); + +impl Deref for Balance { + type Target = u128; + + fn deref(&self) -> &Self::Target { + &self.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 crate::{AssetId, BlockNumber, Decimals, ExtrinsicIndex}; use std::collections::HashMap; + use parity_scale_codec::{Decode, Encode}; use serde::{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(Debug)] pub struct OrderQuery { @@ -22,7 +94,6 @@ pub mod api_v2 { #[derive(Debug, Serialize)] pub struct OrderStatus { pub order: String, - pub payment_status: PaymentStatus, pub message: String, pub recipient: String, pub server_info: ServerInfo, @@ -30,9 +101,10 @@ pub mod api_v2 { pub order_info: OrderInfo, } - #[derive(Debug, Serialize)] + #[derive(Debug, Serialize, Encode, Decode)] pub struct OrderInfo { pub withdrawal_status: WithdrawalStatus, + pub payment_status: PaymentStatus, pub amount: f64, pub currency: CurrencyInfo, pub callback: String, @@ -40,7 +112,21 @@ pub mod api_v2 { pub payment_account: String, } - #[derive(Debug, Serialize)] + impl OrderInfo { + pub fn new(query: OrderQuery, currency: CurrencyInfo, payment_account: String) -> Self { + OrderInfo { + withdrawal_status: WithdrawalStatus::Waiting, + payment_status: PaymentStatus::Pending, + amount: query.amount, + currency, + callback: query.callback, + transactions: Vec::new(), + payment_account, + } + } + } + + #[derive(Debug, Serialize, Decode, Encode)] #[serde(rename_all = "lowercase")] pub enum PaymentStatus { Pending, @@ -48,7 +134,7 @@ pub mod api_v2 { Unknown, } - #[derive(Debug, Serialize)] + #[derive(Debug, Serialize, Decode, Encode)] #[serde(rename_all = "lowercase")] pub enum WithdrawalStatus { Waiting, @@ -84,7 +170,7 @@ pub mod api_v2 { Critical, } - #[derive(Debug, Serialize)] + #[derive(Debug, Serialize, Decode, Encode)] pub struct CurrencyInfo { pub currency: String, pub chain_name: String, @@ -105,7 +191,7 @@ pub mod api_v2 { pub asset_id: Option, } - #[derive(Clone, Copy, Debug, Serialize)] + #[derive(Clone, Copy, Debug, Serialize, Decode, Encode)] #[serde(rename_all = "lowercase")] pub enum TokenKind { Asset, @@ -120,7 +206,7 @@ pub mod api_v2 { pub kalatori_remark: String, } - #[derive(Debug, Serialize)] + #[derive(Debug, Serialize, Decode, Encode)] pub struct TransactionInfo { #[serde(skip_serializing_if = "Option::is_none", flatten)] finalized_tx: Option, @@ -133,14 +219,14 @@ pub mod api_v2 { status: TxStatus, } - #[derive(Debug, Serialize)] + #[derive(Debug, Serialize, Decode, Encode)] struct FinalizedTx { block_number: BlockNumber, position_in_block: ExtrinsicIndex, timestamp: String, } - #[derive(Debug)] + #[derive(Debug, Decode, Encode)] enum Amount { All, Exact(f64), @@ -153,7 +239,7 @@ pub mod api_v2 { } } - #[derive(Debug, Serialize)] + #[derive(Debug, Serialize, Decode, Encode)] #[serde(rename_all = "lowercase")] enum TxStatus { Pending, @@ -161,3 +247,22 @@ pub mod api_v2 { Failed, } } + +#[cfg(test)] +#[test] +#[allow( + clippy::inconsistent_digit_grouping, + clippy::unreadable_literal, + clippy::float_cmp +)] + +fn balance_insufficient_precision() { + const DECIMALS: 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/main.rs b/src/main.rs index 597ef24..d260a0f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,5 @@ use mnemonic_external::{regular::InternalWordList, WordSet}; use substrate_crypto_light::{common::cut_path, sr25519::Pair}; - use serde::Deserialize; use std::{ borrow::Cow, @@ -34,6 +33,7 @@ mod state; mod utils; use database::ConfigWoChains; +use crate::definitions::{Chain, Version, Timestamp}; use error::Error; use rpc::Processor; use state::State; @@ -51,16 +51,6 @@ const DEFAULT_CONFIG: &str = "configs/polkadot.toml"; const DEFAULT_SOCKET: SocketAddr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 16726); const DEFAULT_DATABASE: &str = "kalatori.db"; -type AssetId = u32; -type Decimals = u8; -type BlockNumber = u64; -type ExtrinsicIndex = u32; -type Version = u64; -type Nonce = u32; -type Timestamp = u64; -type PalletIndex = u8; - -type BlockHash = primitive_types::H256; #[tokio::main] async fn main() -> Result<(), Error> { @@ -436,74 +426,6 @@ impl Config { } } -#[derive(Deserialize)] -#[serde(rename_all = "kebab-case")] -struct Chain { - name: String, - endpoints: Vec, - #[serde(flatten)] - native_token: Option, - asset: Option>, -} - -#[derive(Deserialize)] -#[serde(rename_all = "kebab-case")] -struct NativeToken { - native_token: String, - decimals: Decimals, -} - -#[derive(Deserialize)] -struct AssetInfo { - name: String, - id: AssetId, -} - -#[derive(Deserialize, Debug, Clone, Copy)] -struct Balance(u128); - -impl Deref for Balance { - type Target = u128; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl Balance { - fn format(&self, decimals: Decimals) -> f64 { - #[allow(clippy::cast_precision_loss)] - let float = **self as f64; - - float / decimal_exponent_product(decimals) - } - - fn parse(float: f64, decimals: Decimals) -> Self { - let parsed_float = (float * decimal_exponent_product(decimals)).round(); - - #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] - Self(parsed_float as _) - } -} - -fn decimal_exponent_product(decimals: Decimals) -> f64 { - 10f64.powi(decimals.into()) -} - -#[cfg(test)] -#[test] -#[allow( - clippy::inconsistent_digit_grouping, - clippy::unreadable_literal, - clippy::float_cmp -)] -fn balance_insufficient_precision() { - const DECIMALS: 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/rpc.rs b/src/rpc.rs index 355ff05..c6d3d2c 100644 --- a/src/rpc.rs +++ b/src/rpc.rs @@ -5,8 +5,8 @@ use crate::{ error::{Error, ErrorChain, NotHex}, state::State, utils::unhex, - AccountId32, AssetId, AssetInfo, Balance, BlockHash, BlockNumber, Chain, Decimals, NativeToken, - Nonce, PalletIndex, TaskTracker, Timestamp, + definitions::{api_v2::{AssetId, BlockNumber, Decimals, }, AssetInfo, Balance, BlockHash, Chain, NativeToken, Nonce, PalletIndex, Timestamp}, +TaskTracker, }; use frame_metadata::{v15::RuntimeMetadataV15, RuntimeMetadata}; use jsonrpsee::core::client::ClientT; @@ -23,6 +23,7 @@ use std::{ fmt::Debug, num::NonZeroU64, }; +use substrate_crypto_light::common::AccountId32; use substrate_parser::{ cards::{ExtendedData, ParsedData}, decode_all_as_type, AsMetadata, ShortSpecs, diff --git a/src/server.rs b/src/server.rs index 3ad74dc..3793228 100644 --- a/src/server.rs +++ b/src/server.rs @@ -2,7 +2,7 @@ use crate::{ definitions::api_v2::*, error::{Error, ErrorOrder, ErrorServer}, state::State, - AssetId, BlockNumber, Decimals, ExtrinsicIndex, + definitions::api_v2::{AssetId, BlockNumber, Decimals, ExtrinsicIndex,} }; use axum::{ extract::{self, rejection::RawPathParamsRejection, MatchedPath, Query, RawPathParams}, diff --git a/src/state.rs b/src/state.rs index f8d3821..898f262 100644 --- a/src/state.rs +++ b/src/state.rs @@ -8,6 +8,7 @@ use crate::{ use std::collections::HashMap; use substrate_crypto_light::sr25519::Pair; +use substrate_crypto_light::common::AccountId32; use tokio::sync::oneshot; /// Struct to store state of daemon. If something requires cooperation of more than one component, From 3c8a5894b5eb21e8ebbc7b68723ec1e33e001b5e Mon Sep 17 00:00:00 2001 From: Slesarev Date: Fri, 26 Apr 2024 10:00:17 +0300 Subject: [PATCH 22/76] feat: query order --- src/callback.rs | 8 ++-- src/database.rs | 94 +++++++++++++++++++++++++++++++++++++++++----- src/definitions.rs | 14 +++++-- src/error.rs | 15 ++++++++ src/main.rs | 12 +++--- src/rpc.rs | 10 +++-- src/server.rs | 26 ++++++------- src/state.rs | 43 ++++++++++++++++----- 8 files changed, 171 insertions(+), 51 deletions(-) diff --git a/src/callback.rs b/src/callback.rs index 93e1b4d..e63cc52 100644 --- a/src/callback.rs +++ b/src/callback.rs @@ -1,13 +1,13 @@ -use crate::{ - definitions::{api_v2::{ +use crate::definitions::{ + api_v2::{ CurrencyInfo, OrderInfo, OrderStatus, PaymentStatus, ServerInfo, TokenKind, WithdrawalStatus, - }, Balance}, + }, + Balance, }; use substrate_crypto_light::common::AccountId32; use tokio::task; - pub const MODULE: &str = module_path!(); pub async fn callback( diff --git a/src/database.rs b/src/database.rs index fb8dcb5..12eb4e7 100644 --- a/src/database.rs +++ b/src/database.rs @@ -5,7 +5,13 @@ //! are spawned here other than main database server thread that does everything in series. use crate::{ - definitions::{api_v2::{AssetId, BlockNumber, CurrencyProperties, OrderQuery, OrderStatus, ServerInfo, ServerStatus}, Balance, Nonce, Timestamp}, + definitions::{ + api_v2::{ + AssetId, BlockNumber, CurrencyProperties, OrderInfo, OrderQuery, PaymentStatus, + ServerInfo, ServerStatus, + }, + Balance, Nonce, Timestamp, + }, error::{Error, ErrorDb}, TaskTracker, }; @@ -46,6 +52,8 @@ const HIT_LIST: &str = "hit_list"; const DB_VERSION_KEY: &str = "db_version"; const DAEMON_INFO: &str = "daemon_info"; +const ORDERS: &[u8] = b"orders"; + // Slots type InvoiceKey = &'static [u8]; @@ -168,21 +176,29 @@ impl Database { let database = if let Some(path) = path_option { tracing::info!("Creating/Opening the database at {path:?}."); - sled::open(path).map_err(ErrorDb::DbStartError)?; + sled::open(path).map_err(ErrorDb::DbStartError)? } else { // TODO /* tracing::warn!( "The in-memory backend for the database is selected. All saved data will be deleted after the shutdown!" );*/ - let db = sled::open("temp.db").map_err(ErrorDb::DbStartError)?; + sled::open("temp.db").map_err(ErrorDb::DbStartError)? }; + let orders = database.open_tree(ORDERS).map_err(ErrorDb::DbStartError)?; task_tracker.spawn("Database server", async move { // No process forking beyond this point! while let Some(request) = rx.recv().await { match request { - DbRequest::CreateOrder => {} + DbRequest::CreateOrder(request) => { + request + .res + .send(create_order(request.order, request.order_info, &orders)); + } + DbRequest::ReadOrder(request) => { + request.res.send(read_order(request.order, &orders)); + } }; } @@ -192,14 +208,72 @@ impl Database { Ok(Self { tx }) } - pub async fn create_order(&self, order_query: OrderQuery) -> Result { + pub async fn create_order( + &self, + order: String, + order_info: OrderInfo, + ) -> Result { + let (res, rx) = oneshot::channel(); + self.tx.send(DbRequest::CreateOrder(CreateOrder { + order, + order_info, + res, + })); + rx.await.map_err(|_| ErrorDb::DbEngineDown)? + } + + pub async fn read_order(&self, order: String) -> Result, ErrorDb> { let (res, rx) = oneshot::channel(); - rx.await.map_err(|_| ErrorDb::DbEngineDown) + self.tx.send(DbRequest::ReadOrder(ReadOrder { order, res })); + rx.await.map_err(|_| ErrorDb::DbEngineDown)? } } enum DbRequest { - CreateOrder, + CreateOrder(CreateOrder), + ReadOrder(ReadOrder), +} + +pub struct CreateOrder { + pub order: String, + pub order_info: OrderInfo, + pub res: oneshot::Sender>, +} + +pub struct ReadOrder { + pub order: String, + pub res: oneshot::Sender, ErrorDb>>, +} + +fn create_order( + order: String, + order_info: OrderInfo, + orders: &sled::Tree, +) -> Result { + match orders.get(&order)? { + Some(record) => { + let old_order_info = OrderInfo::decode(&mut &record[..])?; + match order_info.payment_status { + PaymentStatus::Pending => { + let _ = orders.insert(order.encode(), order_info.encode())?; + Ok(order_info) + } + PaymentStatus::Paid => Ok(old_order_info), + } + } + None => { + orders.insert(order.encode(), order_info.encode())?; + Ok(order_info) + } + } +} + +fn read_order(order: String, orders: &sled::Tree) -> Result, ErrorDb> { + if let Some(order) = orders.get(order)? { + Ok(Some(OrderInfo::decode(&mut &order[..])?)) + } else { + Ok(None) + } } //impl StateInterface { @@ -238,7 +312,7 @@ enum DbRequest { OrderStatus { order, payment_status: PaymentStatus::Unknown, - message: String::new(), +message: String::new(), recipient: state.0.recipient.to_ss58check(), server_info: state.server_info(), order_info: OrderInfo { @@ -306,7 +380,7 @@ let pay_acc: AccountId = state supported_currencies: state.currencies.clone(), } */ - +/* #[derive(Deserialize, Debug)] pub struct Invoicee { pub callback: String, @@ -314,7 +388,7 @@ pub struct Invoicee { pub paid: bool, pub paym_acc: Account, } - +*/ /* */ diff --git a/src/definitions.rs b/src/definitions.rs index 8ea2986..c403f98 100644 --- a/src/definitions.rs +++ b/src/definitions.rs @@ -65,7 +65,6 @@ 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 { @@ -77,7 +76,7 @@ pub mod api_v2 { 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; @@ -91,6 +90,15 @@ pub mod api_v2 { 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, @@ -131,7 +139,6 @@ pub mod api_v2 { pub enum PaymentStatus { Pending, Paid, - Unknown, } #[derive(Debug, Serialize, Decode, Encode)] @@ -265,4 +272,3 @@ fn balance_insufficient_precision() { assert_eq!(*parsed, 931395_862219815_2); assert_eq!(parsed.format(DECIMALS), 931395.862219815_1); } - diff --git a/src/error.rs b/src/error.rs index 73098a8..1a73079 100644 --- a/src/error.rs +++ b/src/error.rs @@ -253,6 +253,21 @@ pub enum ErrorDb { #[error("Operating system related I/O error {0:?}")] IoError(std::io::Error), + + #[error("Database storage decoding error: {0:?}")] + CodecError(parity_scale_codec::Error), +} + +impl From for ErrorDb { + fn from(e: sled::Error) -> Self { + ErrorDb::DbInternalError(e) + } +} + +impl From for ErrorDb { + fn from(e: parity_scale_codec::Error) -> Self { + ErrorDb::CodecError(e) + } } impl From for ErrorDb { diff --git a/src/main.rs b/src/main.rs index d260a0f..b2d8663 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,4 @@ use mnemonic_external::{regular::InternalWordList, WordSet}; -use substrate_crypto_light::{common::cut_path, sr25519::Pair}; use serde::Deserialize; use std::{ borrow::Cow, @@ -13,6 +12,7 @@ use std::{ panic, str, }; use substrate_crypto_light::common::{AccountId32, AsBase58}; +use substrate_crypto_light::{common::cut_path, sr25519::Pair}; use tokio::{ signal, sync::mpsc::{self, UnboundedReceiver, UnboundedSender}, @@ -32,8 +32,8 @@ mod server; mod state; mod utils; +use crate::definitions::{Chain, Timestamp, Version}; use database::ConfigWoChains; -use crate::definitions::{Chain, Version, Timestamp}; use error::Error; use rpc::Processor; use state::State; @@ -51,7 +51,6 @@ const DEFAULT_CONFIG: &str = "configs/polkadot.toml"; const DEFAULT_SOCKET: SocketAddr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 16726); const DEFAULT_DATABASE: &str = "kalatori.db"; - #[tokio::main] async fn main() -> Result<(), Error> { let shutdown_notification = CancellationToken::new(); @@ -104,6 +103,8 @@ async fn main() -> Result<(), Error> { })) }; + let instance_id = String::from("TODO: add unique ID and save it in db"); + // Start services tracing::info!( @@ -144,6 +145,7 @@ async fn main() -> Result<(), Error> { rpc: rpc.clone(), }, db, + instance_id, task_tracker.clone(), )?; @@ -425,7 +427,3 @@ impl Config { toml::from_str(&unparsed_config).map_err(|_| Error::ConfigFileParse(config_path)) } } - - - - diff --git a/src/rpc.rs b/src/rpc.rs index c6d3d2c..29b34a4 100644 --- a/src/rpc.rs +++ b/src/rpc.rs @@ -1,12 +1,14 @@ use crate::{ chain::{base58prefix, pallet_index, storage_key, unit}, - database::Invoicee, definitions::api_v2::CurrencyProperties, + definitions::{ + api_v2::{AssetId, BlockNumber, Decimals}, + AssetInfo, Balance, BlockHash, Chain, NativeToken, Nonce, PalletIndex, Timestamp, + }, error::{Error, ErrorChain, NotHex}, state::State, utils::unhex, - definitions::{api_v2::{AssetId, BlockNumber, Decimals, }, AssetInfo, Balance, BlockHash, Chain, NativeToken, Nonce, PalletIndex, Timestamp}, -TaskTracker, + TaskTracker, }; use frame_metadata::{v15::RuntimeMetadataV15, RuntimeMetadata}; use jsonrpsee::core::client::ClientT; @@ -1139,11 +1141,13 @@ fn construct_transfer(to: &AccountId, amount: u128) -> Value { .into_value() } */ +/* #[derive(Debug)] struct InvoiceChanges { invoice: Invoicee, incoming: HashMap, } +*/ #[derive(Deserialize, Debug)] struct Transferred { diff --git a/src/server.rs b/src/server.rs index 3793228..bed71e7 100644 --- a/src/server.rs +++ b/src/server.rs @@ -2,7 +2,6 @@ use crate::{ definitions::api_v2::*, error::{Error, ErrorOrder, ErrorServer}, state::State, - definitions::api_v2::{AssetId, BlockNumber, Decimals, ExtrinsicIndex,} }; use axum::{ extract::{self, rejection::RawPathParamsRejection, MatchedPath, Query, RawPathParams}, @@ -60,7 +59,7 @@ async fn process_order( matched_path: &MatchedPath, path_result: Result, query: &HashMap, -) -> Result<(OrderStatus, OrderSuccess), ErrorOrder> { +) -> Result { const ORDER_ID: &str = "order_id"; let path_parameters = @@ -72,11 +71,10 @@ async fn process_order( .to_owned(); if query.is_empty() { - let order_status = state + state .order_status(&order) .await - .map_err(|_| ErrorOrder::InternalError)?; - Ok((order_status, OrderSuccess::Found)) + .map_err(|_| ErrorOrder::InternalError) } else { let get_parameter = |parameter: &str| { query @@ -98,7 +96,7 @@ async fn process_order( return Err(ErrorOrder::LessThanExistentialDeposit(0.07)); } - let order_status = state + state .create_order(OrderQuery { order, amount, @@ -106,9 +104,7 @@ async fn process_order( currency, }) .await - .map_err(|_| ErrorOrder::InternalError)?; - - Ok((order_status, OrderSuccess::Created)) + .map_err(|_| ErrorOrder::InternalError) } } @@ -120,11 +116,13 @@ async fn order( query: Query>, ) -> Response { match process_order(state, &matched_path, path_result, &query).await { - Ok((order_status, order_success)) => match order_success { - OrderSuccess::Created => (StatusCode::CREATED, Json(order_status)), - OrderSuccess::Found => (StatusCode::OK, Json(order_status)), - } - .into_response(), + 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 { ErrorOrder::LessThanExistentialDeposit(existential_deposit) => ( StatusCode::BAD_REQUEST, diff --git a/src/state.rs b/src/state.rs index 898f262..d8c0f22 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,14 +1,14 @@ use crate::{ database::Database, - definitions::api_v2::{CurrencyProperties, OrderQuery, OrderStatus, ServerInfo, ServerStatus}, + definitions::api_v2::{CurrencyProperties, OrderQuery, OrderResponse, OrderStatus, ServerInfo, ServerStatus}, error::Error, ConfigWoChains, TaskTracker, }; use std::collections::HashMap; -use substrate_crypto_light::sr25519::Pair; use substrate_crypto_light::common::AccountId32; +use substrate_crypto_light::sr25519::Pair; use tokio::sync::oneshot; /// Struct to store state of daemon. If something requires cooperation of more than one component, @@ -32,6 +32,7 @@ impl State { rpc, }: ConfigWoChains, db: Database, + instance_id: String, task_tracker: TaskTracker, ) -> Result { /* @@ -47,11 +48,22 @@ impl State { */ let (tx, mut rx) = tokio::sync::mpsc::channel(1024); + let recipient_ss58 = String::new(); // TODO ASAP + // Remember to always spawn async here or things might deadlock task_tracker.spawn("State Handler", async move { while let Some(request) = rx.recv().await { match request { - StateAccessRequest::GetInvoiceStatus(a) => {} + StateAccessRequest::GetInvoiceStatus(request) => { + let server_info = ServerInfo { // TODO + version: env!("CARGO_PKG_VERSION"), + instance_id: instance_id.clone(), + debug, + kalatori_remark: remark.clone(), + }; + + request.res.send(get_invoice_status(request.order, recipient_ss58.clone(), server_info, &db).await); + } StateAccessRequest::CreateInvoice(a) => {} StateAccessRequest::ServerStatus(res) => { let description = ServerInfo { @@ -75,7 +87,7 @@ impl State { Ok(Self { tx }) } - pub async fn order_status(&self, order: &str) -> Result { + pub async fn order_status(&self, order: &str) -> Result { let (res, rx) = oneshot::channel(); self.tx .send(StateAccessRequest::GetInvoiceStatus(GetInvoiceStatus { @@ -83,7 +95,7 @@ impl State { res, })) .await; - rx.await.map_err(|_| Error::Fatal) + rx.await.map_err(|_| Error::Fatal)? } pub async fn server_status(&self) -> Result { @@ -92,7 +104,7 @@ impl State { rx.await.map_err(|_| Error::Fatal) } - pub async fn create_order(&self, order_query: OrderQuery) -> Result { + pub async fn create_order(&self, order_query: OrderQuery) -> Result { let (res, rx) = oneshot::channel(); /* Invoicee { @@ -108,7 +120,7 @@ impl State { res, })) .await; - rx.await.map_err(|_| Error::Fatal) + rx.await.map_err(|_| Error::Fatal)? } pub fn interface(&self) -> Self { @@ -126,10 +138,23 @@ enum StateAccessRequest { struct GetInvoiceStatus { pub order: String, - pub res: oneshot::Sender, + pub res: oneshot::Sender>, } struct CreateInvoice { pub order_query: OrderQuery, - pub res: oneshot::Sender, + pub res: oneshot::Sender>, +} + +async fn get_invoice_status(order: String, recipient: String, server_info: ServerInfo, db: &Database) -> Result { + if let Some(order_info) = db.read_order(order.clone()).await? { + let message = String::new(); //TODO + Ok(OrderResponse::FoundOrder(OrderStatus { + order, + message, + recipient, + server_info, + order_info, + })) + } else {Ok(OrderResponse::NotFound)} } From 7c3c606cd7ac992d5e06a708d8ebf479e08def32 Mon Sep 17 00:00:00 2001 From: Slesarev Date: Fri, 26 Apr 2024 22:42:54 +0300 Subject: [PATCH 23/76] feat: create order --- src/chain.rs | 8 ++ src/database.rs | 19 ++-- src/definitions.rs | 59 +++++++++-- src/error.rs | 19 ++++ src/main.rs | 21 ++-- src/rpc.rs | 250 +++++++++++++++++++++++++++++++++++++++++++++ src/state.rs | 93 ++++++++++++----- 7 files changed, 413 insertions(+), 56 deletions(-) diff --git a/src/chain.rs b/src/chain.rs index 2008508..eac7051 100644 --- a/src/chain.rs +++ b/src/chain.rs @@ -14,6 +14,14 @@ use substrate_parser::{ cards::{ExtendedData, ParsedData}, decode_all_as_type, AsMetadata, ShortSpecs, }; +use substrate_crypto_light::common::{DeriveJunction, FullDerivation}; + +pub fn derivations<'a>(recipient: &'a str, order: &'a str) -> FullDerivation<'a> { + FullDerivation { + junctions: vec![DeriveJunction::hard(recipient), DeriveJunction::hard(order)], + password: None, + } +} pub fn hashed_key_element(data: &[u8], hasher: &StorageHasher) -> Vec { match hasher { diff --git a/src/database.rs b/src/database.rs index 12eb4e7..db0922d 100644 --- a/src/database.rs +++ b/src/database.rs @@ -7,7 +7,7 @@ use crate::{ definitions::{ api_v2::{ - AssetId, BlockNumber, CurrencyProperties, OrderInfo, OrderQuery, PaymentStatus, + AssetId, BlockNumber, CurrencyProperties, OrderCreateResponse, OrderInfo, OrderQuery, PaymentStatus, ServerInfo, ServerStatus, }, Balance, Nonce, Timestamp, @@ -212,7 +212,7 @@ impl Database { &self, order: String, order_info: OrderInfo, - ) -> Result { + ) -> Result { let (res, rx) = oneshot::channel(); self.tx.send(DbRequest::CreateOrder(CreateOrder { order, @@ -237,7 +237,7 @@ enum DbRequest { pub struct CreateOrder { pub order: String, pub order_info: OrderInfo, - pub res: oneshot::Sender>, + pub res: oneshot::Sender>, } pub struct ReadOrder { @@ -249,23 +249,24 @@ fn create_order( order: String, order_info: OrderInfo, orders: &sled::Tree, -) -> Result { - match orders.get(&order)? { +) -> Result { + Ok( + match orders.get(&order)? { Some(record) => { let old_order_info = OrderInfo::decode(&mut &record[..])?; match order_info.payment_status { PaymentStatus::Pending => { let _ = orders.insert(order.encode(), order_info.encode())?; - Ok(order_info) + OrderCreateResponse::Modified } - PaymentStatus::Paid => Ok(old_order_info), + PaymentStatus::Paid => OrderCreateResponse::Collision(old_order_info) } } None => { orders.insert(order.encode(), order_info.encode())?; - Ok(order_info) + OrderCreateResponse::New } - } + }) } fn read_order(order: String, orders: &sled::Tree) -> Result, ErrorDb> { diff --git a/src/definitions.rs b/src/definitions.rs index c403f98..c51e015 100644 --- a/src/definitions.rs +++ b/src/definitions.rs @@ -11,6 +11,8 @@ pub type PalletIndex = u8; pub type BlockHash = primitive_types::H256; +pub type Entropy = Vec; // TODO: maybe enforce something here + #[derive(Deserialize)] #[serde(rename_all = "kebab-case")] pub struct Chain { @@ -109,7 +111,7 @@ pub mod api_v2 { pub order_info: OrderInfo, } - #[derive(Debug, Serialize, Encode, Decode)] + #[derive(Clone, Debug, Serialize, Encode, Decode)] pub struct OrderInfo { pub withdrawal_status: WithdrawalStatus, pub payment_status: PaymentStatus, @@ -134,14 +136,20 @@ pub mod api_v2 { } } - #[derive(Debug, Serialize, Decode, Encode)] + pub enum OrderCreateResponse { + New, + Modified, + Collision(OrderInfo), + } + + #[derive(Clone, Debug, Serialize, Decode, Encode)] #[serde(rename_all = "lowercase")] pub enum PaymentStatus { Pending, Paid, } - #[derive(Debug, Serialize, Decode, Encode)] + #[derive(Clone, Debug, Serialize, Decode, Encode)] #[serde(rename_all = "lowercase")] pub enum WithdrawalStatus { Waiting, @@ -149,7 +157,7 @@ pub mod api_v2 { Completed, } - #[derive(Debug, Serialize)] + #[derive(Clone, Debug, Serialize)] pub struct ServerStatus { pub description: ServerInfo, pub supported_currencies: HashMap, @@ -177,7 +185,7 @@ pub mod api_v2 { Critical, } - #[derive(Debug, Serialize, Decode, Encode)] + #[derive(Clone, Debug, Serialize, Decode, Encode)] pub struct CurrencyInfo { pub currency: String, pub chain_name: String, @@ -186,6 +194,21 @@ pub mod api_v2 { 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)] @@ -196,6 +219,22 @@ pub mod api_v2 { 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)] @@ -205,7 +244,7 @@ pub mod api_v2 { Balances, } - #[derive(Debug, Serialize)] + #[derive(Clone, Debug, Serialize)] pub struct ServerInfo { pub version: &'static str, pub instance_id: String, @@ -213,7 +252,7 @@ pub mod api_v2 { pub kalatori_remark: String, } - #[derive(Debug, Serialize, Decode, Encode)] + #[derive(Clone, Debug, Serialize, Decode, Encode)] pub struct TransactionInfo { #[serde(skip_serializing_if = "Option::is_none", flatten)] finalized_tx: Option, @@ -226,14 +265,14 @@ pub mod api_v2 { status: TxStatus, } - #[derive(Debug, Serialize, Decode, Encode)] + #[derive(Clone, Debug, Serialize, Decode, Encode)] struct FinalizedTx { block_number: BlockNumber, position_in_block: ExtrinsicIndex, timestamp: String, } - #[derive(Debug, Decode, Encode)] + #[derive(Clone, Debug, Decode, Encode)] enum Amount { All, Exact(f64), @@ -246,7 +285,7 @@ pub mod api_v2 { } } - #[derive(Debug, Serialize, Decode, Encode)] + #[derive(Clone, Debug, Serialize, Decode, Encode)] #[serde(rename_all = "lowercase")] enum TxStatus { Pending, diff --git a/src/error.rs b/src/error.rs index 1a73079..3fc98bd 100644 --- a/src/error.rs +++ b/src/error.rs @@ -6,6 +6,7 @@ use primitive_types::H256; use serde_json::Value; use sled::Error as DatabaseError; use substrate_parser::error::*; +use substrate_crypto_light::error::Error as CryptoError; use tokio::task::JoinError; #[derive(Debug, thiserror::Error)] @@ -28,6 +29,9 @@ pub enum Error { #[error("Database error: {0:?}")] ErrorDb(ErrorDb), + #[error("Order error: {0:?}")] + ErrorOrder(ErrorOrder), + #[error("Daemon server error: {0:?}")] ErrorServer(ErrorServer), @@ -40,6 +44,9 @@ pub enum Error { #[error("Seed phrase invalid: {0:?}")] InvalidSeed(ErrorWordList), + #[error("Derivation failed: {0:?}")] + InvalidDerivation(CryptoError), + #[error("Fatal error. System is shutting down.")] Fatal, @@ -59,6 +66,12 @@ impl From for Error { } } +impl From for Error { + fn from(e: ErrorOrder) -> Error { + Error::ErrorOrder(e) + } +} + impl From for Error { fn from(e: ErrorServer) -> Error { Error::ErrorServer(e) @@ -77,6 +90,12 @@ impl From for Error { } } +impl From for Error { + fn from(e: CryptoError) -> Self { + Error::InvalidDerivation(e) + } +} + #[derive(Debug, thiserror::Error)] pub enum ErrorChain { #[error("Format of fetched base58 prefix {value} is not supported.")] diff --git a/src/main.rs b/src/main.rs index b2d8663..e9baebd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -32,7 +32,7 @@ mod server; mod state; mod utils; -use crate::definitions::{Chain, Timestamp, Version}; +use crate::definitions::{Chain, Entropy, Timestamp, Version}; use database::ConfigWoChains; use error::Error; use rpc::Processor; @@ -60,7 +60,7 @@ async fn main() -> Result<(), Error> { // Read env - let (pair, old_pairs) = parse_seeds()?; + let secret_entropy = parse_seeds()?; let recipient = env::var(RECIPIENT).map_err(|_| Error::Env(RECIPIENT.to_string()))?; let remark = env::var(REMARK).map_err(|_| Error::Env(REMARK.to_string()))?; @@ -134,8 +134,7 @@ async fn main() -> Result<(), Error> { let state = State::initialise( currencies, - pair, - old_pairs, + secret_entropy, ConfigWoChains { recipient: recipient.clone(), debug: config.debug, @@ -272,10 +271,10 @@ fn default_filter() -> String { filter } -fn parse_seeds() -> Result<(Pair, HashMap), Error> { - let pair = seed_from_phrase(&env::var(SEED).map_err(|_| Error::Env(SEED.to_string()))?)?; +fn parse_seeds() -> Result { + entropy_from_phrase(&env::var(SEED).map_err(|_| Error::Env(SEED.to_string()))?) - let mut old_pairs = HashMap::new(); + //let mut old_pairs = HashMap::new(); /* TODO: add this at least when you do something about these for (raw_key, raw_value) in env::vars_os() { let raw_key_bytes = raw_key.as_encoded_bytes(); @@ -292,18 +291,18 @@ fn parse_seeds() -> Result<(Pair, HashMap), Error> { } } */ - Ok((pair, old_pairs)) } -pub fn seed_from_phrase(seed: &str) -> Result { +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)?; } - let entropy = word_set.to_entropy()?; + Ok(word_set.to_entropy()?) + /* let derivation = cut_path("").expect("empty derivation is hardcoded"); Ok(Pair::from_entropy_and_full_derivation(&entropy, derivation) - .expect("empty derivation and password are hardcoded")) + .expect("empty derivation and password are hardcoded"))*/ } #[derive(Clone)] diff --git a/src/rpc.rs b/src/rpc.rs index 29b34a4..734b8be 100644 --- a/src/rpc.rs +++ b/src/rpc.rs @@ -1205,3 +1205,253 @@ where // Ok(()) // } // } +// +// + +/* +pub async fn assets_set_at_block(address: &str, block_hash: &str) -> Vec { + let metadata_v15 = metadata_v15(address, block_hash).await.unwrap(); + + let mut assets_set: Vec = Vec::new(); + + 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; + } + } + } + + let assets_asset_storage_metadata = assets_asset_storage_metadata.unwrap(); + let assets_metadata_storage_metadata = assets_metadata_storage_metadata.unwrap(); + + let available_keys_assets_asset = get_keys_from_storage(address, "Assets", "Asset", block_hash) + .await + .unwrap(); + 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(address, string_key, block_hash) + .await + .unwrap(); + if let Value::String(ref string_value) = value_fetch { + let key_data = hex::decode(string_key.trim_start_matches("0x")).unwrap(); + let value_data = hex::decode(string_value.trim_start_matches("0x")).unwrap(); + 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, + ) + .unwrap(); + 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 + { + //println!("got asset id: {value}"); + value + } else { + panic!("asset id not u32") + } + } else { + panic!("assets asset key has no parseable part") + } + } else { + panic!("assets asset key is not a single hash entity") + } + }; + 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(_) => { + panic!("expected map with single entry, got plain") + } + StorageEntryType::Map { + hashers, + key: key_ty, + value: value_ty, + } => { + if hashers.len() == 1 { + let hasher = &hashers[0]; + match metadata_v15.types.resolve(key_ty.id).unwrap().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( + address, + &key_assets_metadata, + block_hash, + ) + .await + .unwrap(); + if let Value::String(ref string_value) = value_fetch { + let value_data = hex::decode( + string_value.trim_start_matches("0x"), + ) + .unwrap(); + let value = decode_all_as_type::< + &[u8], + (), + RuntimeMetadataV15, + >( + value_ty, + &value_data.as_ref(), + &mut (), + &metadata_v15.types, + ) + .unwrap(); + + 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; + } + } + let name = name.unwrap(); + let symbol = symbol.unwrap(); + let decimals = decimals.unwrap(); + assets_set.push(Asset { + asset_id, + name, + symbol, + decimals, + }) + } else { + panic!("unexpected assets metadata value structure") + } + } + } + _ => panic!("wrong data type"), + } + } else { + panic!("expected map with single entry, got multiple entries") + } + } + } + } + } + } + } + } + assets_set +} +*/ diff --git a/src/state.rs b/src/state.rs index d8c0f22..a380300 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,13 +1,14 @@ use crate::{ + chain::derivations, database::Database, - definitions::api_v2::{CurrencyProperties, OrderQuery, OrderResponse, OrderStatus, ServerInfo, ServerStatus}, - error::Error, + definitions::{api_v2::{CurrencyProperties, OrderCreateResponse, OrderQuery, OrderInfo, OrderResponse, OrderStatus, ServerInfo, ServerStatus}, Entropy}, + error::{Error, ErrorOrder}, ConfigWoChains, TaskTracker, }; use std::collections::HashMap; -use substrate_crypto_light::common::AccountId32; +use substrate_crypto_light::common::{AccountId32, AsBase58}; use substrate_crypto_light::sr25519::Pair; use tokio::sync::oneshot; @@ -21,8 +22,7 @@ pub struct State { impl State { pub fn initialise( currencies: HashMap, - current_pair: Pair, - old_pairs: HashMap, + seed_entropy: Entropy, ConfigWoChains { recipient, debug, @@ -48,33 +48,37 @@ impl State { */ let (tx, mut rx) = tokio::sync::mpsc::channel(1024); - let recipient_ss58 = String::new(); // TODO ASAP + let recipient_ss58 = recipient.to_base58_string(2); //TODO - // Remember to always spawn async here or things might deadlock - task_tracker.spawn("State Handler", async move { - while let Some(request) = rx.recv().await { - match request { - StateAccessRequest::GetInvoiceStatus(request) => { - let server_info = ServerInfo { // TODO + let server_info = ServerInfo { // TODO version: env!("CARGO_PKG_VERSION"), instance_id: instance_id.clone(), debug, kalatori_remark: remark.clone(), }; - request.res.send(get_invoice_status(request.order, recipient_ss58.clone(), server_info, &db).await); + let state = StateData { + currencies, + recipient: recipient_ss58, + server_info, + db, + seed_entropy, + }; + + // Remember to always spawn async here or things might deadlock + task_tracker.spawn("State Handler", async move { + while let Some(request) = rx.recv().await { + match request { + StateAccessRequest::GetInvoiceStatus(request) => { + request.res.send(state.get_invoice_status(request.order).await); + } + StateAccessRequest::CreateInvoice(request) => { + request.res.send(state.create_invoice(request.order_query).await); } - StateAccessRequest::CreateInvoice(a) => {} StateAccessRequest::ServerStatus(res) => { - let description = ServerInfo { - version: env!("CARGO_PKG_VERSION"), - instance_id: String::new(), - debug, - kalatori_remark: remark.clone(), - }; let server_status = ServerStatus { - description, - supported_currencies: currencies.clone(), + description: state.server_info.clone(), + supported_currencies: state.currencies.clone(), }; res.send(server_status); } @@ -146,15 +150,52 @@ struct CreateInvoice { pub res: oneshot::Sender>, } -async fn get_invoice_status(order: String, recipient: String, server_info: ServerInfo, db: &Database) -> Result { - if let Some(order_info) = db.read_order(order.clone()).await? { +struct StateData { + currencies: HashMap, + recipient: String, + server_info: ServerInfo, + db: Database, + seed_entropy: Entropy, +} + +impl StateData { + 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, - server_info, + recipient: self.recipient.clone(), + server_info: self.server_info.clone(), order_info, })) } 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(ErrorOrder::UnknownCurrency)?; + let currency = currency.info(order_query.currency.clone()); + let payment_account = Pair::from_entropy_and_full_derivation(&self.seed_entropy, derivations(&self.recipient, &order_query.order))?.public().to_base58_string(currency.ss58); + let order_info = OrderInfo::new(order_query, currency, payment_account); + match self.db.create_order(order.clone(), order_info.clone()).await? { + OrderCreateResponse::New => + Ok(OrderResponse::NewOrder(self.order_status(order, order_info, String::new()))), + OrderCreateResponse::Modified => + Ok(OrderResponse::ModifiedOrder(self.order_status(order, order_info, String::new()))), + OrderCreateResponse::Collision(order_status) => Ok(OrderResponse::CollidedOrder(self.order_status(order, order_info, 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(), + server_info: self.server_info.clone(), + order_info, + } + } +} + + From 231736d026595ba0a53316f9f8e1fe176e570ee1 Mon Sep 17 00:00:00 2001 From: Slesarev Date: Sat, 27 Apr 2024 01:42:43 +0300 Subject: [PATCH 24/76] feat: assets data fetcher --- src/chain.rs | 2 +- src/error.rs | 13 +++++-- src/rpc.rs | 96 +++++++++++++++++++++++++++++----------------------- 3 files changed, 64 insertions(+), 47 deletions(-) diff --git a/src/chain.rs b/src/chain.rs index eac7051..45584fd 100644 --- a/src/chain.rs +++ b/src/chain.rs @@ -1,4 +1,4 @@ -//! Utils to process chain data +//! Utils to process chain data without accessing the chain //TransactionToFill::init(&mut (), metadata, genesis_hash).unwrap(); use crate::error::ErrorChain; diff --git a/src/error.rs b/src/error.rs index 3fc98bd..473b703 100644 --- a/src/error.rs +++ b/src/error.rs @@ -183,9 +183,6 @@ pub enum ErrorChain { #[error("Fetched values were not sent through successfully.")] NotSent, - #[error("Received QR payload is not a Substrate one.")] - NotSubstrate, - #[error("No unit value is fetched.")] NoUnit, @@ -242,6 +239,10 @@ pub enum ErrorChain { #[error("Aura slot duration could not be parsed as u64")] AuraSlotDurationFormat, + + #[error("Internal error: {0:?}")] // TODO this should be replaced by specific errors + ErrorUtil(ErrorUtil), + } impl From for ErrorChain { @@ -256,6 +257,12 @@ impl From for ErrorChain { } } +impl From for ErrorChain { + fn from(e: ErrorUtil) -> Self { + ErrorChain::ErrorUtil(e) + } +} + #[derive(Debug, thiserror::Error)] pub enum ErrorDb { #[error("Currency key is not found")] diff --git a/src/rpc.rs b/src/rpc.rs index 734b8be..6b9267e 100644 --- a/src/rpc.rs +++ b/src/rpc.rs @@ -1,8 +1,8 @@ use crate::{ - chain::{base58prefix, pallet_index, storage_key, unit}, + chain::{base58prefix, hashed_key_element, pallet_index, storage_key, system_properties_to_short_specs, unit}, definitions::api_v2::CurrencyProperties, definitions::{ - api_v2::{AssetId, BlockNumber, Decimals}, + api_v2::{AssetId, BlockNumber, CurrencyInfo, Decimals, TokenKind}, AssetInfo, Balance, BlockHash, Chain, NativeToken, Nonce, PalletIndex, Timestamp, }, error::{Error, ErrorChain, NotHex}, @@ -10,15 +10,16 @@ use crate::{ utils::unhex, TaskTracker, }; -use frame_metadata::{v15::RuntimeMetadataV15, RuntimeMetadata}; +use frame_metadata::{v15::{RuntimeMetadataV15, StorageEntryType}, RuntimeMetadata}; use jsonrpsee::core::client::ClientT; use jsonrpsee::rpc_params; use jsonrpsee::ws_client::{WsClient, WsClientBuilder}; -use parity_scale_codec::DecodeAll; +use parity_scale_codec::{Encode, Decode, DecodeAll}; use primitive_types::H256; -use scale_info::TypeDef; +use scale_info::{TypeDef, TypeDefPrimitive}; use serde::{Deserialize, Deserializer}; use serde_json::{Map, Number, Value}; +use sp_crypto_hashing::{blake2_128, blake2_256, twox_128, twox_256, twox_64}; use std::{ borrow::Cow, collections::{hash_map::Entry, HashMap}, @@ -27,8 +28,9 @@ use std::{ }; use substrate_crypto_light::common::AccountId32; use substrate_parser::{ - cards::{ExtendedData, ParsedData}, - decode_all_as_type, AsMetadata, ShortSpecs, + cards::{ExtendedData, ParsedData, Sequence}, + decode_all_as_type, decode_as_storage_entry, AsMetadata, ShortSpecs, + storage_data::{KeyData, KeyPart}, }; use tokio::sync::{mpsc, oneshot}; use tokio_util::sync::CancellationToken; @@ -272,29 +274,37 @@ async fn check_sufficiency_and_fetch_min_balance( }) }*/ -pub async fn storage_fetch( +pub async fn get_value_from_storage( client: &WsClient, - prefix: &str, - storage_name: &str, - block: &BlockHash, -) -> Result { - let key = storage_key(prefix, storage_name); - value_by_key_from_storage(client, &key, &hex::encode(block)).await + whole_key: &str, + block_hash: &str, +) -> Result> { + let value: Value = client + .request("state_getStorage", rpc_params![whole_key, block_hash]) + .await?; + Ok(value) } -pub async fn value_by_key_from_storage( +pub async fn get_keys_from_storage( client: &WsClient, - whole_key: &str, + prefix: &str, + storage_name: &str, block_hash: &str, -) -> Result { - let storage_value = client - .request("state_getStorage", rpc_params![whole_key, block_hash]) +) -> Result> { + let keys: Value = client + .request( + "state_getKeys", + rpc_params![ + format!( + "0x{}{}", + hex::encode(twox_128(prefix.as_bytes())), + hex::encode(twox_128(storage_name.as_bytes())) + ), + block_hash + ], + ) .await?; - if let Value::String(a) = storage_value { - Ok(a) - } else { - Err(ErrorChain::StorageValueFormat(whole_key.to_string())) - } + Ok(keys) } /* @@ -420,7 +430,7 @@ pub async fn prepare( Ok((connected_chains_jh.await?, currencies_jh.await?)) } - +*/ /// fetch genesis hash, must be a hexadecimal string transformable into /// H256 format async fn genesis_hash(client: &WsClient) -> Result { @@ -515,7 +525,7 @@ async fn specs(client: &WsClient, metadata: &RuntimeMetadataV15, block_hash: &Bl } } - +/* async fn state_call(client: &WsClient, params: RpcParams) -> Result { let res = client .request( @@ -523,12 +533,11 @@ async fn state_call(client: &WsClient, params: RpcParams) -> Result Vec { - let metadata_v15 = metadata_v15(address, block_hash).await.unwrap(); +pub async fn assets_set_at_block(client: &WsClient, block_hash: &str, metadata_v15: RuntimeMetadataV15, rpc_url: String, ss58: u16) -> HashMap { + //let metadata_v15 = metadata_v15(address, block_hash).await.unwrap(); - let mut assets_set: Vec = Vec::new(); + let mut assets_set = HashMap::new(); let mut assets_asset_storage_metadata = None; let mut assets_metadata_storage_metadata = None; @@ -1241,13 +1249,13 @@ pub async fn assets_set_at_block(address: &str, block_hash: &str) -> Vec let assets_asset_storage_metadata = assets_asset_storage_metadata.unwrap(); let assets_metadata_storage_metadata = assets_metadata_storage_metadata.unwrap(); - let available_keys_assets_asset = get_keys_from_storage(address, "Assets", "Asset", block_hash) + let available_keys_assets_asset = get_keys_from_storage(client, "Assets", "Asset", block_hash) .await .unwrap(); 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(address, string_key, block_hash) + let value_fetch = get_value_from_storage(client, string_key, block_hash) .await .unwrap(); if let Value::String(ref string_value) = value_fetch { @@ -1318,7 +1326,7 @@ pub async fn assets_set_at_block(address: &str, block_hash: &str) -> Vec )) ); let value_fetch = get_value_from_storage( - address, + client, &key_assets_metadata, block_hash, ) @@ -1426,15 +1434,18 @@ pub async fn assets_set_at_block(address: &str, block_hash: &str) -> Vec break; } } - let name = name.unwrap(); + //let name = name.unwrap(); let symbol = symbol.unwrap(); let decimals = decimals.unwrap(); - assets_set.push(Asset { - asset_id, - name, - symbol, - decimals, - }) + assets_set.insert(symbol, + CurrencyProperties { + chain_name: "TODO".into(), + kind: TokenKind::Asset, + decimals, + rpc_url: rpc_url.clone(), + asset_id: Some(asset_id), + ss58, + }); } else { panic!("unexpected assets metadata value structure") } @@ -1454,4 +1465,3 @@ pub async fn assets_set_at_block(address: &str, block_hash: &str) -> Vec } assets_set } -*/ From 805c139f8bdc2cbefa6454b82859d3d4c528f6e2 Mon Sep 17 00:00:00 2001 From: Slesarev Date: Mon, 29 Apr 2024 01:28:35 +0300 Subject: [PATCH 25/76] feat: basic db ops --- src/chain.rs | 2 +- src/database.rs | 97 +++- src/definitions.rs | 4 +- src/error.rs | 15 +- src/main.rs | 12 +- src/rpc.rs | 1107 ++++---------------------------------------- src/state.rs | 105 +++-- src/unused | 941 +++++++++++++++++++++++++++++++++++++ 8 files changed, 1221 insertions(+), 1062 deletions(-) create mode 100644 src/unused diff --git a/src/chain.rs b/src/chain.rs index 45584fd..16fb6a1 100644 --- a/src/chain.rs +++ b/src/chain.rs @@ -10,11 +10,11 @@ use parity_scale_codec::{Decode, Encode}; use scale_info::{TypeDef, TypeDefPrimitive}; use serde_json::{Map, Number, Value}; use sp_crypto_hashing::{blake2_128, blake2_256, twox_128, twox_256, twox_64}; +use substrate_crypto_light::common::{DeriveJunction, FullDerivation}; use substrate_parser::{ cards::{ExtendedData, ParsedData}, decode_all_as_type, AsMetadata, ShortSpecs, }; -use substrate_crypto_light::common::{DeriveJunction, FullDerivation}; pub fn derivations<'a>(recipient: &'a str, order: &'a str) -> FullDerivation<'a> { FullDerivation { diff --git a/src/database.rs b/src/database.rs index db0922d..efa2c79 100644 --- a/src/database.rs +++ b/src/database.rs @@ -7,8 +7,8 @@ use crate::{ definitions::{ api_v2::{ - AssetId, BlockNumber, CurrencyProperties, OrderCreateResponse, OrderInfo, OrderQuery, PaymentStatus, - ServerInfo, ServerStatus, + AssetId, BlockNumber, CurrencyProperties, OrderCreateResponse, OrderInfo, OrderQuery, + PaymentStatus, ServerInfo, ServerStatus, WithdrawalStatus, }, Balance, Nonce, Timestamp, }, @@ -199,6 +199,15 @@ impl Database { DbRequest::ReadOrder(request) => { request.res.send(read_order(request.order, &orders)); } + DbRequest::MarkPaid(request) => { + request.res.send(mark_paid(request.order, &orders)); + } + DbRequest::MarkWithdrawn(request) => { + request.res.send(mark_withdrawn(request.order, &orders)); + } + DbRequest::MarkStuck(request) => { + request.res.send(mark_stuck(request.order, &orders)); + } }; } @@ -227,11 +236,35 @@ impl Database { self.tx.send(DbRequest::ReadOrder(ReadOrder { order, res })); rx.await.map_err(|_| ErrorDb::DbEngineDown)? } + + pub async fn mark_paid(&self, order: String) -> Result<(), ErrorDb> { + let (res, rx) = oneshot::channel(); + self.tx + .send(DbRequest::MarkPaid(ModifyOrder { order, res })); + rx.await.map_err(|_| ErrorDb::DbEngineDown)? + } + + pub async fn mark_withdrawn(&self, order: String) -> Result<(), ErrorDb> { + let (res, rx) = oneshot::channel(); + self.tx + .send(DbRequest::MarkWithdrawn(ModifyOrder { order, res })); + rx.await.map_err(|_| ErrorDb::DbEngineDown)? + } + + pub async fn mark_stuck(&self, order: String) -> Result<(), ErrorDb> { + let (res, rx) = oneshot::channel(); + self.tx + .send(DbRequest::MarkStuck(ModifyOrder { order, res })); + rx.await.map_err(|_| ErrorDb::DbEngineDown)? + } } enum DbRequest { CreateOrder(CreateOrder), ReadOrder(ReadOrder), + MarkPaid(ModifyOrder), + MarkWithdrawn(ModifyOrder), + MarkStuck(ModifyOrder), } pub struct CreateOrder { @@ -245,13 +278,17 @@ pub struct ReadOrder { pub res: oneshot::Sender, ErrorDb>>, } +pub struct ModifyOrder { + pub order: String, + pub res: oneshot::Sender>, +} + fn create_order( order: String, order_info: OrderInfo, orders: &sled::Tree, ) -> Result { - Ok( - match orders.get(&order)? { + Ok(match orders.get(&order)? { Some(record) => { let old_order_info = OrderInfo::decode(&mut &record[..])?; match order_info.payment_status { @@ -259,7 +296,7 @@ fn create_order( let _ = orders.insert(order.encode(), order_info.encode())?; OrderCreateResponse::Modified } - PaymentStatus::Paid => OrderCreateResponse::Collision(old_order_info) + PaymentStatus::Paid => OrderCreateResponse::Collision(old_order_info), } } None => { @@ -277,6 +314,56 @@ fn read_order(order: String, orders: &sled::Tree) -> Result, E } } +fn mark_paid(order: String, orders: &sled::Tree) -> Result<(), ErrorDb> { + 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(()) + } else { + Err(ErrorDb::AlreadyPaid(order)) + } + } else { + Err(ErrorDb::OrderNotFound(order)) + } +} +fn mark_withdrawn(order: String, orders: &sled::Tree) -> Result<(), ErrorDb> { + 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(ErrorDb::WithdrawalWasAttempted(order)) + } + } else { + Err(ErrorDb::NotPaid(order)) + } + } else { + Err(ErrorDb::OrderNotFound(order)) + } +} +fn mark_stuck(order: String, orders: &sled::Tree) -> Result<(), ErrorDb> { + 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(ErrorDb::WithdrawalWasAttempted(order)) + } + } else { + Err(ErrorDb::NotPaid(order)) + } + } else { + Err(ErrorDb::OrderNotFound(order)) + } +} //impl StateInterface { /* Ok(( diff --git a/src/definitions.rs b/src/definitions.rs index c51e015..2b93546 100644 --- a/src/definitions.rs +++ b/src/definitions.rs @@ -142,14 +142,14 @@ pub mod api_v2 { Collision(OrderInfo), } - #[derive(Clone, Debug, Serialize, Decode, Encode)] + #[derive(Clone, Debug, Serialize, Decode, Encode, PartialEq)] #[serde(rename_all = "lowercase")] pub enum PaymentStatus { Pending, Paid, } - #[derive(Clone, Debug, Serialize, Decode, Encode)] + #[derive(Clone, Debug, Serialize, Decode, Encode, PartialEq)] #[serde(rename_all = "lowercase")] pub enum WithdrawalStatus { Waiting, diff --git a/src/error.rs b/src/error.rs index 473b703..7c890ae 100644 --- a/src/error.rs +++ b/src/error.rs @@ -5,8 +5,8 @@ use mnemonic_external::error::ErrorWordList; use primitive_types::H256; use serde_json::Value; use sled::Error as DatabaseError; -use substrate_parser::error::*; use substrate_crypto_light::error::Error as CryptoError; +use substrate_parser::error::*; use tokio::task::JoinError; #[derive(Debug, thiserror::Error)] @@ -242,7 +242,6 @@ pub enum ErrorChain { #[error("Internal error: {0:?}")] // TODO this should be replaced by specific errors ErrorUtil(ErrorUtil), - } impl From for ErrorChain { @@ -282,6 +281,18 @@ pub enum ErrorDb { #[error("Database storage decoding error: {0:?}")] CodecError(parity_scale_codec::Error), + + #[error("Order {0} not found")] + OrderNotFound(String), + + #[error("Order {0} was already paid")] + AlreadyPaid(String), + + #[error("Order {0} is not paid yet")] + NotPaid(String), + + #[error("There was already an attempt to withdraw order {0}")] + WithdrawalWasAttempted(String), } impl From for ErrorDb { diff --git a/src/main.rs b/src/main.rs index e9baebd..ad26b8d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -35,7 +35,7 @@ mod utils; use crate::definitions::{Chain, Entropy, Timestamp, Version}; use database::ConfigWoChains; use error::Error; -use rpc::Processor; +use rpc::ChainManager; use state::State; const CONFIG: &str = "KALATORI_CONFIG"; @@ -132,6 +132,8 @@ async fn main() -> Result<(), Error> { let db = database::Database::init(database_path, task_tracker.clone())?; + let chain_manager = ChainManager::ignite(config.chain, task_tracker.clone())?; + let state = State::initialise( currencies, secret_entropy, @@ -144,10 +146,12 @@ async fn main() -> Result<(), Error> { rpc: rpc.clone(), }, db, + chain_manager, instance_id, task_tracker.clone(), )?; + /* task_tracker.spawn( "proc", Processor::ignite( @@ -156,7 +160,7 @@ async fn main() -> Result<(), Error> { state.clone(), shutdown_notification.clone(), ), - ); + );*/ let server = server::new(shutdown_notification.clone(), host, state).await?; @@ -299,10 +303,6 @@ pub fn entropy_from_phrase(seed: &str) -> Result { word_set.add_word(&word, &InternalWordList)?; } Ok(word_set.to_entropy()?) - /* - let derivation = cut_path("").expect("empty derivation is hardcoded"); - Ok(Pair::from_entropy_and_full_derivation(&entropy, derivation) - .expect("empty derivation and password are hardcoded"))*/ } #[derive(Clone)] diff --git a/src/rpc.rs b/src/rpc.rs index 6b9267e..99ffe93 100644 --- a/src/rpc.rs +++ b/src/rpc.rs @@ -1,5 +1,8 @@ use crate::{ - chain::{base58prefix, hashed_key_element, pallet_index, storage_key, system_properties_to_short_specs, unit}, + chain::{ + base58prefix, hashed_key_element, pallet_index, storage_key, + system_properties_to_short_specs, unit, + }, definitions::api_v2::CurrencyProperties, definitions::{ api_v2::{AssetId, BlockNumber, CurrencyInfo, Decimals, TokenKind}, @@ -10,11 +13,14 @@ use crate::{ utils::unhex, TaskTracker, }; -use frame_metadata::{v15::{RuntimeMetadataV15, StorageEntryType}, RuntimeMetadata}; +use frame_metadata::{ + v15::{RuntimeMetadataV15, StorageEntryType}, + RuntimeMetadata, +}; use jsonrpsee::core::client::ClientT; use jsonrpsee::rpc_params; use jsonrpsee::ws_client::{WsClient, WsClientBuilder}; -use parity_scale_codec::{Encode, Decode, DecodeAll}; +use parity_scale_codec::{Decode, DecodeAll, Encode}; use primitive_types::H256; use scale_info::{TypeDef, TypeDefPrimitive}; use serde::{Deserialize, Deserializer}; @@ -29,8 +35,9 @@ use std::{ use substrate_crypto_light::common::AccountId32; use substrate_parser::{ cards::{ExtendedData, ParsedData, Sequence}, - decode_all_as_type, decode_as_storage_entry, AsMetadata, ShortSpecs, + decode_all_as_type, decode_as_storage_entry, storage_data::{KeyData, KeyPart}, + AsMetadata, ShortSpecs, }; use tokio::sync::{mpsc, oneshot}; use tokio_util::sync::CancellationToken; @@ -54,39 +61,6 @@ const BABE: &str = "Babe"; const AURA: &str = "AuraApi"; -/* -type ConnectedChainsChannel = ( - Sender>, - (String, ConnectedChain), -); - -type CurrenciesChannel = ( - Sender>, - (String, CurrencyProperties), -); - -struct AssetsInfoFetcher<'a> { - assets: (AssetInfo, Vec), - pallet_index: Option, -} -*/ -/* -async fn fetch_finalized_head_number_and_hash( - methods: &LegacyRpcMethods, -) -> Result<(BlockNumber, BlockHash)> { - let head_hash = methods - .chain_get_finalized_head() - .await - .context("failed to get the finalized head hash")?; - let head = 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)) -} -*/ #[derive(Debug)] struct ChainProperties { @@ -110,169 +84,7 @@ struct AssetProperties { min_balance: Balance, decimals: Decimals, } -/* -impl AssetProperties { - async fn fetch(asset: AssetId) -> Result { - Ok(Self { - min_balance: check_sufficiency_and_fetch_min_balance(asset).await?, - decimals: fetch_asset_decimals(asset).await?, - }) - } -}*/ -/* -impl ChainProperties { - async fn fetch( - chain: &str, - currencies: Sender, - native_token_option: Option, - assets_fetcher: Option>, - account_lifetime: BlockNumber, - depth: Option, - ) -> Result { - //const ADDRESS_PREFIX: (&str, &str) = (SYSTEM, "SS58Prefix"); - const EXISTENTIAL_DEPOSIT: "ExistentialDeposit";//(&str, &str) = (BALANCES, "ExistentialDeposit"); - const BLOCK_HASH_COUNT: "BlockHashCount";//(&str, &str) = (SYSTEM, "BlockHashCount"); - - /* wtf is this? - let try_add_currency = |name, asset| async move { - let (tx, rx) = oneshot::channel(); - - currencies - .send((tx, (name, CurrencyProperties { - chain_name: chain.to_owned(), - kind: , - decimals: , - rpc_url: , - asset_if: , - }))) - .unwrap(); - - if let Some(( - name, - Currency { - chain: other_chain, .. - }, - )) = rx.await.unwrap() - { - Err(anyhow::anyhow!( - "chain {other_chain:?} already has the native token or an asset with the name {name:?}, all currency names must be unique" - )) - } else { - Ok(()) - } - };*/ - - let specs = specs(); - - let assets_pallet = if let Some(AssetsInfoFetcher { - assets: (last_asset_info, assets_info), - storage, - pallet_index, - }) = assets_fetcher - { - async fn try_add_asset( - assets: &mut HashMap, - id: AssetId, - chain: &str, - ) -> Result<()> { - match assets.entry(id) { - Entry::Occupied(_) => Err(anyhow::anyhow!( - "chain {chain} has 2 assets with the same ID {id}", - )), - Entry::Vacant(entry) => { - entry.insert(AssetProperties::fetch(storage, id).await?); - - Ok(()) - } - } - } - - let mut assets = HashMap::with_capacity(assets_info.len().saturating_add(1)); - - for asset_info in assets_info { - //try_add_currency.clone()(asset_info.name, Some(asset_info.id)).await?; - try_add_asset(&mut assets, asset_info.id, chain, storage).await?; - } - - //try_add_currency.clone()(last_asset_info.name, Some(last_asset_info.id)).await?; - try_add_asset(&mut assets, last_asset_info.id, chain, storage).await?; - - Some(AssetsPallet { - assets, - multi_location: pallet_index, - }) - } else { - None - }; - - let block_hash_count = fetch_constant(constants, SYSTEM, BLOCK_HASH_COUNT)?; - //Some(native_token.decimals), - - let existential_deposit = if let Some(_native_token) = native_token_option { - Some(fetch_constant(metadata, BALANCES, ).map(Balance)?) - } else { - None - }; - - let chain = Self { - specs, - existential_deposit, - assets_pallet, - block_hash_count, - account_lifetime, - depth, - }; - - Ok(chain) - } -}*/ -/* -async fn check_sufficiency_and_fetch_min_balance( - client: &WsClient, - asset: AssetId, - block: BlockHash, -) -> Result { - const ASSET: &str = "Asset"; - const MIN_BALANCE: &str = "min_balance"; - const IS_SUFFICIENT: &str = "is_sufficient"; - - let asset_info = storage_fetch(client, ASSETS, ASSET, block); - /* - .fetch(&dynamic::storage(ASSETS, ASSET, vec![asset.into()])) - .await - .with_context(|| format!("failed to fetch asset {asset} info from a chain"))? - .with_context(|| { - format!("received nothing after fetching asset info {asset} from a chain") - })? - .to_value() - .with_context(|| format!("failed to decode asset {asset} info"))?; - */ - - let encoded_is_sufficient = asset_info - .at(IS_SUFFICIENT) - .with_context(|| format!("{IS_SUFFICIENT} field wasn't found in asset {asset} info"))?; - - if !encoded_is_sufficient.as_bool().with_context(|| { - format!( - "expected `bool` as the type of {IS_SUFFICIENT:?} in asset {asset} info, got `{:?}`", - encoded_is_sufficient.value - ) - })? { - anyhow::bail!("only sufficient assets are supported, asset {asset} isn't sufficient"); - } - - let encoded_min_balance = asset_info - .at(MIN_BALANCE) - .with_context(|| format!("{MIN_BALANCE} field wasn't found in asset {asset} info"))?; - - encoded_min_balance.as_u128().map(Balance).with_context(|| { - format!( - "expected `u128` as the type of {MIN_BALANCE:?} in asset {asset} info, got `{:?}`", - encoded_min_balance.value - ) - }) -}*/ pub async fn get_value_from_storage( client: &WsClient, @@ -307,130 +119,6 @@ pub async fn get_keys_from_storage( Ok(keys) } -/* -async fn fetch_asset_decimals( - client: &WsClient, - asset: AssetId, - block: &BlockHash, -) -> Result { - const METADATA: &str = "Metadata"; - const DECIMALS: &str = "decimals"; - - let asset_metadata = storage_fetch(client, ASSETS, METADATA, block); - /*storage - .fetch(&dynamic::storage(ASSETS, METADATA, vec![asset.into()])) - .await - .with_context(|| format!("failed to fetch asset {asset} metadata from a chain"))? - .with_context(|| { - format!("received nothing after fetching asset {asset} metadata from a chain") - })? - .to_value() - .with_context(|| format!("failed to decode asset {asset} metadata"))?;*/ - let encoded_decimals = asset_metadata - .at(DECIMALS) - .with_context(|| format!("{DECIMALS} field wasn't found in asset {asset} metadata"))?; - - let decimals = encoded_decimals.as_u128().with_context(|| { - format!( - "expected `u128` as the type of asset {asset} {DECIMALS:?}, got `{:?}`", - encoded_decimals.value - ) - })?; - - decimals.try_into().with_context(|| { - format!("asset {asset} {DECIMALS:?} must be less than `u8`, got {decimals}") - }) -}*/ -/* -pub async fn prepare( - chains: Vec, - account_lifetime: Timestamp, - depth: Option, -) -> Result<( - HashMap, - HashMap, -), ErrorChain> { - let mut connected_chains = HashMap::with_capacity(chains.len()); - let mut currencies = HashMap::with_capacity( - chains - .iter() - .map(|chain| { - chain - .asset - .as_ref() - .map(Vec::len) - .unwrap_or_default() - .saturating_add(chain.native_token.is_some().into()) - }) - .sum(), - ); - - let (connected_chains_tx, mut connected_chains_rx) = - mpsc::channel::(1024); - let (currencies_tx, mut currencies_rx) = mpsc::channel::(1024); - - let connected_chains_jh = tokio::spawn(async move { - while let Some((tx, (name, chain))) = connected_chains_rx.recv().await { - tx.send(match connected_chains.entry(name) { - Entry::Occupied(entry) => Some(entry.remove_entry()), - Entry::Vacant(entry) => { - tracing::info!("Prepared the {:?} chain:\n{:#?}", entry.key(), chain); - - entry.insert(chain); - - None - } - }) - .unwrap(); - } - - connected_chains - }); - - let currencies_jh = tokio::spawn(async move { - while let Some((tx, (name, currency))) = currencies_rx.recv().await { - tx.send(match currencies.entry(name) { - Entry::Occupied(entry) => Some(entry.remove_entry()), - Entry::Vacant(entry) => { - tracing::info!( - %currency.chain_name, ?currency.asset_id, - "Registered the currency {:?}.", - entry.key(), - ); - - entry.insert(currency); - - None - } - }) - .unwrap(); - } - - currencies - }); - - let (task_tracker, error_rx) = TaskTracker::new(); - - for chain in chains { - task_tracker.spawn( - format!("the {:?} chain preparator", chain.name), - prepare_chain( - chain, - connected_chains_tx.clone(), - currencies_tx.clone(), - account_lifetime, - depth, - ), - ); - } - - drop((connected_chains_tx, currencies_tx)); - - task_tracker.try_wait(error_rx).await?; - - Ok((connected_chains_jh.await?, currencies_jh.await?)) -} -*/ /// fetch genesis hash, must be a hexadecimal string transformable into /// H256 format async fn genesis_hash(client: &WsClient) -> Result { @@ -449,314 +137,86 @@ async fn genesis_hash(client: &WsClient) -> Result { .try_into() .map_err(|_| ErrorChain::GenesisHashLength)?, )) - }, + } _ => return Err(ErrorChain::GenesisHashFormat), } - } /// fetch current block hash, to request later the metadata and specs for /// the same block async fn block_hash(client: &WsClient) -> Result { - let block_hash_request: Value = client - .request("chain_getBlockHash", rpc_params![]) - .await - .map_err(ErrorChain::Client)?; - match block_hash_request { - Value::String(x) => { - let block_hash_raw = unhex(&x, NotHex::BlockHash)?; + let block_hash_request: Value = client + .request("chain_getBlockHash", rpc_params![]) + .await + .map_err(ErrorChain::Client)?; + match block_hash_request { + Value::String(x) => { + let block_hash_raw = unhex(&x, NotHex::BlockHash)?; Ok(H256( block_hash_raw .try_into() .map_err(|_| ErrorChain::BlockHashLength)?, )) - } - _ => return Err(ErrorChain::BlockHashFormat), } + _ => return Err(ErrorChain::BlockHashFormat), + } } /// fetch metadata at known block async fn metadata(client: &WsClient, block: &BlockHash) -> Result { let block_hash_string = block.to_string(); - let metadata_request: Value = client - .request( - "state_call", - rpc_params![ - "Metadata_metadata_at_version", - "0f000000", - &block_hash_string - ], - ) - .await - .map_err(ErrorChain::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(|_| ErrorChain::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(ErrorChain::NoMetadataV15), - Err(_) => return Err(ErrorChain::MetadataNotDecodeable), - } - } else { - return Err(ErrorChain::NoMetaPrefix); - } - } else { - return Err(ErrorChain::NoMetadataV15); - } - } - _ => return Err(ErrorChain::MetadataFormat), - }; -} - -// fetch specs at known block -async fn specs(client: &WsClient, metadata: &RuntimeMetadataV15, block_hash: &BlockHash) -> Result { - let specs_request: Value = client - .request("system_properties", rpc_params![hex::encode(&block_hash.0)]) - .await?; - //.map_err(ErrorChain::Client)?; - match specs_request { - Value::Object(properties) => system_properties_to_short_specs(&properties, &metadata), - _ => return Err(ErrorChain::PropertiesFormat), - } -} - -/* -async fn state_call(client: &WsClient, params: RpcParams) -> Result { - let res = client + let metadata_request: Value = client .request( "state_call", - params, + rpc_params![ + "Metadata_metadata_at_version", + "0f000000", + &block_hash_string + ], ) .await .map_err(ErrorChain::Client)?; - if let Value::String(a) = res { Ok(a) } else { Err(ErrorChain::StateCallResponse(res)) } -} - - -#[tracing::instrument(skip_all, fields(chain = chain.name))] -async fn prepare_chain( - chain: Chain, - connected_chains: Sender, - currencies: Sender, - account_lifetime: Timestamp, - depth_option: Option, -) -> Result, ErrorChain> { - let chain_name = chain.name; - let endpoint = chain - .endpoints - .first().ok_or(ErrorChain::EmptyEndpoints)?; - let client = WsClientBuilder::default() - .build(&endpoint) - .await - .map_err(ErrorChain::Client)?; - - let genesis = genesis_hash(&client).await?; - - let finalized_hash = block_hash(&client).await?; - let block_hash_string = finalized_hash.to_string(); - let metadata = metadata(&client, &finalized_hash).await?; - let specs = specs(&client, &metadata, &finalized_hash).await?; - - //TODO: we don't have to require this mechanism at all - let block_time = if pallet_index(&metadata, BABE).is_some() { - match fetch_constant(&metadata, BABE, "ExpectedBlockTime").ok_or(ErrorChain::BabeExpectedBlockTime)?.data { - - } - } else { - //const SLOT_DURATION: &str = "slot_duration"; - - state_call( - &client, - rpc_params![ - "Aura_slot_duration", - &block_hash_string - ], - ) - .await? - .parse::() - .map_err(|_| ErrorChain::AuraSlotDurationFormat)? - - /* - ( - runtime_api - .at(finalized_hash) - .call(dynamic::runtime_api_call( - AURA, - SLOT_DURATION, - Vec::::new(), - )) - .await - .context("failed to fetch Aura's slot duration")? - .as_type() - .context("failed to decode Aura's slot duration")?, - Some(runtime_api), - )*/ - }; - - let block_time_non_zero = - NonZeroU64::new(block_time).ok_or(ErrorChain::ZeroBlockTime)?;//.context("block interval can't equal 0")?; - - let account_lifetime_in_blocks = account_lifetime / block_time_non_zero; -/* TODO - if account_lifetime_in_blocks == 0 { - anyhow::bail!("block interval is longer than the given `account-lifetime`"); - } -*/ -/* - let depth_in_blocks = if let Some(depth) = depth_option { - let depth_in_blocks = depth / block_time_non_zero; - - if depth_in_blocks > account_lifetime_in_blocks { - anyhow::bail!("`depth` can't be greater than `account-lifetime`"); - } - - Some( - NonZeroU64::new(depth_in_blocks) - .context("block interval is longer than the given `depth`")?, - ) - } else { - None - }; -*/ - let rpc = endpoint.into(); -/* - let assets_info_fetcher = if let Some(assets) = chain - .asset - .and_then(|mut assets| assets.pop().map(|latest| (latest, assets))) - { - const ASSET_ID: &str = "asset_id"; - const SOME: &str = "Some"; - - let extension = metadata - .extrinsic() - .signed_extensions() - .iter() - .find(|extension| extension.identifier() == CHARGE_ASSET_TX_PAYMENT) - .with_context(|| { - format!("failed to find the {CHARGE_ASSET_TX_PAYMENT:?} extension in metadata") - })? - .extra_ty(); - let types = metadata.types(); - - let TypeDef::Composite(ref extension_type) = types - .resolve(extension);/* - .with_context(|| { - format!("failed to resolve the type of the {CHARGE_ASSET_TX_PAYMENT:?} extension") - })? - .type_def - else { - anyhow::bail!("{CHARGE_ASSET_TX_PAYMENT:?} extension has an unexpected type"); - };*/ - - let asset_id_field = extension_type - .fields - .iter() - .find_map(|field| { - field - .name - .as_ref() - .and_then(|name| (name == ASSET_ID).then_some(field.ty.id)) - });/* - .with_context(|| { - format!( - "failed to find the field {ASSET_ID:?} in the {CHARGE_ASSET_TX_PAYMENT:?} extension" - ) - })?;*/ - - let TypeDef::Variant(ref option) = types.resolve(asset_id_field); - /*.with_context(|| { - format!( - "failed to resolve the type of the field {ASSET_ID:?} in the {CHARGE_ASSET_TX_PAYMENT:?} extension" - ) - })?.type_def else { - anyhow::bail!( - "field {ASSET_ID:?} in the {CHARGE_ASSET_TX_PAYMENT:?} extension has an unexpected type" - ); - };*/ - - let asset_id_some = option.variants.iter().find_map(|variant| { - if variant.name == SOME { - variant.fields.first().map(|field| { - if variant.fields.len() > 1 { - tracing::warn!( - ?variant.fields, - "The field {ASSET_ID:?} in the {CHARGE_ASSET_TX_PAYMENT:?} extension contains multiple inner fields instead of just 1." - ); + 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(|_| ErrorChain::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(ErrorChain::NoMetadataV15), + Err(_) => return Err(ErrorChain::MetadataNotDecodeable), } - - field.ty.id - }) + } else { + return Err(ErrorChain::NoMetaPrefix); + } } else { - None + return Err(ErrorChain::NoMetadataV15); } - })/*.with_context(|| format!( - "field {ASSET_ID:?} in the {CHARGE_ASSET_TX_PAYMENT:?} extension doesn't contain the {SOME:?} variant" - ))?*/; - - let asset_id = &types.resolve(asset_id_some);/*.with_context(|| { - format!( - "failed to resolve the type of the {SOME:?} variant of the field {ASSET_ID:?} in the {CHARGE_ASSET_TX_PAYMENT:?} extension" - ) - })?.type_def;*/ - - let pallet_index = if let TypeDef::Primitive(_) = asset_id { - None - } else { - pallet_index(metadata, ASSETS) - }; - Some(AssetsInfoFetcher { - assets, - pallet_index, - }) - } else { - None - }; - - let storage = if assets_info_fetcher.is_some() { - Some(storage_client) - } else { - None - }; -*/ - /* - let properties = ChainProperties::fetch( - &chain_name, - currencies, - chain.native_token, - assets_info_fetcher, - account_lifetime_in_blocks, - depth_in_blocks, - ) - .await?; - - let connected_chain = ConnectedChain { - //methods, - genesis, - rpc, - client, - properties, + } + _ => return Err(ErrorChain::MetadataFormat), }; +} - let (tx, rx) = oneshot::channel(); - - connected_chains - .send((tx, (chain_name, connected_chain))) - .unwrap(); - - if let Some((name, _)) = rx.await.unwrap() { - anyhow::bail!( - "found `[chain]`s with the same name ({name:?}) in the config, all chain names must be unique", - ); +// fetch specs at known block +async fn specs( + client: &WsClient, + metadata: &RuntimeMetadataV15, + block_hash: &BlockHash, +) -> Result { + let specs_request: Value = client + .request("system_properties", rpc_params![hex::encode(&block_hash.0)]) + .await?; + //.map_err(ErrorChain::Client)?; + match specs_request { + Value::Object(properties) => system_properties_to_short_specs(&properties, &metadata), + _ => return Err(ErrorChain::PropertiesFormat), } -*/ - Ok("".into()) } -*/ + #[derive(Debug)] pub struct Currency { chain: String, @@ -771,392 +231,43 @@ pub struct ConnectedChain { properties: ChainProperties, } -//#[derive(Debug)] -//struct Shutdown; - -//impl Error for Shutdown {} - -pub struct Processor { - state: State, - recipient: AccountId32, - shutdown_notification: CancellationToken, +/// RPC server handle +#[derive(Clone, Debug)] +pub struct ChainManager { + pub tx: tokio::sync::mpsc::Sender, } -impl Processor { - pub async fn ignite( - rpc: String, - recipient: AccountId32, - state: State, - notif: CancellationToken, - ) -> Result, Error> { +impl ChainManager { + pub fn ignite( + chain: Vec, + task_tracker: TaskTracker, + ) -> Result { + /* let client = WsClientBuilder::default() .build(rpc.clone()) .await .map_err(ErrorChain::Client)?; +*/ - Processor { - state, - recipient, - shutdown_notification: notif, - }; - Ok("The RPC module is shut down.".into()) - /* - .execute() - .await - .or_else(|error| { - error - .downcast() - .map(|Shutdown| "The RPC module is shut down.".into()) - })*/ - } - /* - async fn execute(mut self) -> Result> { - let (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; + let (tx, mut rx) = mpsc::channel(1024); - next_unscanned_number = head_number.checked_add(1).context(MAX_BLOCK_NUMBER_ERROR)?; - subscription = self.finalized_heads().await?; + //let watch_chain: HashMap> = chain.iter().map(|a| (a.name, a.endpoints)).into() - loop { - self.process_finalized_heads(subscription, &mut next_unscanned_number) - .await?; + task_tracker.spawn("Blockchain connections manager", async move { + - tracing::warn!("Lost the connection while processing finalized heads. Retrying..."); + Ok("Chain manager is shutting down".into()) + }); - 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, BlockHash)> { - 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(Self{tx}) - Ok((head.block.header.number, head_hash)) } - - 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: BlockHash) -> Result<()> { - tracing::debug!("Processing the 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 invoices = &mut *self.state.invoices.write().await; - - // 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(); - - #[allow(clippy::single_match)] - match (metadata.pallet.name(), &*metadata.variant.name) { - // (SYSTEM, UPDATE) => update = true, - (ASSETS, TRANSFERRED) => { - let tr = Transferred::deserialize( - event - .field_values() - .context("failed to decode event's fields")?, - ) - .context("failed to deserialize a transfer event")?; - - tracing::info!("{tr:?}"); - /* TODO process using cache and db access - #[allow(clippy::unnecessary_find_map)] - if let Some(invoic) = invoices.iter().find_map(|invoic| { - tracing::info!("{tr:?} {invoic:?}"); - tracing::info!("{}", tr.to == invoic.1.paym_acc); - tracing::info!("{}", *invoic.1.amount >= tr.amount); - - if tr.to == invoic.1.paym_acc && *invoic.1.amount <= tr.amount { - Some(invoic) - } else { - None - } - }) { - tracing::info!("{invoic:?}"); - - if !invoic.1.callback.is_empty() { - tracing::info!("{:?}", invoic.1.callback); - - crate::callback::callback( - invoic.1.callback.clone(), - invoic.0.to_string(), - self.state.recipient.clone(), - self.state.debug, - self.state.remark.clone(), - invoic.1.amount, - self.state.rpc.clone(), - invoic.1.paym_acc.clone(), - ) - .await; - } - - invoices.insert( - invoic.0.clone(), - Invoicee { - callback: invoic.1.callback.clone(), - amount: Balance(*invoic.1.amount), - paid: true, - paym_acc: invoic.1.paym_acc.clone(), - }, - ); - }*/ - } - _ => {} - } - } - - // 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")?; - // } - - Ok(()) - } - - async fn balance(&self, hash: BlockHash, account: &AccountId) -> Result { - const ACCOUNT: &str = "Account"; - const BALANCE: &str = "balance"; - /* TODO: this should fetch balance and also considet native-nonnative shit wtf is this? - if let Some(account_info) = self - .storage - .at(hash) - .fetch(&dynamic::storage( - ASSETS, - ACCOUNT, - vec![ - Value::from(1337u32), - 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().map(Balance).with_context(|| { - format!("expected `u128` as the type of a balance, got {encoded_balance}") - }) - } else { - Ok(Balance(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, Asset::Id(1337)); - */ - //TODO create tx - /* - self.client - .tx() - .create_signed(&call, signer, extensions.build()) - .await - .context("failed to create a transfer transaction") - */ - } - */ - // async fn current_nonce(&self, account: &AccountId) -> Result { - // self.api - // .blocks - // .at(self.finalized_head_number_and_hash().await?.0) - // .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: BlockHash, - // invoice: AccountId, - // 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: &AccountId, amount: u128) -> Value { - const TRANSFER_KEEP_ALIVE: &str = "transfer"; - dbg!(amount); +enum ChainRequest { - dynamic::tx( - ASSETS, - TRANSFER_KEEP_ALIVE, - vec![ - 1337.into(), - scale_value::value!(Id(Value::from_bytes(to))), - amount.into(), - ], - ) - .into_value() } -*/ -/* -#[derive(Debug)] -struct InvoiceChanges { - invoice: Invoicee, - incoming: HashMap, -} -*/ + #[derive(Deserialize, Debug)] struct Transferred { @@ -1176,48 +287,18 @@ where <([u8; 32],)>::deserialize(deserializer).map(|address| AccountId32(address.0)) } -// impl Transferred { -// fn process( -// self, -// invoices_changes: &mut HashMap, -// invoices: &mut HashMap, -// ) -> Result<()> { -// let usd_asset = 1337u32; - -// tracing::debug!("Transferred event: {self:?}"); - -// if self.from == self.to || self.amount == 0 || self.asset_id != usd_asset { -// return Ok(()); -// } - -// match invoices_changes.entry(self.to) { -// Entry::Occupied(entry) => { -// entry -// .into_mut() -// .incoming -// .entry(self.from) -// .and_modify(|amount| *amount = Balance(amount.saturating_add(self.amount))) -// .or_insert(Balance(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(()) -// } -// } +// TODO: add proper errors // +// not urgent since it happens on boot once for now // - -pub async fn assets_set_at_block(client: &WsClient, block_hash: &str, metadata_v15: RuntimeMetadataV15, rpc_url: String, ss58: u16) -> HashMap { +/// Get all sufficient assets from a chain +pub async fn assets_set_at_block( + client: &WsClient, + block_hash: &str, + metadata_v15: RuntimeMetadataV15, + rpc_url: String, + ss58: u16, +) -> HashMap { //let metadata_v15 = metadata_v15(address, block_hash).await.unwrap(); let mut assets_set = HashMap::new(); @@ -1437,7 +518,8 @@ pub async fn assets_set_at_block(client: &WsClient, block_hash: &str, metadata_v //let name = name.unwrap(); let symbol = symbol.unwrap(); let decimals = decimals.unwrap(); - assets_set.insert(symbol, + assets_set.insert( + symbol, CurrencyProperties { chain_name: "TODO".into(), kind: TokenKind::Asset, @@ -1445,7 +527,8 @@ pub async fn assets_set_at_block(client: &WsClient, block_hash: &str, metadata_v rpc_url: rpc_url.clone(), asset_id: Some(asset_id), ss58, - }); + }, + ); } else { panic!("unexpected assets metadata value structure") } diff --git a/src/state.rs b/src/state.rs index a380300..d8bfeff 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,8 +1,15 @@ use crate::{ chain::derivations, database::Database, - definitions::{api_v2::{CurrencyProperties, OrderCreateResponse, OrderQuery, OrderInfo, OrderResponse, OrderStatus, ServerInfo, ServerStatus}, Entropy}, + definitions::{ + api_v2::{ + CurrencyProperties, OrderCreateResponse, OrderInfo, OrderQuery, OrderResponse, + OrderStatus, ServerInfo, ServerStatus, + }, + Entropy, + }, error::{Error, ErrorOrder}, + rpc::ChainManager, ConfigWoChains, TaskTracker, }; @@ -32,6 +39,7 @@ impl State { rpc, }: ConfigWoChains, db: Database, + chain_manager: ChainManager, instance_id: String, task_tracker: TaskTracker, ) -> Result { @@ -50,12 +58,13 @@ impl State { let recipient_ss58 = recipient.to_base58_string(2); //TODO - let server_info = ServerInfo { // TODO - version: env!("CARGO_PKG_VERSION"), - instance_id: instance_id.clone(), - debug, - kalatori_remark: remark.clone(), - }; + let server_info = ServerInfo { + // TODO + version: env!("CARGO_PKG_VERSION"), + instance_id: instance_id.clone(), + debug, + kalatori_remark: remark.clone(), + }; let state = StateData { currencies, @@ -70,10 +79,14 @@ impl State { while let Some(request) = rx.recv().await { match request { StateAccessRequest::GetInvoiceStatus(request) => { - request.res.send(state.get_invoice_status(request.order).await); + request + .res + .send(state.get_invoice_status(request.order).await); } StateAccessRequest::CreateInvoice(request) => { - request.res.send(state.create_invoice(request.order_query).await); + request + .res + .send(state.create_invoice(request.order_query).await); } StateAccessRequest::ServerStatus(res) => { let server_status = ServerStatus { @@ -160,31 +173,57 @@ struct StateData { impl StateData { 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(), - server_info: self.server_info.clone(), - order_info, - })) - } else {Ok(OrderResponse::NotFound)} -} + 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(), + server_info: self.server_info.clone(), + order_info, + })) + } 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(ErrorOrder::UnknownCurrency)?; - let currency = currency.info(order_query.currency.clone()); - let payment_account = Pair::from_entropy_and_full_derivation(&self.seed_entropy, derivations(&self.recipient, &order_query.order))?.public().to_base58_string(currency.ss58); - let order_info = OrderInfo::new(order_query, currency, payment_account); - match self.db.create_order(order.clone(), order_info.clone()).await? { - OrderCreateResponse::New => - Ok(OrderResponse::NewOrder(self.order_status(order, order_info, String::new()))), - OrderCreateResponse::Modified => - Ok(OrderResponse::ModifiedOrder(self.order_status(order, order_info, String::new()))), - OrderCreateResponse::Collision(order_status) => Ok(OrderResponse::CollidedOrder(self.order_status(order, order_info, String::from("Order with this ID was already processed")))) - } + let order = order_query.order.clone(); + let currency = self + .currencies + .get(&order_query.currency) + .ok_or(ErrorOrder::UnknownCurrency)?; + let currency = currency.info(order_query.currency.clone()); + let payment_account = Pair::from_entropy_and_full_derivation( + &self.seed_entropy, + derivations(&self.recipient, &order_query.order), + )? + .public() + .to_base58_string(currency.ss58); + let order_info = OrderInfo::new(order_query, currency, payment_account); + match self + .db + .create_order(order.clone(), order_info.clone()) + .await? + { + OrderCreateResponse::New => Ok(OrderResponse::NewOrder(self.order_status( + order, + order_info, + String::new(), + ))), + OrderCreateResponse::Modified => Ok(OrderResponse::ModifiedOrder(self.order_status( + order, + order_info, + String::new(), + ))), + OrderCreateResponse::Collision(order_status) => { + Ok(OrderResponse::CollidedOrder(self.order_status( + order, + order_info, + String::from("Order with this ID was already processed"), + ))) + } + } } fn order_status(&self, order: String, order_info: OrderInfo, message: String) -> OrderStatus { @@ -197,5 +236,3 @@ impl StateData { } } } - - diff --git a/src/unused b/src/unused new file mode 100644 index 0000000..833e385 --- /dev/null +++ b/src/unused @@ -0,0 +1,941 @@ +/* +type ConnectedChainsChannel = ( + Sender>, + (String, ConnectedChain), +); + +type CurrenciesChannel = ( + Sender>, + (String, CurrencyProperties), +); + +struct AssetsInfoFetcher<'a> { + assets: (AssetInfo, Vec), + pallet_index: Option, +} +*/ +/* +async fn fetch_finalized_head_number_and_hash( + methods: &LegacyRpcMethods, +) -> Result<(BlockNumber, BlockHash)> { + let head_hash = methods + .chain_get_finalized_head() + .await + .context("failed to get the finalized head hash")?; + let head = 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)) +} +*/ +/* +impl AssetProperties { + async fn fetch(asset: AssetId) -> Result { + Ok(Self { + min_balance: check_sufficiency_and_fetch_min_balance(asset).await?, + decimals: fetch_asset_decimals(asset).await?, + }) + } +}*/ +/* +impl ChainProperties { + async fn fetch( + chain: &str, + currencies: Sender, + native_token_option: Option, + assets_fetcher: Option>, + account_lifetime: BlockNumber, + depth: Option, + ) -> Result { + //const ADDRESS_PREFIX: (&str, &str) = (SYSTEM, "SS58Prefix"); + const EXISTENTIAL_DEPOSIT: "ExistentialDeposit";//(&str, &str) = (BALANCES, "ExistentialDeposit"); + const BLOCK_HASH_COUNT: "BlockHashCount";//(&str, &str) = (SYSTEM, "BlockHashCount"); + + /* wtf is this? + let try_add_currency = |name, asset| async move { + let (tx, rx) = oneshot::channel(); + + currencies + .send((tx, (name, CurrencyProperties { + chain_name: chain.to_owned(), + kind: , + decimals: , + rpc_url: , + asset_if: , + }))) + .unwrap(); + + if let Some(( + name, + Currency { + chain: other_chain, .. + }, + )) = rx.await.unwrap() + { + Err(anyhow::anyhow!( + "chain {other_chain:?} already has the native token or an asset with the name {name:?}, all currency names must be unique" + )) + } else { + Ok(()) + } + };*/ + + let specs = specs(); + + let assets_pallet = if let Some(AssetsInfoFetcher { + assets: (last_asset_info, assets_info), + storage, + pallet_index, + }) = assets_fetcher + { + async fn try_add_asset( + assets: &mut HashMap, + id: AssetId, + chain: &str, + ) -> Result<()> { + match assets.entry(id) { + Entry::Occupied(_) => Err(anyhow::anyhow!( + "chain {chain} has 2 assets with the same ID {id}", + )), + Entry::Vacant(entry) => { + entry.insert(AssetProperties::fetch(storage, id).await?); + + Ok(()) + } + } + } + + let mut assets = HashMap::with_capacity(assets_info.len().saturating_add(1)); + + for asset_info in assets_info { + //try_add_currency.clone()(asset_info.name, Some(asset_info.id)).await?; + try_add_asset(&mut assets, asset_info.id, chain, storage).await?; + } + + //try_add_currency.clone()(last_asset_info.name, Some(last_asset_info.id)).await?; + try_add_asset(&mut assets, last_asset_info.id, chain, storage).await?; + + Some(AssetsPallet { + assets, + multi_location: pallet_index, + }) + } else { + None + }; + let block_hash_count = fetch_constant(constants, SYSTEM, BLOCK_HASH_COUNT)?; + + //Some(native_token.decimals), + + let existential_deposit = if let Some(_native_token) = native_token_option { + Some(fetch_constant(metadata, BALANCES, ).map(Balance)?) + } else { + None + }; + + let chain = Self { + specs, + existential_deposit, + assets_pallet, + block_hash_count, + account_lifetime, + depth, + }; + + Ok(chain) + } +}*/ +/* +async fn check_sufficiency_and_fetch_min_balance( + client: &WsClient, + asset: AssetId, + block: BlockHash, +) -> Result { + const ASSET: &str = "Asset"; + const MIN_BALANCE: &str = "min_balance"; + const IS_SUFFICIENT: &str = "is_sufficient"; + + let asset_info = storage_fetch(client, ASSETS, ASSET, block); + /* + .fetch(&dynamic::storage(ASSETS, ASSET, vec![asset.into()])) + .await + .with_context(|| format!("failed to fetch asset {asset} info from a chain"))? + .with_context(|| { + format!("received nothing after fetching asset info {asset} from a chain") + })? + .to_value() + .with_context(|| format!("failed to decode asset {asset} info"))?; + */ + + let encoded_is_sufficient = asset_info + .at(IS_SUFFICIENT) + .with_context(|| format!("{IS_SUFFICIENT} field wasn't found in asset {asset} info"))?; + + if !encoded_is_sufficient.as_bool().with_context(|| { + format!( + "expected `bool` as the type of {IS_SUFFICIENT:?} in asset {asset} info, got `{:?}`", + encoded_is_sufficient.value + ) + })? { + anyhow::bail!("only sufficient assets are supported, asset {asset} isn't sufficient"); + } + + let encoded_min_balance = asset_info + .at(MIN_BALANCE) + .with_context(|| format!("{MIN_BALANCE} field wasn't found in asset {asset} info"))?; + + encoded_min_balance.as_u128().map(Balance).with_context(|| { + format!( + "expected `u128` as the type of {MIN_BALANCE:?} in asset {asset} info, got `{:?}`", + encoded_min_balance.value + ) + }) +}*/ +/* +async fn fetch_asset_decimals( + client: &WsClient, + asset: AssetId, + block: &BlockHash, +) -> Result { + const METADATA: &str = "Metadata"; + const DECIMALS: &str = "decimals"; + + let asset_metadata = storage_fetch(client, ASSETS, METADATA, block); + /*storage + .fetch(&dynamic::storage(ASSETS, METADATA, vec![asset.into()])) + .await + .with_context(|| format!("failed to fetch asset {asset} metadata from a chain"))? + .with_context(|| { + format!("received nothing after fetching asset {asset} metadata from a chain") + })? + .to_value() + .with_context(|| format!("failed to decode asset {asset} metadata"))?;*/ + let encoded_decimals = asset_metadata + .at(DECIMALS) + .with_context(|| format!("{DECIMALS} field wasn't found in asset {asset} metadata"))?; + + let decimals = encoded_decimals.as_u128().with_context(|| { + format!( + "expected `u128` as the type of asset {asset} {DECIMALS:?}, got `{:?}`", + encoded_decimals.value + ) + })?; + + decimals.try_into().with_context(|| { + format!("asset {asset} {DECIMALS:?} must be less than `u8`, got {decimals}") + }) +}*/ +/* +pub async fn prepare( + chains: Vec, + account_lifetime: Timestamp, + depth: Option, +) -> Result<( + HashMap, + HashMap, +), ErrorChain> { + let mut connected_chains = HashMap::with_capacity(chains.len()); + let mut currencies = HashMap::with_capacity( + chains + .iter() + .map(|chain| { + chain + .asset + .as_ref() + .map(Vec::len) + .unwrap_or_default() + .saturating_add(chain.native_token.is_some().into()) + }) + .sum(), + ); + + let (connected_chains_tx, mut connected_chains_rx) = + mpsc::channel::(1024); + let (currencies_tx, mut currencies_rx) = mpsc::channel::(1024); + + let connected_chains_jh = tokio::spawn(async move { + while let Some((tx, (name, chain))) = connected_chains_rx.recv().await { + tx.send(match connected_chains.entry(name) { + Entry::Occupied(entry) => Some(entry.remove_entry()), + Entry::Vacant(entry) => { + tracing::info!("Prepared the {:?} chain:\n{:#?}", entry.key(), chain); + + entry.insert(chain); + + None + } + }) + .unwrap(); + } + + connected_chains + }); + + let currencies_jh = tokio::spawn(async move { + while let Some((tx, (name, currency))) = currencies_rx.recv().await { + tx.send(match currencies.entry(name) { + Entry::Occupied(entry) => Some(entry.remove_entry()), + Entry::Vacant(entry) => { + tracing::info!( + %currency.chain_name, ?currency.asset_id, + "Registered the currency {:?}.", + entry.key(), + ); + + entry.insert(currency); + + None + } + }) + .unwrap(); + } + + currencies + }); + + let (task_tracker, error_rx) = TaskTracker::new(); + + for chain in chains { + task_tracker.spawn( + format!("the {:?} chain preparator", chain.name), + prepare_chain( + chain, + connected_chains_tx.clone(), + currencies_tx.clone(), + account_lifetime, + depth, + ), + ); + } + + drop((connected_chains_tx, currencies_tx)); + + task_tracker.try_wait(error_rx).await?; + + Ok((connected_chains_jh.await?, currencies_jh.await?)) +} +*/ +/* +async fn state_call(client: &WsClient, params: RpcParams) -> Result { + let res = client + .request( + "state_call", + params, + ) + .await + .map_err(ErrorChain::Client)?; + if let Value::String(a) = res { Ok(a) } else { Err(ErrorChain::StateCallResponse(res)) } +} + + +#[tracing::instrument(skip_all, fields(chain = chain.name))] +async fn prepare_chain( + chain: Chain, + connected_chains: Sender, + currencies: Sender, + account_lifetime: Timestamp, + depth_option: Option, +) -> Result, ErrorChain> { + let chain_name = chain.name; + let endpoint = chain + .endpoints + .first().ok_or(ErrorChain::EmptyEndpoints)?; + let client = WsClientBuilder::default() + .build(&endpoint) + .await + .map_err(ErrorChain::Client)?; + + let genesis = genesis_hash(&client).await?; + + let finalized_hash = block_hash(&client).await?; + let block_hash_string = finalized_hash.to_string(); + let metadata = metadata(&client, &finalized_hash).await?; + let specs = specs(&client, &metadata, &finalized_hash).await?; + + //TODO: we don't have to require this mechanism at all + let block_time = if pallet_index(&metadata, BABE).is_some() { + match fetch_constant(&metadata, BABE, "ExpectedBlockTime").ok_or(ErrorChain::BabeExpectedBlockTime)?.data { + + } + } else { + //const SLOT_DURATION: &str = "slot_duration"; + + state_call( + &client, + rpc_params![ + "Aura_slot_duration", + &block_hash_string + ], + ) + .await? + .parse::() + .map_err(|_| ErrorChain::AuraSlotDurationFormat)? + + /* + ( + runtime_api + .at(finalized_hash) + .call(dynamic::runtime_api_call( + AURA, + SLOT_DURATION, + Vec::::new(), + )) + .await + .context("failed to fetch Aura's slot duration")? + .as_type() + .context("failed to decode Aura's slot duration")?, + Some(runtime_api), + )*/ + }; + + let block_time_non_zero = + NonZeroU64::new(block_time).ok_or(ErrorChain::ZeroBlockTime)?;//.context("block interval can't equal 0")?; + + let account_lifetime_in_blocks = account_lifetime / block_time_non_zero; +/* TODO + if account_lifetime_in_blocks == 0 { + anyhow::bail!("block interval is longer than the given `account-lifetime`"); + } +*/ +/* + let depth_in_blocks = if let Some(depth) = depth_option { + let depth_in_blocks = depth / block_time_non_zero; + + if depth_in_blocks > account_lifetime_in_blocks { + anyhow::bail!("`depth` can't be greater than `account-lifetime`"); + } + + Some( + NonZeroU64::new(depth_in_blocks) + .context("block interval is longer than the given `depth`")?, + ) + } else { + None + }; +*/ + let rpc = endpoint.into(); +/* + let assets_info_fetcher = if let Some(assets) = chain + .asset + .and_then(|mut assets| assets.pop().map(|latest| (latest, assets))) + { + const ASSET_ID: &str = "asset_id"; + const SOME: &str = "Some"; + + let extension = metadata + .extrinsic() + .signed_extensions() + .iter() + .find(|extension| extension.identifier() == CHARGE_ASSET_TX_PAYMENT) + .with_context(|| { + format!("failed to find the {CHARGE_ASSET_TX_PAYMENT:?} extension in metadata") + })? + .extra_ty(); + let types = metadata.types(); + + let TypeDef::Composite(ref extension_type) = types + .resolve(extension);/* + .with_context(|| { + format!("failed to resolve the type of the {CHARGE_ASSET_TX_PAYMENT:?} extension") + })? + .type_def + else { + anyhow::bail!("{CHARGE_ASSET_TX_PAYMENT:?} extension has an unexpected type"); + };*/ + + let asset_id_field = extension_type + .fields + .iter() + .find_map(|field| { + field + .name + .as_ref() + .and_then(|name| (name == ASSET_ID).then_some(field.ty.id)) + });/* + .with_context(|| { + format!( + "failed to find the field {ASSET_ID:?} in the {CHARGE_ASSET_TX_PAYMENT:?} extension" + ) + })?;*/ + + let TypeDef::Variant(ref option) = types.resolve(asset_id_field); + /*.with_context(|| { + format!( + "failed to resolve the type of the field {ASSET_ID:?} in the {CHARGE_ASSET_TX_PAYMENT:?} extension" + ) + })?.type_def else { + anyhow::bail!( + "field {ASSET_ID:?} in the {CHARGE_ASSET_TX_PAYMENT:?} extension has an unexpected type" + ); + };*/ + + let asset_id_some = option.variants.iter().find_map(|variant| { + if variant.name == SOME { + variant.fields.first().map(|field| { + if variant.fields.len() > 1 { + tracing::warn!( + ?variant.fields, + "The field {ASSET_ID:?} in the {CHARGE_ASSET_TX_PAYMENT:?} extension contains multiple inner fields instead of just 1." + ); + } + + field.ty.id + }) + } else { + None + } + })/*.with_context(|| format!( + "field {ASSET_ID:?} in the {CHARGE_ASSET_TX_PAYMENT:?} extension doesn't contain the {SOME:?} variant" + ))?*/; + + let asset_id = &types.resolve(asset_id_some);/*.with_context(|| { + format!( + "failed to resolve the type of the {SOME:?} variant of the field {ASSET_ID:?} in the {CHARGE_ASSET_TX_PAYMENT:?} extension" + ) + })?.type_def;*/ + + let pallet_index = if let TypeDef::Primitive(_) = asset_id { + None + } else { + pallet_index(metadata, ASSETS) + }; + Some(AssetsInfoFetcher { + assets, + pallet_index, + }) + } else { + None + }; + + let storage = if assets_info_fetcher.is_some() { + Some(storage_client) + } else { + None + }; +*/ + /* + let properties = ChainProperties::fetch( + &chain_name, + currencies, + chain.native_token, + assets_info_fetcher, + account_lifetime_in_blocks, + depth_in_blocks, + ) + .await?; + + let connected_chain = ConnectedChain { + //methods, + genesis, + rpc, + client, + properties, + }; + + let (tx, rx) = oneshot::channel(); + + connected_chains + .send((tx, (chain_name, connected_chain))) + .unwrap(); + + if let Some((name, _)) = rx.await.unwrap() { + anyhow::bail!( + "found `[chain]`s with the same name ({name:?}) in the config, all chain names must be unique", + ); + } +*/ + Ok("".into()) +} +*/ + + + + /* + .execute() + .await + .or_else(|error| { + error + .downcast() + .map(|Shutdown| "The RPC module is shut down.".into()) + })*/ + + /* + async fn execute(mut self) -> Result> { + let (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; + + next_unscanned_number = head_number.checked_add(1).context(MAX_BLOCK_NUMBER_ERROR)?; + subscription = self.finalized_heads().await?; + + loop { + self.process_finalized_heads(subscription, &mut next_unscanned_number) + .await?; + + tracing::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, BlockHash)> { + 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 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: BlockHash) -> Result<()> { + tracing::debug!("Processing the 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 invoices = &mut *self.state.invoices.write().await; + + // 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(); + + #[allow(clippy::single_match)] + match (metadata.pallet.name(), &*metadata.variant.name) { + // (SYSTEM, UPDATE) => update = true, + (ASSETS, TRANSFERRED) => { + let tr = Transferred::deserialize( + event + .field_values() + .context("failed to decode event's fields")?, + ) + .context("failed to deserialize a transfer event")?; + + tracing::info!("{tr:?}"); + /* TODO process using cache and db access + #[allow(clippy::unnecessary_find_map)] + if let Some(invoic) = invoices.iter().find_map(|invoic| { + tracing::info!("{tr:?} {invoic:?}"); + tracing::info!("{}", tr.to == invoic.1.paym_acc); + tracing::info!("{}", *invoic.1.amount >= tr.amount); + + if tr.to == invoic.1.paym_acc && *invoic.1.amount <= tr.amount { + Some(invoic) + } else { + None + } + }) { + tracing::info!("{invoic:?}"); + if !invoic.1.callback.is_empty() { + tracing::info!("{:?}", invoic.1.callback); + + crate::callback::callback( + invoic.1.callback.clone(), + invoic.0.to_string(), + self.state.recipient.clone(), + self.state.debug, + self.state.remark.clone(), + invoic.1.amount, + self.state.rpc.clone(), + invoic.1.paym_acc.clone(), + ) + .await; + } + + invoices.insert( + invoic.0.clone(), + Invoicee { + callback: invoic.1.callback.clone(), + amount: Balance(*invoic.1.amount), + paid: true, + paym_acc: invoic.1.paym_acc.clone(), + }, + ); + }*/ + } + _ => {} + } + } + + // 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")?; + // } + + Ok(()) + } + + async fn balance(&self, hash: BlockHash, account: &AccountId) -> Result { + const ACCOUNT: &str = "Account"; + const BALANCE: &str = "balance"; + /* TODO: this should fetch balance and also considet native-nonnative shit wtf is this? + if let Some(account_info) = self + .storage + .at(hash) + .fetch(&dynamic::storage( + ASSETS, + ACCOUNT, + vec![ + Value::from(1337u32), + 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().map(Balance).with_context(|| { + format!("expected `u128` as the type of a balance, got {encoded_balance}") + }) + } else { + Ok(Balance(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, Asset::Id(1337)); + */ + //TODO create tx + /* + self.client + .tx() + .create_signed(&call, signer, extensions.build()) + .await + .context("failed to create a transfer transaction") + */ + } + */ + + // async fn current_nonce(&self, account: &AccountId) -> Result { + // self.api + // .blocks + // .at(self.finalized_head_number_and_hash().await?.0) + // .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: BlockHash, + // invoice: AccountId, + // 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: &AccountId, amount: u128) -> Value { + const TRANSFER_KEEP_ALIVE: &str = "transfer"; + + dbg!(amount); + + dynamic::tx( + ASSETS, + TRANSFER_KEEP_ALIVE, + vec![ + 1337.into(), + scale_value::value!(Id(Value::from_bytes(to))), + amount.into(), + ], + ) + .into_value() +} +*/ +// impl Transferred { +// fn process( +// self, +// invoices_changes: &mut HashMap, +// invoices: &mut HashMap, +// ) -> Result<()> { +// let usd_asset = 1337u32; + +// tracing::debug!("Transferred event: {self:?}"); + +// if self.from == self.to || self.amount == 0 || self.asset_id != usd_asset { +// return Ok(()); +// } + +// match invoices_changes.entry(self.to) { +// Entry::Occupied(entry) => { +// entry +// .into_mut() +// .incoming +// .entry(self.from) +// .and_modify(|amount| *amount = Balance(amount.saturating_add(self.amount))) +// .or_insert(Balance(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(()) +// } +// } + From aa9b2b082e6c462a5b85df984decd99db95491b2 Mon Sep 17 00:00:00 2001 From: Slesarev Date: Wed, 1 May 2024 02:02:59 +0300 Subject: [PATCH 26/76] feat: graceful shutdown --- src/database.rs | 43 ++++++++----- src/definitions.rs | 17 +++--- src/error.rs | 15 +++++ src/main.rs | 37 +++++++---- src/rpc.rs | 149 ++++++++++++++++++++++++++++++++++++++++----- src/server.rs | 6 -- src/state.rs | 26 +++++--- 7 files changed, 228 insertions(+), 65 deletions(-) diff --git a/src/database.rs b/src/database.rs index efa2c79..bf0bb3e 100644 --- a/src/database.rs +++ b/src/database.rs @@ -192,25 +192,31 @@ impl Database { while let Some(request) = rx.recv().await { match request { DbRequest::CreateOrder(request) => { - request + let _unused = request .res .send(create_order(request.order, request.order_info, &orders)); } DbRequest::ReadOrder(request) => { - request.res.send(read_order(request.order, &orders)); + let _unused = request.res.send(read_order(request.order, &orders)); } DbRequest::MarkPaid(request) => { - request.res.send(mark_paid(request.order, &orders)); + let _unused = request.res.send(mark_paid(request.order, &orders)); } DbRequest::MarkWithdrawn(request) => { - request.res.send(mark_withdrawn(request.order, &orders)); + let _unused = request.res.send(mark_withdrawn(request.order, &orders)); } DbRequest::MarkStuck(request) => { - request.res.send(mark_stuck(request.order, &orders)); + let _unused = request.res.send(mark_stuck(request.order, &orders)); + } + DbRequest::Shutdown(res) => { + let _ = res.send(()); + break; } }; } + drop(database.flush()); + Ok("Database server is shutting down".into()) }); @@ -223,40 +229,46 @@ impl Database { order_info: OrderInfo, ) -> Result { let (res, rx) = oneshot::channel(); - self.tx.send(DbRequest::CreateOrder(CreateOrder { + let _unused = self.tx.send(DbRequest::CreateOrder(CreateOrder { order, order_info, res, - })); + })).await; rx.await.map_err(|_| ErrorDb::DbEngineDown)? } pub async fn read_order(&self, order: String) -> Result, ErrorDb> { let (res, rx) = oneshot::channel(); - self.tx.send(DbRequest::ReadOrder(ReadOrder { order, res })); + let _unused = self.tx.send(DbRequest::ReadOrder(ReadOrder { order, res })).await; rx.await.map_err(|_| ErrorDb::DbEngineDown)? } pub async fn mark_paid(&self, order: String) -> Result<(), ErrorDb> { let (res, rx) = oneshot::channel(); - self.tx - .send(DbRequest::MarkPaid(ModifyOrder { order, res })); + let _unused = self.tx + .send(DbRequest::MarkPaid(ModifyOrder { order, res })).await; rx.await.map_err(|_| ErrorDb::DbEngineDown)? } pub async fn mark_withdrawn(&self, order: String) -> Result<(), ErrorDb> { let (res, rx) = oneshot::channel(); - self.tx - .send(DbRequest::MarkWithdrawn(ModifyOrder { order, res })); + let _unused = self.tx + .send(DbRequest::MarkWithdrawn(ModifyOrder { order, res })).await; rx.await.map_err(|_| ErrorDb::DbEngineDown)? } pub async fn mark_stuck(&self, order: String) -> Result<(), ErrorDb> { let (res, rx) = oneshot::channel(); - self.tx - .send(DbRequest::MarkStuck(ModifyOrder { order, res })); + let _unused = self.tx + .send(DbRequest::MarkStuck(ModifyOrder { order, res })).await; rx.await.map_err(|_| ErrorDb::DbEngineDown)? } + + pub async fn shutdown(&self) { + let (tx, rx) = oneshot::channel(); + let _unused = self.tx.send(DbRequest::Shutdown(tx)).await; + let _ = rx.await; + } } enum DbRequest { @@ -265,6 +277,7 @@ enum DbRequest { MarkPaid(ModifyOrder), MarkWithdrawn(ModifyOrder), MarkStuck(ModifyOrder), + Shutdown(oneshot::Sender<()>), } pub struct CreateOrder { @@ -293,7 +306,7 @@ fn create_order( let old_order_info = OrderInfo::decode(&mut &record[..])?; match order_info.payment_status { PaymentStatus::Pending => { - let _ = orders.insert(order.encode(), order_info.encode())?; + drop(orders.insert(order.encode(), order_info.encode())?); OrderCreateResponse::Modified } PaymentStatus::Paid => OrderCreateResponse::Collision(old_order_info), diff --git a/src/definitions.rs b/src/definitions.rs index 2b93546..53dd656 100644 --- a/src/definitions.rs +++ b/src/definitions.rs @@ -16,24 +16,25 @@ pub type Entropy = Vec; // TODO: maybe enforce something here #[derive(Deserialize)] #[serde(rename_all = "kebab-case")] pub struct Chain { - name: String, - endpoints: Vec, + pub name: String, + pub endpoints: Vec, #[serde(flatten)] - native_token: Option, - asset: Option>, + pub native_token: Option, + #[serde(default)] + pub asset: Vec, } #[derive(Deserialize)] #[serde(rename_all = "kebab-case")] pub struct NativeToken { - native_token: String, - decimals: api_v2::Decimals, + pub name: String, + pub decimals: api_v2::Decimals, } #[derive(Deserialize)] pub struct AssetInfo { - name: String, - id: api_v2::AssetId, + pub name: String, + pub id: api_v2::AssetId, } #[derive(Deserialize, Debug, Clone, Copy)] diff --git a/src/error.rs b/src/error.rs index 7c890ae..ad943cd 100644 --- a/src/error.rs +++ b/src/error.rs @@ -52,6 +52,9 @@ pub enum Error { #[error("Operating system related I/O error {0:?}")] IoError(std::io::Error), + + #[error("Duplicate config record for token {0}")] + DuplicateCurrency(String), } impl From for Error { @@ -242,6 +245,18 @@ pub enum ErrorChain { #[error("Internal error: {0:?}")] // TODO this should be replaced by specific errors ErrorUtil(ErrorUtil), + + #[error("Invoice account could not be parsed: {0:?}")] + InvoiceAccount(substrate_crypto_light::error::Error), + + #[error("Chain {0} not found")] + InvalidChain(String), + + #[error("Currency {0} not found")] + InvalidCurrency(String), + + #[error("Chain manager dropped a message, probably due to chain disconnect; maybe it should be sent again")] + MessageDropped, } impl From for ErrorChain { diff --git a/src/main.rs b/src/main.rs index ad26b8d..03af6bc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,7 +15,7 @@ use substrate_crypto_light::common::{AccountId32, AsBase58}; use substrate_crypto_light::{common::cut_path, sr25519::Pair}; use tokio::{ signal, - sync::mpsc::{self, UnboundedReceiver, UnboundedSender}, + sync::{mpsc, oneshot}, task::JoinHandle, }; use tokio_util::{sync::CancellationToken, task}; @@ -116,11 +116,6 @@ async fn main() -> Result<(), Error> { let (task_tracker, error_rx) = TaskTracker::new(); - task_tracker.spawn( - "the shutdown listener", - shutdown_listener(shutdown_notification.clone()), - ); - //let (chains, currencies) = rpc::prepare(config.chain, config.account_lifetime, config.depth).await let currencies = HashMap::new(); @@ -132,7 +127,7 @@ async fn main() -> Result<(), Error> { let db = database::Database::init(database_path, task_tracker.clone())?; - let chain_manager = ChainManager::ignite(config.chain, task_tracker.clone())?; + let (cm_tx, mut cm_rx) = oneshot::channel(); let state = State::initialise( currencies, @@ -146,11 +141,16 @@ async fn main() -> Result<(), Error> { rpc: rpc.clone(), }, db, - chain_manager, + cm_rx, instance_id, task_tracker.clone(), )?; + task_tracker.spawn( + "the shutdown listener", + shutdown_listener(shutdown_notification.clone(), state.interface()), + ); + /* task_tracker.spawn( "proc", @@ -162,7 +162,16 @@ async fn main() -> Result<(), Error> { ), );*/ - let server = server::new(shutdown_notification.clone(), host, state).await?; + cm_tx + .send(ChainManager::ignite( + config.chain, + state.interface(), + task_tracker.clone(), + shutdown_notification.clone(), + )?) + .map_err(|_| Error::Fatal)?; + + let server = server::new(shutdown_notification.clone(), host, state.interface()).await?; // task_tracker.spawn(shutdown( // processor.ignite(last_saved_block, task_tracker.clone(), error_tx.clone()), @@ -308,11 +317,11 @@ pub fn entropy_from_phrase(seed: &str) -> Result { #[derive(Clone)] struct TaskTracker { inner: task::TaskTracker, - error_tx: UnboundedSender<(Cow<'static, str>, Error)>, + error_tx: mpsc::UnboundedSender<(Cow<'static, str>, Error)>, } impl TaskTracker { - fn new() -> (Self, UnboundedReceiver<(Cow<'static, str>, Error)>) { + fn new() -> (Self, mpsc::UnboundedReceiver<(Cow<'static, str>, Error)>) { let (error_tx, error_rx) = mpsc::unbounded_channel(); let inner = task::TaskTracker::new(); @@ -341,7 +350,7 @@ impl TaskTracker { async fn wait_with_notification( self, - mut error_rx: UnboundedReceiver<(Cow<'static, str>, Error)>, + mut error_rx: mpsc::UnboundedReceiver<(Cow<'static, str>, Error)>, shutdown_notification: CancellationToken, ) { drop(self.error_tx); @@ -361,7 +370,7 @@ impl TaskTracker { async fn try_wait( self, - mut error_rx: UnboundedReceiver<(Cow<'static, str>, Error)>, + mut error_rx: mpsc::UnboundedReceiver<(Cow<'static, str>, Error)>, ) -> Result<(), Error> { drop(self.error_tx); @@ -377,6 +386,7 @@ impl TaskTracker { async fn shutdown_listener( shutdown_notification: CancellationToken, + state: State, ) -> Result, Error> { tokio::select! { biased; @@ -389,6 +399,7 @@ async fn shutdown_listener( tracing::info!("Received the shutdown signal. Initialising the shutdown..."); shutdown_notification.cancel(); + state.shutdown().await; } () = shutdown_notification.cancelled() => {} } diff --git a/src/rpc.rs b/src/rpc.rs index 99ffe93..58a9986 100644 --- a/src/rpc.rs +++ b/src/rpc.rs @@ -3,7 +3,7 @@ use crate::{ base58prefix, hashed_key_element, pallet_index, storage_key, system_properties_to_short_specs, unit, }, - definitions::api_v2::CurrencyProperties, + definitions::api_v2::{CurrencyProperties, OrderInfo}, definitions::{ api_v2::{AssetId, BlockNumber, CurrencyInfo, Decimals, TokenKind}, AssetInfo, Balance, BlockHash, Chain, NativeToken, Nonce, PalletIndex, Timestamp, @@ -32,7 +32,7 @@ use std::{ fmt::Debug, num::NonZeroU64, }; -use substrate_crypto_light::common::AccountId32; +use substrate_crypto_light::common::{AccountId32, AsBase58}; use substrate_parser::{ cards::{ExtendedData, ParsedData, Sequence}, decode_all_as_type, decode_as_storage_entry, @@ -61,7 +61,6 @@ const BABE: &str = "Babe"; const AURA: &str = "AuraApi"; - #[derive(Debug)] struct ChainProperties { specs: ShortSpecs, @@ -85,7 +84,6 @@ struct AssetProperties { decimals: Decimals, } - pub async fn get_value_from_storage( client: &WsClient, whole_key: &str, @@ -238,36 +236,155 @@ pub struct ChainManager { } 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, task_tracker: TaskTracker, + cancellation_token: CancellationToken, ) -> Result { /* - let client = WsClientBuilder::default() - .build(rpc.clone()) - .await - .map_err(ErrorChain::Client)?; -*/ - + let client = WsClientBuilder::default() + .build(rpc.clone()) + .await + .map_err(ErrorChain::Client)?; + */ let (tx, mut rx) = mpsc::channel(1024); - //let watch_chain: HashMap> = chain.iter().map(|a| (a.name, a.endpoints)).into() + let mut watch_chain = HashMap::new(); - task_tracker.spawn("Blockchain connections manager", async move { - + let mut currency_map = HashMap::new(); - Ok("Chain manager is shutting down".into()) - }); + // start network monitors + for c in chain { + let (chain_tx, mut chain_rx) = mpsc::channel(1024); + watch_chain.insert(c.name.clone(), chain_tx); + if let Some(a) = c.native_token { + if let Some(_) = currency_map.insert(a.name.clone(), c.name.clone()) { + return Err(Error::DuplicateCurrency(a.name)); + } + } + for a in c.asset { + if let Some(_) = currency_map.insert(a.name.clone(), c.name.clone()) { + return Err(Error::DuplicateCurrency(a.name)); + } + } - Ok(Self{tx}) + task_tracker.spawn(format!("Chain {} watcher", c.name.clone()), async move { + let mut watched_accounts = Vec::new(); + let mut shutdown = false; + for endpoint in c.endpoints.iter().cycle() { + // not restarting chain if shutdown is in progress + if shutdown { + break; + } + if let Ok(client) = WsClientBuilder::default().build(endpoint).await { + while let Some(request) = chain_rx.recv().await { + match request { + ChainTrackerRequest::WatchAccount(request) => { + watched_accounts.push(request); + } + ChainTrackerRequest::Shutdown(res) => { + shutdown = true; + let _ = res.send(()); + break; + } + } + } + } + } + Ok(format!("Chain {} monitor shut down", c.name).into()) + }); + } + 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(ErrorChain::InvalidChain(chain.to_string()))); + } + } else { + let _unused = request + .res + .send(Err(ErrorChain::InvalidCurrency(request.currency))); + } + } + ChainRequest::Shutdown(res) => { + + for (name, chain) in watch_chain.drain() { + let (tx, rx) = oneshot::channel(); + let _unused = chain.send(ChainTrackerRequest::Shutdown(tx)).await; + let _ = rx.await; + } + let _ = res.send(()); + break; + } + } + } + + Ok("Chain manager is shutting down".into()) + }); + + Ok(Self { tx }) + } + + pub async fn add_invoice(&self, order: OrderInfo) -> Result<(), ErrorChain> { + let (res, rx) = oneshot::channel(); + self.tx + .send(ChainRequest::WatchAccount(WatchAccount::new(order, res)?)).await.map_err(|_| ErrorChain::MessageDropped)?; + rx.await.map_err(|_| ErrorChain::MessageDropped)? + } + + pub async fn shutdown(&self) -> () { + let (tx, rx) = oneshot::channel(); + let _unused = self.tx.send(ChainRequest::Shutdown(tx)).await; + let _ = rx.await; + () } } enum ChainRequest { + WatchAccount(WatchAccount), + Shutdown(oneshot::Sender<()>), +} +enum ChainTrackerRequest { + WatchAccount(WatchAccount), + Shutdown(oneshot::Sender<()>), } +struct WatchAccount { + address: AccountId32, + currency: String, + amount: Balance, + res: oneshot::Sender>, +} + +impl WatchAccount { + fn new( + order: OrderInfo, + res: oneshot::Sender>, + ) -> Result { + Ok(WatchAccount { + address: AccountId32::from_base58_string(&order.payment_account) + .map_err(ErrorChain::InvoiceAccount)? + .0, + currency: order.currency.currency, + amount: Balance::parse(order.amount, order.currency.decimals), + res, + }) + } +} #[derive(Deserialize, Debug)] struct Transferred { diff --git a/src/server.rs b/src/server.rs index bed71e7..88375a0 100644 --- a/src/server.rs +++ b/src/server.rs @@ -42,12 +42,6 @@ pub async fn new( }) } -#[derive(Debug, Serialize)] -enum OrderSuccess { - Created, - Found, -} - #[derive(Debug, Serialize)] struct InvalidParameter { parameter: String, diff --git a/src/state.rs b/src/state.rs index d8bfeff..e2e451d 100644 --- a/src/state.rs +++ b/src/state.rs @@ -39,7 +39,7 @@ impl State { rpc, }: ConfigWoChains, db: Database, - chain_manager: ChainManager, + chain_manager: oneshot::Receiver, instance_id: String, task_tracker: TaskTracker, ) -> Result { @@ -76,24 +76,31 @@ impl State { // Remember to always spawn async here or things might deadlock task_tracker.spawn("State Handler", async move { + let chain_manager = chain_manager.await.map_err(|_| Error::Fatal)?; while let Some(request) = rx.recv().await { match request { StateAccessRequest::GetInvoiceStatus(request) => { request .res - .send(state.get_invoice_status(request.order).await); + .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); + .send(state.create_invoice(request.order_query).await).map_err(|_| Error::Fatal)?; } StateAccessRequest::ServerStatus(res) => { let server_status = ServerStatus { description: state.server_info.clone(), supported_currencies: state.currencies.clone(), }; - res.send(server_status); + res.send(server_status).map_err(|_| Error::Fatal)?; + } + // Orchestrate shutdown from here + StateAccessRequest::Shutdown => { + chain_manager.shutdown().await; + state.db.shutdown().await; + break; } }; } @@ -111,13 +118,13 @@ impl State { order: order.to_string(), res, })) - .await; + .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; + self.tx.send(StateAccessRequest::ServerStatus(res)).await.map_err(|_| Error::Fatal)?; rx.await.map_err(|_| Error::Fatal) } @@ -136,7 +143,7 @@ impl State { order_query, res, })) - .await; + .await.map_err(|_| Error::Fatal)?; rx.await.map_err(|_| Error::Fatal)? } @@ -145,12 +152,17 @@ impl State { tx: self.tx.clone(), } } + + pub async fn shutdown(&self) { + self.tx.send(StateAccessRequest::Shutdown).await.unwrap(); + } } enum StateAccessRequest { GetInvoiceStatus(GetInvoiceStatus), CreateInvoice(CreateInvoice), ServerStatus(oneshot::Sender), + Shutdown, } struct GetInvoiceStatus { From 9fd08077c2719a25095ebfb317dbbe7b101f4ee0 Mon Sep 17 00:00:00 2001 From: Slesarev Date: Thu, 2 May 2024 22:36:51 +0300 Subject: [PATCH 27/76] feat: check balances --- Cargo.lock | 31 +---- Cargo.toml | 4 +- src/chain.rs | 167 +++++++++++++++++++++++- src/database.rs | 44 ++++--- src/definitions.rs | 4 +- src/error.rs | 3 + src/rpc.rs | 308 +++++++++++++++++++++++++++++++++++++-------- src/state.rs | 61 +++++---- 8 files changed, 503 insertions(+), 119 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b37ed12..03bba9b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1130,7 +1130,7 @@ dependencies = [ "sp-crypto-hashing", "substrate-constructor", "substrate-crypto-light", - "substrate_parser 0.6.1 (git+https://github.com/Alzymologist/substrate-parser)", + "substrate_parser", "thiserror", "tokio", "tokio-util", @@ -2188,8 +2188,7 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "substrate-constructor" version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73855d033fd5a8e24342d11d43aa3657f030a7d00c064d63988577b173c17922" +source = "git+https://github.com/Alzymologist/substrate-constructor#09decf076dfca2e1c4934e76ed34ac8cf59b2f23" dependencies = [ "bitvec", "external-memory-tools", @@ -2199,8 +2198,9 @@ dependencies = [ "parity-scale-codec", "primitive-types", "scale-info", - "sp-arithmetic 24.0.0", - "substrate_parser 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", + "sp-arithmetic 25.0.0", + "sp-crypto-hashing", + "substrate_parser", ] [[package]] @@ -2237,32 +2237,13 @@ dependencies = [ "hex", "num-bigint", "parity-scale-codec", + "plot_icon", "primitive-types", "scale-info", "sp-arithmetic 24.0.0", "sp-core-hashing", ] -[[package]] -name = "substrate_parser" -version = "0.6.1" -source = "git+https://github.com/Alzymologist/substrate-parser#65de6a4fe207a64f9857247af4e9f7509fa6de4f" -dependencies = [ - "base58", - "bitvec", - "blake2", - "external-memory-tools", - "frame-metadata", - "hex", - "num-bigint", - "parity-scale-codec", - "plot_icon", - "primitive-types", - "scale-info", - "sp-arithmetic 25.0.0", - "sp-crypto-hashing", -] - [[package]] name = "subtle" version = "2.5.0" diff --git a/Cargo.toml b/Cargo.toml index 7fd79fe..89e5b26 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,8 +29,8 @@ substrate-crypto-light = { git = "https://github.com/Alzymologist/substrate-cryp jsonrpsee = { version = "0.22.4", features = ["ws-client"] } mnemonic-external = { git = "https://github.com/Alzymologist/mnemonic-external" } thiserror = "1.0.58" -substrate_parser = { git = "https://github.com/Alzymologist/substrate-parser" } -substrate-constructor = "0.1.0" +substrate_parser = "0.6.1" #{ git = "https://github.com/Alzymologist/substrate-parser" } +substrate-constructor = { git = "https://github.com/Alzymologist/substrate-constructor" } frame-metadata = "16.0.0" hex = "0.4.3" parity-scale-codec = "3.6.9" diff --git a/src/chain.rs b/src/chain.rs index 16fb6a1..67aaf81 100644 --- a/src/chain.rs +++ b/src/chain.rs @@ -10,11 +10,176 @@ use parity_scale_codec::{Decode, Encode}; use scale_info::{TypeDef, TypeDefPrimitive}; use serde_json::{Map, Number, Value}; use sp_crypto_hashing::{blake2_128, blake2_256, twox_128, twox_256, twox_64}; -use substrate_crypto_light::common::{DeriveJunction, FullDerivation}; +use substrate_crypto_light::common::{AccountId32, DeriveJunction, FullDerivation}; use substrate_parser::{ cards::{ExtendedData, ParsedData}, decode_all_as_type, AsMetadata, ShortSpecs, }; +use substrate_constructor::{fill_prepare::{PrimitiveToFill, SpecialTypeToFill, TypeContentToFill, UnsignedToFill}, storage_query::{EntrySelector, EntrySelectorFunctional, FinalizedStorageQuery, StorageEntryTypeToFill, StorageSelector, StorageSelectorFunctional}}; + +#[derive(Clone, Debug)] +pub struct FinalizedQueries { + pub system_account: FinalizedStorageQuery, + pub assets_account: FinalizedStorageQuery, +} + +pub fn balance_queries( + metadata_v15: &RuntimeMetadataV15, + account_id: &AccountId32, + asset_id: u32, +) -> FinalizedQueries { + let storage_selector = StorageSelector::init(&mut (), metadata_v15).unwrap(); + + if let StorageSelector::Functional(mut storage_selector_functional) = storage_selector { + let mut index_system_in_pallet_selector = None; + let mut index_assets_in_pallet_selector = None; + + for (index, pallet) in storage_selector_functional + .available_pallets + .iter() + .enumerate() + { + match pallet.prefix.as_str() { + "System" => index_system_in_pallet_selector = Some(index), + "Assets" => index_assets_in_pallet_selector = Some(index), + _ => {} + } + if index_system_in_pallet_selector.is_some() + && index_assets_in_pallet_selector.is_some() + { + break; + } + } + let index_system_in_pallet_selector = index_system_in_pallet_selector.unwrap(); + let index_assets_in_pallet_selector = index_assets_in_pallet_selector.unwrap(); + + let mut system_account_query = None; + let mut assets_account_query = None; + + // System - Account + storage_selector_functional = StorageSelectorFunctional::new_at::<(), RuntimeMetadataV15>( + &storage_selector_functional.available_pallets, + &mut (), + &metadata_v15.types, + index_system_in_pallet_selector, + ) + .unwrap(); + + 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; + } + } + let entry_index = entry_index.unwrap(); + *entry_selector_functional = EntrySelectorFunctional::new_at::<(), RuntimeMetadataV15>( + &entry_selector_functional.available_entries, + &mut (), + &metadata_v15.types, + entry_index, + ) + .unwrap(); + 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(substrate_parser::additional_types::AccountId32(account_id.0)) + } + } + + system_account_query = storage_selector_functional.query.finalize().unwrap(); + } + + // Assets - Account + storage_selector_functional = StorageSelectorFunctional::new_at::<(), RuntimeMetadataV15>( + &storage_selector_functional.available_pallets, + &mut (), + &metadata_v15.types, + index_assets_in_pallet_selector, + ) + .unwrap(); + 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; + } + } + let entry_index = entry_index.unwrap(); + *entry_selector_functional = EntrySelectorFunctional::new_at::<(), RuntimeMetadataV15>( + &entry_selector_functional.available_entries, + &mut (), + &metadata_v15.types, + entry_index, + ) + .unwrap(); + 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(substrate_parser::additional_types::AccountId32(account_id.0)), + 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); + } + } + _ => {} + } + } + } + } + + assets_account_query = storage_selector_functional.query.finalize().unwrap(); + } + + return FinalizedQueries { + system_account: system_account_query.unwrap(), + assets_account: assets_account_query.unwrap(), + }; + } + panic!("was unable to find something"); +} pub fn derivations<'a>(recipient: &'a str, order: &'a str) -> FullDerivation<'a> { FullDerivation { diff --git a/src/database.rs b/src/database.rs index bf0bb3e..9ee236e 100644 --- a/src/database.rs +++ b/src/database.rs @@ -192,9 +192,11 @@ impl Database { while let Some(request) = rx.recv().await { match request { DbRequest::CreateOrder(request) => { - let _unused = request - .res - .send(create_order(request.order, request.order_info, &orders)); + let _unused = request.res.send(create_order( + request.order, + request.order_info, + &orders, + )); } DbRequest::ReadOrder(request) => { let _unused = request.res.send(read_order(request.order, &orders)); @@ -229,38 +231,50 @@ impl Database { order_info: OrderInfo, ) -> Result { let (res, rx) = oneshot::channel(); - let _unused = self.tx.send(DbRequest::CreateOrder(CreateOrder { - order, - order_info, - res, - })).await; + let _unused = self + .tx + .send(DbRequest::CreateOrder(CreateOrder { + order, + order_info, + res, + })) + .await; rx.await.map_err(|_| ErrorDb::DbEngineDown)? } pub async fn read_order(&self, order: String) -> Result, ErrorDb> { let (res, rx) = oneshot::channel(); - let _unused = self.tx.send(DbRequest::ReadOrder(ReadOrder { order, res })).await; + let _unused = self + .tx + .send(DbRequest::ReadOrder(ReadOrder { order, res })) + .await; rx.await.map_err(|_| ErrorDb::DbEngineDown)? } pub async fn mark_paid(&self, order: String) -> Result<(), ErrorDb> { let (res, rx) = oneshot::channel(); - let _unused = self.tx - .send(DbRequest::MarkPaid(ModifyOrder { order, res })).await; + let _unused = self + .tx + .send(DbRequest::MarkPaid(ModifyOrder { order, res })) + .await; rx.await.map_err(|_| ErrorDb::DbEngineDown)? } pub async fn mark_withdrawn(&self, order: String) -> Result<(), ErrorDb> { let (res, rx) = oneshot::channel(); - let _unused = self.tx - .send(DbRequest::MarkWithdrawn(ModifyOrder { order, res })).await; + let _unused = self + .tx + .send(DbRequest::MarkWithdrawn(ModifyOrder { order, res })) + .await; rx.await.map_err(|_| ErrorDb::DbEngineDown)? } pub async fn mark_stuck(&self, order: String) -> Result<(), ErrorDb> { let (res, rx) = oneshot::channel(); - let _unused = self.tx - .send(DbRequest::MarkStuck(ModifyOrder { order, res })).await; + let _unused = self + .tx + .send(DbRequest::MarkStuck(ModifyOrder { order, res })) + .await; rx.await.map_err(|_| ErrorDb::DbEngineDown)? } diff --git a/src/definitions.rs b/src/definitions.rs index 53dd656..55a61ec 100644 --- a/src/definitions.rs +++ b/src/definitions.rs @@ -37,8 +37,8 @@ pub struct AssetInfo { pub id: api_v2::AssetId, } -#[derive(Deserialize, Debug, Clone, Copy)] -pub struct Balance(u128); +#[derive(Deserialize, Debug, Clone, Copy, PartialEq, PartialOrd)] +pub struct Balance(pub u128); impl Deref for Balance { type Target = u128; diff --git a/src/error.rs b/src/error.rs index ad943cd..02a1bfd 100644 --- a/src/error.rs +++ b/src/error.rs @@ -257,6 +257,9 @@ pub enum ErrorChain { #[error("Chain manager dropped a message, probably due to chain disconnect; maybe it should be sent again")] MessageDropped, + + #[error("Block subscription terminated")] + BlockSubscriptionTerminated, } impl From for ErrorChain { diff --git a/src/rpc.rs b/src/rpc.rs index 58a9986..4dc81b7 100644 --- a/src/rpc.rs +++ b/src/rpc.rs @@ -1,6 +1,6 @@ use crate::{ chain::{ - base58prefix, hashed_key_element, pallet_index, storage_key, + base58prefix, balance_queries, hashed_key_element, pallet_index, storage_key, system_properties_to_short_specs, unit, }, definitions::api_v2::{CurrencyProperties, OrderInfo}, @@ -17,7 +17,7 @@ use frame_metadata::{ v15::{RuntimeMetadataV15, StorageEntryType}, RuntimeMetadata, }; -use jsonrpsee::core::client::ClientT; +use jsonrpsee::core::client::{ClientT, Subscription, SubscriptionClientT}; use jsonrpsee::rpc_params; use jsonrpsee::ws_client::{WsClient, WsClientBuilder}; use parity_scale_codec::{Decode, DecodeAll, Encode}; @@ -36,10 +36,14 @@ use substrate_crypto_light::common::{AccountId32, AsBase58}; use substrate_parser::{ cards::{ExtendedData, ParsedData, Sequence}, decode_all_as_type, decode_as_storage_entry, + special_indicators::SpecialtyUnsignedInteger, storage_data::{KeyData, KeyPart}, AsMetadata, ShortSpecs, }; -use tokio::sync::{mpsc, oneshot}; +use tokio::{ + sync::{mpsc, oneshot}, + time::{timeout, Duration}, +}; use tokio_util::sync::CancellationToken; pub const MODULE: &str = module_path!(); @@ -88,7 +92,7 @@ pub async fn get_value_from_storage( client: &WsClient, whole_key: &str, block_hash: &str, -) -> Result> { +) -> Result { let value: Value = client .request("state_getStorage", rpc_params![whole_key, block_hash]) .await?; @@ -100,7 +104,7 @@ pub async fn get_keys_from_storage( prefix: &str, storage_name: &str, block_hash: &str, -) -> Result> { +) -> Result { let keys: Value = client .request( "state_getKeys", @@ -142,9 +146,9 @@ async fn genesis_hash(client: &WsClient) -> Result { /// fetch current block hash, to request later the metadata and specs for /// the same block -async fn block_hash(client: &WsClient) -> Result { +async fn block_hash(client: &WsClient, number: String) -> Result { let block_hash_request: Value = client - .request("chain_getBlockHash", rpc_params![]) + .request("chain_getBlockHash", rpc_params![number]) .await .map_err(ErrorChain::Client)?; match block_hash_request { @@ -259,43 +263,25 @@ impl ChainManager { // start network monitors for c in chain { let (chain_tx, mut chain_rx) = mpsc::channel(1024); - watch_chain.insert(c.name.clone(), chain_tx); - if let Some(a) = c.native_token { + watch_chain.insert(c.name.clone(), chain_tx.clone()); + 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)); + return Err(Error::DuplicateCurrency(a.name.clone())); } } - for a in c.asset { + for a in &c.asset { if let Some(_) = currency_map.insert(a.name.clone(), c.name.clone()) { - return Err(Error::DuplicateCurrency(a.name)); + return Err(Error::DuplicateCurrency(a.name.clone())); } } - task_tracker.spawn(format!("Chain {} watcher", c.name.clone()), async move { - let mut watched_accounts = Vec::new(); - let mut shutdown = false; - for endpoint in c.endpoints.iter().cycle() { - // not restarting chain if shutdown is in progress - if shutdown { - break; - } - if let Ok(client) = WsClientBuilder::default().build(endpoint).await { - while let Some(request) = chain_rx.recv().await { - match request { - ChainTrackerRequest::WatchAccount(request) => { - watched_accounts.push(request); - } - ChainTrackerRequest::Shutdown(res) => { - shutdown = true; - let _ = res.send(()); - break; - } - } - } - } - } - Ok(format!("Chain {} monitor shut down", c.name).into()) - }); + start_chain_watch( + c, + chain_tx.clone(), + chain_rx, + task_tracker.clone(), + cancellation_token.clone(), + )?; } task_tracker @@ -307,7 +293,9 @@ impl ChainManager { 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; + let _unused = receiver + .send(ChainTrackerRequest::WatchAccount(request)) + .await; } else { let _unused = request .res @@ -320,7 +308,6 @@ impl ChainManager { } } ChainRequest::Shutdown(res) => { - for (name, chain) in watch_chain.drain() { let (tx, rx) = oneshot::channel(); let _unused = chain.send(ChainTrackerRequest::Shutdown(tx)).await; @@ -341,7 +328,9 @@ impl ChainManager { pub async fn add_invoice(&self, order: OrderInfo) -> Result<(), ErrorChain> { let (res, rx) = oneshot::channel(); self.tx - .send(ChainRequest::WatchAccount(WatchAccount::new(order, res)?)).await.map_err(|_| ErrorChain::MessageDropped)?; + .send(ChainRequest::WatchAccount(WatchAccount::new(order, res)?)) + .await + .map_err(|_| ErrorChain::MessageDropped)?; rx.await.map_err(|_| ErrorChain::MessageDropped)? } @@ -361,6 +350,7 @@ enum ChainRequest { enum ChainTrackerRequest { WatchAccount(WatchAccount), Shutdown(oneshot::Sender<()>), + NewBlock(String), } struct WatchAccount { @@ -384,6 +374,131 @@ impl WatchAccount { res, }) } + + async fn check(&mut self, client: &WsClient, metadata: &RuntimeMetadataV15, block: &H256, currency: &CurrencyProperties) -> Result { + let balance = balances_at_account(client, &block, &metadata, &self.address, currency.asset_id).await?; + Ok( balance >= self.amount ) + } +} + +fn start_chain_watch( + c: Chain, + chain_tx: mpsc::Sender, + mut chain_rx: mpsc::Receiver, + task_tracker: TaskTracker, + cancellation_token: CancellationToken, +) -> Result<(), ErrorChain> { + //let (block_source_tx, mut block_source_rx) = mpsc::channel(16); + task_tracker + .clone() + .spawn(format!("Chain {} watcher", c.name.clone()), async move { + let watchdog = 30000; + let mut watched_accounts = Vec::new(); + let mut shutdown = false; + for endpoint in c.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 + match prepare_chain(&client, endpoint, chain_tx.clone(), task_tracker.clone()) + .await + { + Ok(_) => (), + Err(e) => { + tracing::info!( + "Failed to connect to chain {}, due to {:?} switching RPC server...", + c.name, + e + ); + continue; + } + } + + // fulfill requests + while let Ok(Some(request)) = + timeout(Duration::from_millis(watchdog), chain_rx.recv()).await + { + match request { + ChainTrackerRequest::NewBlock(block) => { + let block = block_hash(&client, block); + } + ChainTrackerRequest::WatchAccount(request) => { + watched_accounts.push(request); + } + ChainTrackerRequest::Shutdown(res) => { + shutdown = true; + let _ = res.send(()); + break; + } + } + } + } + } + Ok(format!("Chain {} monitor shut down", c.name).into()) + }); + Ok(()) +} + +async fn prepare_chain( + client: &WsClient, + rpc_url: &str, + chain_tx: mpsc::Sender, + task_tracker: TaskTracker, +) -> Result<(), ErrorChain> { + let genesis_hash = genesis_hash(&client).await?; + let mut blocks: Subscription = client + .subscribe( + "chain_subscribeFinalizedHeads", + rpc_params![], + "unsubscribe blocks", + ) + .await?; + let block = next_block(client, &mut blocks).await?; + let metadata = metadata(&client, &block).await?; + let specs = specs(&client, &metadata, &block).await?; + let assets = + assets_set_at_block(&client, &block, &metadata, rpc_url, specs.base58prefix).await?; + task_tracker.spawn("watching blocks at {rpc_url}", async move { + while let Ok(block) = next_block_number(&mut blocks).await { + if chain_tx + .send(ChainTrackerRequest::NewBlock(block)) + .await + .is_err() + { + break; + } + } + Ok("Block watch at {rpc_url} stopped".into()) + }); + + Ok(()) +} + +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(ErrorChain::BlockSubscriptionTerminated), + } +} + +async fn next_block( + client: &WsClient, + blocks: &mut Subscription, +) -> Result { + block_hash(&client, next_block_number(blocks).await?).await +} + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "kebab-case")] +struct BlockHead { + //digest: Value, + //extrinsics_root: String, + pub number: String, + //parent_hash: String, + //state_root: String, } #[derive(Deserialize, Debug)] @@ -411,13 +526,12 @@ where /// Get all sufficient assets from a chain pub async fn assets_set_at_block( client: &WsClient, - block_hash: &str, - metadata_v15: RuntimeMetadataV15, - rpc_url: String, + block_hash: &H256, + metadata_v15: &RuntimeMetadataV15, + rpc_url: &str, ss58: u16, -) -> HashMap { - //let metadata_v15 = metadata_v15(address, block_hash).await.unwrap(); - +) -> Result, ErrorChain> { + let block_hash = &hex::encode(&block_hash.0); let mut assets_set = HashMap::new(); let mut assets_asset_storage_metadata = None; @@ -447,9 +561,8 @@ pub async fn assets_set_at_block( let assets_asset_storage_metadata = assets_asset_storage_metadata.unwrap(); let assets_metadata_storage_metadata = assets_metadata_storage_metadata.unwrap(); - let available_keys_assets_asset = get_keys_from_storage(client, "Assets", "Asset", block_hash) - .await - .unwrap(); + let available_keys_assets_asset = + get_keys_from_storage(client, "Assets", "Asset", block_hash).await?; if let Value::Array(ref keys_array) = available_keys_assets_asset { for key in keys_array.iter() { if let Value::String(string_key) = key { @@ -641,7 +754,7 @@ pub async fn assets_set_at_block( chain_name: "TODO".into(), kind: TokenKind::Asset, decimals, - rpc_url: rpc_url.clone(), + rpc_url: rpc_url.to_string(), asset_id: Some(asset_id), ss58, }, @@ -663,5 +776,98 @@ pub async fn assets_set_at_block( } } } - assets_set + Ok(assets_set) +} + +pub async fn balances_at_account( + client: &WsClient, + block_hash: &H256, + metadata_v15: &RuntimeMetadataV15, + account_id: &AccountId32, + asset_id: Option, +) -> Result { + let block_hash = &hex::encode(&block_hash.0); + if let Some(asset_id) = asset_id { + let queries = balance_queries(metadata_v15, account_id, asset_id); + + let value_fetch = get_value_from_storage(client, &queries.system_account.key, block_hash) + .await + .unwrap(); + if let Value::String(ref string_value) = value_fetch { + let value_data = hex::decode(string_value.trim_start_matches("0x")).unwrap(); + let value = decode_all_as_type::<&[u8], (), RuntimeMetadataV15>( + &queries.system_account.value_ty, + &value_data.as_ref(), + &mut (), + &metadata_v15.types, + ) + .unwrap(); + 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 + { + println!("free balance in system: {value}") + } + } + if inner_field.field_name == Some("reserved".to_string()) { + if let ParsedData::PrimitiveU128 { + value, + specialty: SpecialtyUnsignedInteger::Balance, + } = inner_field.data.data + { + println!("reserved balance in system: {value}") + } + } + if inner_field.field_name == Some("frozen".to_string()) { + if let ParsedData::PrimitiveU128 { + value, + specialty: SpecialtyUnsignedInteger::Balance, + } = inner_field.data.data + { + println!("frozen balance in system: {value}") + } + } + } + } + } + } + } + } + + let value_fetch = get_value_from_storage(client, &queries.assets_account.key, block_hash) + .await + .unwrap(); + if let Value::String(ref string_value) = value_fetch { + let value_data = hex::decode(string_value.trim_start_matches("0x")).unwrap(); + let value = decode_all_as_type::<&[u8], (), RuntimeMetadataV15>( + &queries.assets_account.value_ty, + &value_data.as_ref(), + &mut (), + &metadata_v15.types, + ) + .unwrap(); + 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)) + } + }; + panic!(); + } else { panic!() } + } else { panic!() } + } else { +panic!("native tokens not implemented"); + } } + diff --git a/src/state.rs b/src/state.rs index e2e451d..2237049 100644 --- a/src/state.rs +++ b/src/state.rs @@ -66,28 +66,32 @@ impl State { kalatori_remark: remark.clone(), }; - let state = StateData { - currencies, - recipient: recipient_ss58, - server_info, - db, - seed_entropy, - }; // Remember to always spawn async here or things might deadlock task_tracker.spawn("State Handler", async move { let chain_manager = chain_manager.await.map_err(|_| Error::Fatal)?; + let state = StateData { + currencies, + recipient: recipient_ss58, + server_info, + db, + chain_manager, + seed_entropy, + }; + while let Some(request) = rx.recv().await { match request { StateAccessRequest::GetInvoiceStatus(request) => { request .res - .send(state.get_invoice_status(request.order).await).map_err(|_| Error::Fatal)?; + .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)?; + .send(state.create_invoice(request.order_query).await) + .map_err(|_| Error::Fatal)?; } StateAccessRequest::ServerStatus(res) => { let server_status = ServerStatus { @@ -98,7 +102,7 @@ impl State { } // Orchestrate shutdown from here StateAccessRequest::Shutdown => { - chain_manager.shutdown().await; + state.chain_manager.shutdown().await; state.db.shutdown().await; break; } @@ -118,13 +122,17 @@ impl State { order: order.to_string(), res, })) - .await.map_err(|_| Error::Fatal)?; + .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)?; + self.tx + .send(StateAccessRequest::ServerStatus(res)) + .await + .map_err(|_| Error::Fatal)?; rx.await.map_err(|_| Error::Fatal) } @@ -143,7 +151,8 @@ impl State { order_query, res, })) - .await.map_err(|_| Error::Fatal)?; + .await + .map_err(|_| Error::Fatal)?; rx.await.map_err(|_| Error::Fatal)? } @@ -180,6 +189,7 @@ struct StateData { recipient: String, server_info: ServerInfo, db: Database, + chain_manager: ChainManager, seed_entropy: Entropy, } @@ -218,16 +228,21 @@ impl StateData { .create_order(order.clone(), order_info.clone()) .await? { - OrderCreateResponse::New => Ok(OrderResponse::NewOrder(self.order_status( - order, - order_info, - String::new(), - ))), - OrderCreateResponse::Modified => Ok(OrderResponse::ModifiedOrder(self.order_status( - order, - order_info, - String::new(), - ))), + OrderCreateResponse::New => { + + Ok(OrderResponse::NewOrder(self.order_status( + order, + order_info, + String::new(), + ))) + }, + OrderCreateResponse::Modified => { + Ok(OrderResponse::ModifiedOrder(self.order_status( + order, + order_info, + String::new(), + ))) + }, OrderCreateResponse::Collision(order_status) => { Ok(OrderResponse::CollidedOrder(self.order_status( order, From 33f8dbc7c2b444635f064e7af8a365f51e48dc8e Mon Sep 17 00:00:00 2001 From: Slesarev Date: Wed, 8 May 2024 10:31:35 +0300 Subject: [PATCH 28/76] feat: balance check --- src/chain.rs | 208 +++++++++++++++++++++----------- src/database.rs | 15 ++- src/error.rs | 34 ++++++ src/rpc.rs | 310 +++++++++++++++++++++++++++++++++++++----------- src/state.rs | 50 +++++--- 5 files changed, 459 insertions(+), 158 deletions(-) diff --git a/src/chain.rs b/src/chain.rs index 67aaf81..0f3d2cf 100644 --- a/src/chain.rs +++ b/src/chain.rs @@ -1,38 +1,85 @@ //! Utils to process chain data without accessing the chain //TransactionToFill::init(&mut (), metadata, genesis_hash).unwrap(); -use crate::error::ErrorChain; +use crate::{definitions::api_v2::AssetId, error::ErrorChain}; use frame_metadata::{ v14::StorageHasher, - v15::{RuntimeMetadataV15, StorageEntryType}, + v15::{RuntimeMetadataV15, StorageEntryMetadata, StorageEntryType}, }; use parity_scale_codec::{Decode, Encode}; -use scale_info::{TypeDef, TypeDefPrimitive}; +use scale_info::{form::PortableForm, TypeDef, TypeDefPrimitive}; use serde_json::{Map, Number, Value}; use sp_crypto_hashing::{blake2_128, blake2_256, twox_128, twox_256, twox_64}; +use substrate_constructor::{ + fill_prepare::{PrimitiveToFill, SpecialTypeToFill, TypeContentToFill, UnsignedToFill}, + storage_query::{ + EntrySelector, EntrySelectorFunctional, FinalizedStorageQuery, StorageEntryTypeToFill, + StorageSelector, StorageSelectorFunctional, + }, +}; use substrate_crypto_light::common::{AccountId32, DeriveJunction, FullDerivation}; use substrate_parser::{ - cards::{ExtendedData, ParsedData}, + cards::{ExtendedData, FieldData, ParsedData}, decode_all_as_type, AsMetadata, ShortSpecs, }; -use substrate_constructor::{fill_prepare::{PrimitiveToFill, SpecialTypeToFill, TypeContentToFill, UnsignedToFill}, storage_query::{EntrySelector, EntrySelectorFunctional, FinalizedStorageQuery, StorageEntryTypeToFill, StorageSelector, StorageSelectorFunctional}}; -#[derive(Clone, Debug)] -pub struct FinalizedQueries { - pub system_account: FinalizedStorageQuery, - pub assets_account: FinalizedStorageQuery, +pub fn events_entry_metadata( + metadata: &RuntimeMetadataV15, +) -> Result<&StorageEntryMetadata, ErrorChain> { + 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(ErrorChain::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 balance_queries( +pub fn asset_balance_query( metadata_v15: &RuntimeMetadataV15, account_id: &AccountId32, - asset_id: u32, -) -> FinalizedQueries { + asset_id: AssetId, +) -> Result { let storage_selector = StorageSelector::init(&mut (), metadata_v15).unwrap(); if let StorageSelector::Functional(mut storage_selector_functional) = storage_selector { - let mut index_system_in_pallet_selector = None; - let mut index_assets_in_pallet_selector = None; + let mut index_assets_in_pallet_selector: Option = None; for (index, pallet) in storage_selector_functional .available_pallets @@ -40,31 +87,22 @@ pub fn balance_queries( .enumerate() { match pallet.prefix.as_str() { - "System" => index_system_in_pallet_selector = Some(index), "Assets" => index_assets_in_pallet_selector = Some(index), _ => {} } - if index_system_in_pallet_selector.is_some() - && index_assets_in_pallet_selector.is_some() - { + if index_assets_in_pallet_selector.is_some() { break; } } - let index_system_in_pallet_selector = index_system_in_pallet_selector.unwrap(); let index_assets_in_pallet_selector = index_assets_in_pallet_selector.unwrap(); - let mut system_account_query = None; - let mut assets_account_query = None; - - // System - Account storage_selector_functional = StorageSelectorFunctional::new_at::<(), RuntimeMetadataV15>( &storage_selector_functional.available_pallets, &mut (), &metadata_v15.types, - index_system_in_pallet_selector, + index_assets_in_pallet_selector, ) .unwrap(); - if let EntrySelector::Functional(ref mut entry_selector_functional) = storage_selector_functional.query.entry_selector { @@ -93,25 +131,81 @@ pub fn balance_queries( 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(substrate_parser::additional_types::AccountId32(account_id.0)) + 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( + substrate_parser::additional_types::AccountId32(account_id.0), + ) + } + 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(ErrorChain::StorageQuery) + } else { + Err(ErrorChain::StorageQuery) + } +} - system_account_query = storage_selector_functional.query.finalize().unwrap(); +pub fn system_balance_query( + metadata_v15: &RuntimeMetadataV15, + account_id: &AccountId32, +) -> Result { + let storage_selector = StorageSelector::init(&mut (), metadata_v15).unwrap(); + 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; + } } + let index_system_in_pallet_selector = index_system_in_pallet_selector.unwrap(); - // Assets - Account storage_selector_functional = StorageSelectorFunctional::new_at::<(), RuntimeMetadataV15>( &storage_selector_functional.available_pallets, &mut (), &metadata_v15.types, - index_assets_in_pallet_selector, + index_system_in_pallet_selector, ) .unwrap(); + if let EntrySelector::Functional(ref mut entry_selector_functional) = storage_selector_functional.query.entry_selector { @@ -140,45 +234,23 @@ pub fn balance_queries( 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(substrate_parser::additional_types::AccountId32(account_id.0)), - 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); - } - } - _ => {} - } - } + if let TypeContentToFill::SpecialType(SpecialTypeToFill::AccountId32( + ref mut account_to_fill, + )) = key_to_fill.content + { + *account_to_fill = Some(substrate_parser::additional_types::AccountId32( + account_id.0, + )) } } - - assets_account_query = storage_selector_functional.query.finalize().unwrap(); } - - return FinalizedQueries { - system_account: system_account_query.unwrap(), - assets_account: assets_account_query.unwrap(), - }; + storage_selector_functional + .query + .finalize()? + .ok_or(ErrorChain::StorageQuery) + } else { + Err(ErrorChain::StorageQuery) } - panic!("was unable to find something"); } pub fn derivations<'a>(recipient: &'a str, order: &'a str) -> FullDerivation<'a> { diff --git a/src/database.rs b/src/database.rs index 9ee236e..f2f8047 100644 --- a/src/database.rs +++ b/src/database.rs @@ -251,11 +251,11 @@ impl Database { rx.await.map_err(|_| ErrorDb::DbEngineDown)? } - pub async fn mark_paid(&self, order: String) -> Result<(), ErrorDb> { + pub async fn mark_paid(&self, order: String) -> Result { let (res, rx) = oneshot::channel(); let _unused = self .tx - .send(DbRequest::MarkPaid(ModifyOrder { order, res })) + .send(DbRequest::MarkPaid(MarkPaid { order, res })) .await; rx.await.map_err(|_| ErrorDb::DbEngineDown)? } @@ -288,7 +288,7 @@ impl Database { enum DbRequest { CreateOrder(CreateOrder), ReadOrder(ReadOrder), - MarkPaid(ModifyOrder), + MarkPaid(MarkPaid), MarkWithdrawn(ModifyOrder), MarkStuck(ModifyOrder), Shutdown(oneshot::Sender<()>), @@ -310,6 +310,11 @@ pub struct ModifyOrder { pub res: oneshot::Sender>, } +pub struct MarkPaid { + pub order: String, + pub res: oneshot::Sender>, +} + fn create_order( order: String, order_info: OrderInfo, @@ -341,13 +346,13 @@ fn read_order(order: String, orders: &sled::Tree) -> Result, E } } -fn mark_paid(order: String, orders: &sled::Tree) -> Result<(), ErrorDb> { +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(()) + Ok(order_info) } else { Err(ErrorDb::AlreadyPaid(order)) } diff --git a/src/error.rs b/src/error.rs index 02a1bfd..2ef46c3 100644 --- a/src/error.rs +++ b/src/error.rs @@ -5,6 +5,7 @@ use mnemonic_external::error::ErrorWordList; use primitive_types::H256; use serde_json::Value; use sled::Error as DatabaseError; +use substrate_constructor::error::*; use substrate_crypto_light::error::Error as CryptoError; use substrate_parser::error::*; use tokio::task::JoinError; @@ -260,6 +261,27 @@ pub enum ErrorChain { #[error("Block subscription terminated")] BlockSubscriptionTerminated, + + #[error("Metadata error: {0:?}")] + MetaVersionErrorPallets(MetaVersionErrorPallets), + + #[error("Storage registry error: {0:?}")] + StorageRegistryError(StorageRegistryError), + + #[error("Balance was not found")] + BalanceNotFound, + + #[error("Storage query could not be formed")] + StorageQuery, + + #[error("Storage format error")] + StorageFormatError, + + #[error("Events could not be fetched")] + EventsMissing, + + #[error("Events do not exist in this chain")] + EventsNonexistant, } impl From for ErrorChain { @@ -280,6 +302,18 @@ impl From for ErrorChain { } } +impl From for ErrorChain { + fn from(e: MetaVersionErrorPallets) -> Self { + ErrorChain::MetaVersionErrorPallets(e) + } +} + +impl From for ErrorChain { + fn from(e: StorageRegistryError) -> Self { + ErrorChain::StorageRegistryError(e) + } +} + #[derive(Debug, thiserror::Error)] pub enum ErrorDb { #[error("Currency key is not found")] diff --git a/src/rpc.rs b/src/rpc.rs index 4dc81b7..81378fe 100644 --- a/src/rpc.rs +++ b/src/rpc.rs @@ -1,7 +1,7 @@ use crate::{ chain::{ - base58prefix, balance_queries, hashed_key_element, pallet_index, storage_key, - system_properties_to_short_specs, unit, + asset_balance_query, base58prefix, events_entry_metadata, hashed_key_element, pallet_index, storage_key, + system_balance_query, system_properties_to_short_specs, unit, }, definitions::api_v2::{CurrencyProperties, OrderInfo}, definitions::{ @@ -14,7 +14,7 @@ use crate::{ TaskTracker, }; use frame_metadata::{ - v15::{RuntimeMetadataV15, StorageEntryType}, + v15::{RuntimeMetadataV15, StorageEntryMetadata, StorageEntryType}, RuntimeMetadata, }; use jsonrpsee::core::client::{ClientT, Subscription, SubscriptionClientT}; @@ -22,7 +22,7 @@ use jsonrpsee::rpc_params; use jsonrpsee::ws_client::{WsClient, WsClientBuilder}; use parity_scale_codec::{Decode, DecodeAll, Encode}; use primitive_types::H256; -use scale_info::{TypeDef, TypeDefPrimitive}; +use scale_info::{form::PortableForm, PortableRegistry, TypeDef, TypeDefPrimitive}; use serde::{Deserialize, Deserializer}; use serde_json::{Map, Number, Value}; use sp_crypto_hashing::{blake2_128, blake2_256, twox_128, twox_256, twox_64}; @@ -34,7 +34,7 @@ use std::{ }; use substrate_crypto_light::common::{AccountId32, AsBase58}; use substrate_parser::{ - cards::{ExtendedData, ParsedData, Sequence}, + cards::{Event, ExtendedData, FieldData, ParsedData, Sequence}, decode_all_as_type, decode_as_storage_entry, special_indicators::SpecialtyUnsignedInteger, storage_data::{KeyData, KeyPart}, @@ -60,11 +60,18 @@ 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"; +#[derive(Debug)] +pub struct EventFilter<'a> { + pub pallet: &'a str, + pub optional_event_variant: Option<&'a str>, +} + #[derive(Debug)] struct ChainProperties { specs: ShortSpecs, @@ -277,8 +284,10 @@ impl ChainManager { start_chain_watch( c, + ¤cy_map, chain_tx.clone(), chain_rx, + state.interface(), task_tracker.clone(), cancellation_token.clone(), )?; @@ -307,6 +316,7 @@ impl ChainManager { .send(Err(ErrorChain::InvalidCurrency(request.currency))); } } + ChainRequest::Reap(request) => {todo!()} ChainRequest::Shutdown(res) => { for (name, chain) in watch_chain.drain() { let (tx, rx) = oneshot::channel(); @@ -325,15 +335,25 @@ impl ChainManager { Ok(Self { tx }) } - pub async fn add_invoice(&self, order: OrderInfo) -> Result<(), ErrorChain> { + pub async fn add_invoice(&self, id: String, order: OrderInfo) -> Result<(), ErrorChain> { let (res, rx) = oneshot::channel(); self.tx - .send(ChainRequest::WatchAccount(WatchAccount::new(order, res)?)) + .send(ChainRequest::WatchAccount(WatchAccount::new(id, order, res)?)) .await .map_err(|_| ErrorChain::MessageDropped)?; rx.await.map_err(|_| ErrorChain::MessageDropped)? } + pub async fn reap(&self, id: String, order: OrderInfo) -> Result<(), ErrorChain> { + let (res, rx) = oneshot::channel(); + self.tx + .send(ChainRequest::Reap(WatchAccount::new(id, order, res)?)) + .await + .map_err(|_| ErrorChain::MessageDropped)?; + rx.await.map_err(|_| ErrorChain::MessageDropped)? + + } + pub async fn shutdown(&self) -> () { let (tx, rx) = oneshot::channel(); let _unused = self.tx.send(ChainRequest::Shutdown(tx)).await; @@ -345,6 +365,7 @@ impl ChainManager { enum ChainRequest { WatchAccount(WatchAccount), Shutdown(oneshot::Sender<()>), + Reap(WatchAccount), } enum ChainTrackerRequest { @@ -353,7 +374,9 @@ enum ChainTrackerRequest { NewBlock(String), } +#[derive(Debug)] struct WatchAccount { + id: String, address: AccountId32, currency: String, amount: Balance, @@ -362,10 +385,12 @@ struct WatchAccount { impl WatchAccount { fn new( + id: String, order: OrderInfo, res: oneshot::Sender>, ) -> Result { Ok(WatchAccount { + id: id, address: AccountId32::from_base58_string(&order.payment_account) .map_err(ErrorChain::InvoiceAccount)? .0, @@ -374,17 +399,56 @@ impl WatchAccount { res, }) } +} + +#[derive(Clone, Debug)] +struct Invoice { + id: String, + address: AccountId32, + currency: String, + amount: Balance, +} + +impl Invoice { + 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, + } + } - async fn check(&mut self, client: &WsClient, metadata: &RuntimeMetadataV15, block: &H256, currency: &CurrencyProperties) -> Result { - let balance = balances_at_account(client, &block, &metadata, &self.address, currency.asset_id).await?; - Ok( balance >= self.amount ) + async fn check( + &self, + client: &WsClient, + metadata: &RuntimeMetadataV15, + block: &H256, + currency: &HashMap, + ) -> Result { + let currency = currency + .get(&self.currency) + .ok_or(ErrorChain::InvalidCurrency(self.currency.clone()))?; + if let Some(asset_id) = currency.asset_id { + let balance = + asset_balance_at_account(client, &block, &metadata, &self.address, asset_id) + .await?; + Ok(balance >= self.amount) + } else { + let balance = + system_balance_at_account(client, &block, &metadata, &self.address).await?; + Ok(balance >= self.amount) + } } } fn start_chain_watch( c: Chain, + currency_map: &HashMap, chain_tx: mpsc::Sender, mut chain_rx: mpsc::Receiver, + state: State, task_tracker: TaskTracker, cancellation_token: CancellationToken, ) -> Result<(), ErrorChain> { @@ -402,7 +466,7 @@ fn start_chain_watch( } if let Ok(client) = WsClientBuilder::default().build(endpoint).await { // prepare chain - match prepare_chain(&client, endpoint, chain_tx.clone(), task_tracker.clone()) + match prepare_chain(&client, &mut watched_accounts, endpoint, chain_tx.clone(), state.interface(), task_tracker.clone()) .await { Ok(_) => (), @@ -416,6 +480,7 @@ fn start_chain_watch( } } + // fulfill requests while let Ok(Some(request)) = timeout(Duration::from_millis(watchdog), chain_rx.recv()).await @@ -423,9 +488,10 @@ fn start_chain_watch( match request { ChainTrackerRequest::NewBlock(block) => { let block = block_hash(&client, block); + } ChainTrackerRequest::WatchAccount(request) => { - watched_accounts.push(request); + watched_accounts.push(Invoice::from_request(request)); } ChainTrackerRequest::Shutdown(res) => { shutdown = true; @@ -443,8 +509,10 @@ fn start_chain_watch( async fn prepare_chain( client: &WsClient, + watched_accounts: &mut Vec, rpc_url: &str, chain_tx: mpsc::Sender, + state: State, task_tracker: TaskTracker, ) -> Result<(), ErrorChain> { let genesis_hash = genesis_hash(&client).await?; @@ -458,8 +526,24 @@ async fn prepare_chain( let block = next_block(client, &mut blocks).await?; let metadata = metadata(&client, &block).await?; let specs = specs(&client, &metadata, &block).await?; - let assets = - assets_set_at_block(&client, &block, &metadata, rpc_url, specs.base58prefix).await?; + let assets = assets_set_at_block(&client, &block, &metadata, rpc_url, specs).await?; + + // check monitored accounts + let mut new_accounts = Vec::new(); + for invoice in watched_accounts.iter() { + match invoice.check(client, &metadata, &block, &assets).await { + Ok(true) => { + state.order_paid(invoice.id.clone()); + } + Ok(false) => new_accounts.push(invoice.to_owned()), + Err(e) => { + tracing::warn!("account fetch error: {0:?}", e); + new_accounts.push(invoice.to_owned()); + } + } + } + *watched_accounts = new_accounts; + task_tracker.spawn("watching blocks at {rpc_url}", async move { while let Ok(block) = next_block_number(&mut blocks).await { if chain_tx @@ -470,6 +554,8 @@ async fn prepare_chain( break; } } + // this should reset chain monitor on timeout; + // but if this breaks, it meand that the latter is already down either way Ok("Block watch at {rpc_url} stopped".into()) }); @@ -500,7 +586,7 @@ struct BlockHead { //parent_hash: String, //state_root: String, } - + /* #[derive(Deserialize, Debug)] struct Transferred { asset_id: u32, @@ -510,14 +596,14 @@ struct Transferred { #[serde(deserialize_with = "account_deserializer")] to: AccountId32, amount: u128, -} - +}*/ +/* fn account_deserializer<'de, D>(deserializer: D) -> Result where D: Deserializer<'de>, { <([u8; 32],)>::deserialize(deserializer).map(|address| AccountId32(address.0)) -} +}*/ // TODO: add proper errors // @@ -529,10 +615,23 @@ pub async fn assets_set_at_block( block_hash: &H256, metadata_v15: &RuntimeMetadataV15, rpc_url: &str, - ss58: u16, + specs: ShortSpecs, ) -> Result, ErrorChain> { let block_hash = &hex::encode(&block_hash.0); 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; @@ -751,12 +850,12 @@ pub async fn assets_set_at_block( assets_set.insert( symbol, CurrencyProperties { - chain_name: "TODO".into(), + chain_name: chain_name.clone(), kind: TokenKind::Asset, decimals, rpc_url: rpc_url.to_string(), asset_id: Some(asset_id), - ss58, + ss58: specs.base58prefix, }, ); } else { @@ -779,24 +878,63 @@ pub async fn assets_set_at_block( Ok(assets_set) } -pub async fn balances_at_account( +pub async fn asset_balance_at_account( client: &WsClient, block_hash: &H256, metadata_v15: &RuntimeMetadataV15, account_id: &AccountId32, - asset_id: Option, + asset_id: AssetId, ) -> Result { let block_hash = &hex::encode(&block_hash.0); - if let Some(asset_id) = asset_id { - let queries = balance_queries(metadata_v15, account_id, asset_id); + let query = asset_balance_query(metadata_v15, account_id, asset_id)?; - let value_fetch = get_value_from_storage(client, &queries.system_account.key, block_hash) + let value_fetch = get_value_from_storage(client, &query.key, block_hash) .await .unwrap(); if let Value::String(ref string_value) = value_fetch { let value_data = hex::decode(string_value.trim_start_matches("0x")).unwrap(); let value = decode_all_as_type::<&[u8], (), RuntimeMetadataV15>( - &queries.system_account.value_ty, + &query.value_ty, + &value_data.as_ref(), + &mut (), + &metadata_v15.types, + ) + .unwrap(); + 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)); + } + } + panic!(); + } else { + panic!() + } + } else { + panic!() + } +} + +async fn system_balance_at_account( + client: &WsClient, + block_hash: &H256, + metadata_v15: &RuntimeMetadataV15, + account_id: &AccountId32, +) -> Result { + let block_hash = &hex::encode(&block_hash.0); + let query = system_balance_query(metadata_v15, account_id)?; + + let value_fetch = get_value_from_storage(client, &query.key, block_hash) + .await + .unwrap(); + if let Value::String(ref string_value) = value_fetch { + let value_data = hex::decode(string_value.trim_start_matches("0x")).unwrap(); + let value = decode_all_as_type::<&[u8], (), RuntimeMetadataV15>( + &query.value_ty, &value_data.as_ref(), &mut (), &metadata_v15.types, @@ -813,25 +951,7 @@ pub async fn balances_at_account( specialty: SpecialtyUnsignedInteger::Balance, } = inner_field.data.data { - println!("free balance in system: {value}") - } - } - if inner_field.field_name == Some("reserved".to_string()) { - if let ParsedData::PrimitiveU128 { - value, - specialty: SpecialtyUnsignedInteger::Balance, - } = inner_field.data.data - { - println!("reserved balance in system: {value}") - } - } - if inner_field.field_name == Some("frozen".to_string()) { - if let ParsedData::PrimitiveU128 { - value, - specialty: SpecialtyUnsignedInteger::Balance, - } = inner_field.data.data - { - println!("frozen balance in system: {value}") + return Ok(Balance(value)); } } } @@ -841,33 +961,81 @@ pub async fn balances_at_account( } } - let value_fetch = get_value_from_storage(client, &queries.assets_account.key, block_hash) - .await - .unwrap(); - if let Value::String(ref string_value) = value_fetch { - let value_data = hex::decode(string_value.trim_start_matches("0x")).unwrap(); - let value = decode_all_as_type::<&[u8], (), RuntimeMetadataV15>( - &queries.assets_account.value_ty, - &value_data.as_ref(), + Err(ErrorChain::BalanceNotFound) +} + +async fn transfer_events(client: &WsClient, block: &H256, metadata_v15: &RuntimeMetadataV15) -> Result, ErrorChain> { + 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 +} + +pub async fn events_at_block( + client: &WsClient, + block_hash: &H256, + optional_filter: Option>, + events_entry_metadata: &StorageEntryMetadata, + types: &PortableRegistry, +) -> Result, ErrorChain> { + let block_hash = &hex::encode(&block_hash.0); + let keys_from_storage = get_keys_from_storage(client, "System", "Events", block_hash).await?; + let mut out = Vec::new(); + if let Value::Array(ref keys_array) = keys_from_storage { + for key in keys_array { + if let Value::String(key) = key { + let data_from_storage = get_value_from_storage(client, &key, block_hash).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(ErrorChain::StorageFormatError) }; + let storage_data = decode_as_storage_entry::<&[u8], (), RuntimeMetadataV15>( + &key_bytes.as_ref(), + &value_bytes.as_ref(), &mut (), - &metadata_v15.types, - ) - .unwrap(); - 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)) + 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())); + } + } + } + } } - }; - panic!(); - } else { panic!() } - } else { panic!() } - } else { -panic!("native tokens not implemented"); + } + } + return Ok(out); + } + } } + Err(ErrorChain::EventsMissing) } diff --git a/src/state.rs b/src/state.rs index 2237049..9b2499d 100644 --- a/src/state.rs +++ b/src/state.rs @@ -56,7 +56,7 @@ impl State { */ let (tx, mut rx) = tokio::sync::mpsc::channel(1024); - let recipient_ss58 = recipient.to_base58_string(2); //TODO + let recipient_ss58 = recipient.to_base58_string(2); // TODO maybe but spec says use "2" let server_info = ServerInfo { // TODO @@ -66,7 +66,6 @@ impl State { kalatori_remark: remark.clone(), }; - // Remember to always spawn async here or things might deadlock task_tracker.spawn("State Handler", async move { let chain_manager = chain_manager.await.map_err(|_| Error::Fatal)?; @@ -100,9 +99,27 @@ impl State { }; 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 + state.chain_manager.reap(id, order).await; + }, + Err(e) => { + tracing::error!("Order was paid but this could not be recorded! {e:?}") + }, + } + } // Orchestrate shutdown from here StateAccessRequest::Shutdown => { + // 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 and shut down state.db.shutdown().await; break; } @@ -156,6 +173,12 @@ impl State { 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 fn interface(&self) -> Self { State { tx: self.tx.clone(), @@ -171,6 +194,7 @@ enum StateAccessRequest { GetInvoiceStatus(GetInvoiceStatus), CreateInvoice(CreateInvoice), ServerStatus(oneshot::Sender), + OrderPaid(String), Shutdown, } @@ -229,20 +253,18 @@ impl StateData { .await? { OrderCreateResponse::New => { - + self.chain_manager.add_invoice(order.clone(), order_info.clone()).await?; Ok(OrderResponse::NewOrder(self.order_status( - order, - order_info, - String::new(), - ))) - }, - OrderCreateResponse::Modified => { - Ok(OrderResponse::ModifiedOrder(self.order_status( - order, - order_info, - String::new(), - ))) + order, + order_info, + String::new(), + ))) }, + OrderCreateResponse::Modified => Ok(OrderResponse::ModifiedOrder(self.order_status( + order, + order_info, + String::new(), + ))), OrderCreateResponse::Collision(order_status) => { Ok(OrderResponse::CollidedOrder(self.order_status( order, From d2a2cbd694eb2aa2914a9434d9ff0dd81de5f18f Mon Sep 17 00:00:00 2001 From: Slesarev Date: Wed, 8 May 2024 12:10:23 +0300 Subject: [PATCH 29/76] feat: separate signing thread --- Cargo.lock | 26 ++++++++-- Cargo.toml | 1 + src/chain.rs | 16 +----- src/error.rs | 53 +++++++++++++------ src/main.rs | 38 ++------------ src/rpc.rs | 99 ++++++++++++++++++++---------------- src/signer.rs | 137 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/state.rs | 47 +++++++++-------- 8 files changed, 285 insertions(+), 132 deletions(-) create mode 100644 src/signer.rs diff --git a/Cargo.lock b/Cargo.lock index 03bba9b..4a5e9fc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1130,7 +1130,7 @@ dependencies = [ "sp-crypto-hashing", "substrate-constructor", "substrate-crypto-light", - "substrate_parser", + "substrate_parser 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", "thiserror", "tokio", "tokio-util", @@ -1138,6 +1138,7 @@ dependencies = [ "tracing", "tracing-subscriber", "ureq", + "zeroize", ] [[package]] @@ -2188,7 +2189,7 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "substrate-constructor" version = "0.1.0" -source = "git+https://github.com/Alzymologist/substrate-constructor#09decf076dfca2e1c4934e76ed34ac8cf59b2f23" +source = "git+https://github.com/Alzymologist/substrate-constructor#391e0760cde6eb88e787207f5c6b43f1f1c33caf" dependencies = [ "bitvec", "external-memory-tools", @@ -2200,7 +2201,8 @@ dependencies = [ "scale-info", "sp-arithmetic 25.0.0", "sp-crypto-hashing", - "substrate_parser", + "substrate-crypto-light", + "substrate_parser 0.6.1 (git+https://github.com/Alzymologist/substrate-parser)", ] [[package]] @@ -2244,6 +2246,24 @@ dependencies = [ "sp-core-hashing", ] +[[package]] +name = "substrate_parser" +version = "0.6.1" +source = "git+https://github.com/Alzymologist/substrate-parser#c227d40952e3d11b384d30e294f08be2abd1ad43" +dependencies = [ + "bitvec", + "external-memory-tools", + "frame-metadata", + "hex", + "num-bigint", + "parity-scale-codec", + "primitive-types", + "scale-info", + "sp-arithmetic 25.0.0", + "sp-crypto-hashing", + "substrate-crypto-light", +] + [[package]] name = "subtle" version = "2.5.0" diff --git a/Cargo.toml b/Cargo.toml index 89e5b26..ed961c1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,6 +38,7 @@ serde_json = "1.0.116" sp-crypto-hashing = "0.1.0" toml = "0.8.12" sled = "0.34.7" +zeroize = "1.7.0" [profile.release] strip = true diff --git a/src/chain.rs b/src/chain.rs index 0f3d2cf..6de17e8 100644 --- a/src/chain.rs +++ b/src/chain.rs @@ -136,11 +136,7 @@ pub fn asset_balance_query( match ty.content { TypeContentToFill::SpecialType(SpecialTypeToFill::AccountId32( ref mut account_to_fill, - )) => { - *account_to_fill = Some( - substrate_parser::additional_types::AccountId32(account_id.0), - ) - } + )) => *account_to_fill = Some(*account_id), TypeContentToFill::Primitive(PrimitiveToFill::CompactUnsigned( ref mut specialty_unsigned_to_fill, )) => { @@ -238,9 +234,7 @@ pub fn system_balance_query( ref mut account_to_fill, )) = key_to_fill.content { - *account_to_fill = Some(substrate_parser::additional_types::AccountId32( - account_id.0, - )) + *account_to_fill = Some(*account_id) } } } @@ -253,12 +247,6 @@ pub fn system_balance_query( } } -pub fn derivations<'a>(recipient: &'a str, order: &'a str) -> FullDerivation<'a> { - FullDerivation { - junctions: vec![DeriveJunction::hard(recipient), DeriveJunction::hard(order)], - password: None, - } -} pub fn hashed_key_element(data: &[u8], hasher: &StorageHasher) -> Vec { match hasher { diff --git a/src/error.rs b/src/error.rs index 2ef46c3..7c0a85b 100644 --- a/src/error.rs +++ b/src/error.rs @@ -36,17 +36,15 @@ pub enum Error { #[error("Daemon server error: {0:?}")] ErrorServer(ErrorServer), + #[error("Signer error: {0:?}")] + ErrorSigner(ErrorSigner), + #[error("Failed to listen to shutdown signal")] ShutdownSignal, #[error("Receiver account could not be parsed: {0:?}")] RecipientAccount(substrate_crypto_light::error::Error), - #[error("Seed phrase invalid: {0:?}")] - InvalidSeed(ErrorWordList), - - #[error("Derivation failed: {0:?}")] - InvalidDerivation(CryptoError), #[error("Fatal error. System is shutting down.")] Fatal, @@ -82,23 +80,18 @@ impl From for Error { } } -impl From for Error { - fn from(e: std::io::Error) -> Self { - Error::IoError(e) +impl From for Error { + fn from(e: ErrorSigner) -> Error { + Error::ErrorSigner(e) } } -impl From for Error { - fn from(e: ErrorWordList) -> Self { - Error::InvalidSeed(e) +impl From for Error { + fn from(e: std::io::Error) -> Self { + Error::IoError(e) } } -impl From for Error { - fn from(e: CryptoError) -> Self { - Error::InvalidDerivation(e) - } -} #[derive(Debug, thiserror::Error)] pub enum ErrorChain { @@ -401,6 +394,34 @@ pub enum ErrorUtil { NotHex(NotHex), } +#[derive(Debug, thiserror::Error)] +pub enum ErrorSigner { + #[error("failed to read `{0}`")] + Env(String), + + #[error("Signer is down")] + SignerDown, + + #[error("Seed phrase invalid: {0:?}")] + InvalidSeed(ErrorWordList), + + #[error("Derivation failed: {0:?}")] + InvalidDerivation(CryptoError), + +} + +impl From for ErrorSigner { + fn from(e: ErrorWordList) -> Self { + ErrorSigner::InvalidSeed(e) + } +} + +impl From for ErrorSigner { + fn from(e: CryptoError) -> Self { + ErrorSigner::InvalidDerivation(e) + } +} + #[derive(Debug, Eq, PartialEq, thiserror::Error)] pub enum NotHex { #[error("Block hash string is not a valid hexadecimal.")] diff --git a/src/main.rs b/src/main.rs index 03af6bc..a362ce7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,3 @@ -use mnemonic_external::{regular::InternalWordList, WordSet}; use serde::Deserialize; use std::{ borrow::Cow, @@ -29,6 +28,7 @@ mod definitions; mod error; mod rpc; mod server; +mod signer; mod state; mod utils; @@ -36,11 +36,11 @@ use crate::definitions::{Chain, Entropy, Timestamp, Version}; use database::ConfigWoChains; use error::Error; use rpc::ChainManager; +use signer::Signer; use state::State; const CONFIG: &str = "KALATORI_CONFIG"; const LOG: &str = "KALATORI_LOG"; -const SEED: &str = "KALATORI_SEED"; const RECIPIENT: &str = "KALATORI_RECIPIENT"; const REMARK: &str = "KALATORI_REMARK"; const OLD_SEED: &str = "KALATORI_OLD_SEED_"; @@ -60,7 +60,6 @@ async fn main() -> Result<(), Error> { // Read env - let secret_entropy = parse_seeds()?; let recipient = env::var(RECIPIENT).map_err(|_| Error::Env(RECIPIENT.to_string()))?; let remark = env::var(REMARK).map_err(|_| Error::Env(REMARK.to_string()))?; @@ -125,13 +124,15 @@ async fn main() -> Result<(), Error> { .map_err(Error::RecipientAccount)? .0; + let signer = Signer::init(recipient.clone(), task_tracker.clone())?; + let db = database::Database::init(database_path, task_tracker.clone())?; let (cm_tx, mut cm_rx) = oneshot::channel(); let state = State::initialise( currencies, - secret_entropy, + signer, ConfigWoChains { recipient: recipient.clone(), debug: config.debug, @@ -284,35 +285,6 @@ fn default_filter() -> String { filter } -fn parse_seeds() -> Result { - entropy_from_phrase(&env::var(SEED).map_err(|_| Error::Env(SEED.to_string()))?) - - //let mut old_pairs = HashMap::new(); - /* TODO: add this at least when you do something about these - for (raw_key, raw_value) in env::vars_os() { - let raw_key_bytes = raw_key.as_encoded_bytes(); - - if let Some(stripped_raw_key) = raw_key_bytes.strip_prefix(OLD_SEED.as_bytes()) { - let key = str::from_utf8(stripped_raw_key) - .context("failed to read an old seed environment variable name")?; - let value = raw_value - .to_str() - .with_context(|| format!("failed to read a seed phrase from `{OLD_SEED}{key}`"))?; - let old_pair = seed_from_phrase(value)?; - - old_pairs.insert(key.to_owned(), old_pair); - } - } - */ -} - -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()?) -} #[derive(Clone)] struct TaskTracker { diff --git a/src/rpc.rs b/src/rpc.rs index 81378fe..854a19b 100644 --- a/src/rpc.rs +++ b/src/rpc.rs @@ -1,7 +1,7 @@ use crate::{ chain::{ - asset_balance_query, base58prefix, events_entry_metadata, hashed_key_element, pallet_index, storage_key, - system_balance_query, system_properties_to_short_specs, unit, + asset_balance_query, base58prefix, events_entry_metadata, hashed_key_element, pallet_index, + storage_key, system_balance_query, system_properties_to_short_specs, unit, }, definitions::api_v2::{CurrencyProperties, OrderInfo}, definitions::{ @@ -316,7 +316,9 @@ impl ChainManager { .send(Err(ErrorChain::InvalidCurrency(request.currency))); } } - ChainRequest::Reap(request) => {todo!()} + ChainRequest::Reap(request) => { + todo!() + } ChainRequest::Shutdown(res) => { for (name, chain) in watch_chain.drain() { let (tx, rx) = oneshot::channel(); @@ -338,20 +340,21 @@ impl ChainManager { pub async fn add_invoice(&self, id: String, order: OrderInfo) -> Result<(), ErrorChain> { let (res, rx) = oneshot::channel(); self.tx - .send(ChainRequest::WatchAccount(WatchAccount::new(id, order, res)?)) + .send(ChainRequest::WatchAccount(WatchAccount::new( + id, order, res, + )?)) .await .map_err(|_| ErrorChain::MessageDropped)?; rx.await.map_err(|_| ErrorChain::MessageDropped)? } pub async fn reap(&self, id: String, order: OrderInfo) -> Result<(), ErrorChain> { - let (res, rx) = oneshot::channel(); + let (res, rx) = oneshot::channel(); self.tx .send(ChainRequest::Reap(WatchAccount::new(id, order, res)?)) .await .map_err(|_| ErrorChain::MessageDropped)?; rx.await.map_err(|_| ErrorChain::MessageDropped)? - } pub async fn shutdown(&self) -> () { @@ -390,7 +393,7 @@ impl WatchAccount { res: oneshot::Sender>, ) -> Result { Ok(WatchAccount { - id: id, + id, address: AccountId32::from_base58_string(&order.payment_account) .map_err(ErrorChain::InvoiceAccount)? .0, @@ -586,7 +589,7 @@ struct BlockHead { //parent_hash: String, //state_root: String, } - /* +/* #[derive(Deserialize, Debug)] struct Transferred { asset_id: u32, @@ -964,7 +967,11 @@ async fn system_balance_at_account( Err(ErrorChain::BalanceNotFound) } -async fn transfer_events(client: &WsClient, block: &H256, metadata_v15: &RuntimeMetadataV15) -> Result, ErrorChain> { +async fn transfer_events( + client: &WsClient, + block: &H256, + metadata_v15: &RuntimeMetadataV15, +) -> Result, ErrorChain> { let events_entry_metadata = events_entry_metadata(&metadata_v15)?; events_at_block( @@ -991,51 +998,55 @@ pub async fn events_at_block( let keys_from_storage = get_keys_from_storage(client, "System", "Events", block_hash).await?; let mut out = Vec::new(); if let Value::Array(ref keys_array) = keys_from_storage { - for key in keys_array { - if let Value::String(key) = key { - let data_from_storage = get_value_from_storage(client, &key, block_hash).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(ErrorChain::StorageFormatError) }; - 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 - { + for key in keys_array { + if let Value::String(key) = key { + let data_from_storage = get_value_from_storage(client, &key, block_hash).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(ErrorChain::StorageFormatError); + }; + 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())); } - } else if event.pallet_name == filter.pallet { - out.push(Event(event.to_owned())); } - } else { - out.push(Event(event.to_owned())); } } } } } + return Ok(out); } } - return Ok(out); - } - } } Err(ErrorChain::EventsMissing) } - diff --git a/src/signer.rs b/src/signer.rs new file mode 100644 index 0000000..8eeca80 --- /dev/null +++ b/src/signer.rs @@ -0,0 +1,137 @@ +//! 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, ErrorSigner}, TaskTracker}; + +use std::env; + +use mnemonic_external::{regular::InternalWordList, WordSet}; +use substrate_crypto_light::{common::{AccountId32, AsBase58, DeriveJunction, FullDerivation}, sr25519::Pair}; +use tokio::sync::{mpsc, oneshot}; +use zeroize::Zeroize; + +const SEED: &str = "KALATORI_SEED"; + +/// 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, + ) -> Result { + let (tx, mut rx) = mpsc::channel(16); + task_tracker.spawn("Signer", async move { + let mut seed_entropy = parse_seeds()?; // 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) => {todo!()}, + 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(|_| ErrorSigner::SignerDown)?; + rx.await.map_err(|_| ErrorSigner::SignerDown)? + } + + pub async fn sign(&self) -> Result, ErrorSigner> {todo!()} + + 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 { + +} + +/// Read seeds from env +/// +/// TODO: read also old seeds and do something about them +fn parse_seeds() -> Result { + entropy_from_phrase(&env::var(SEED).map_err(|_| ErrorSigner::Env(SEED.to_string()))?) +} + +/// Convert seed phrase to entropy +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()?) +} + +/// Standartized 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 index 9b2499d..3e0efba 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,5 +1,4 @@ use crate::{ - chain::derivations, database::Database, definitions::{ api_v2::{ @@ -10,13 +9,13 @@ use crate::{ }, error::{Error, ErrorOrder}, rpc::ChainManager, + signer::Signer, ConfigWoChains, TaskTracker, }; use std::collections::HashMap; use substrate_crypto_light::common::{AccountId32, AsBase58}; -use substrate_crypto_light::sr25519::Pair; use tokio::sync::oneshot; /// Struct to store state of daemon. If something requires cooperation of more than one component, @@ -29,7 +28,7 @@ pub struct State { impl State { pub fn initialise( currencies: HashMap, - seed_entropy: Entropy, + signer: Signer, ConfigWoChains { recipient, debug, @@ -75,7 +74,7 @@ impl State { server_info, db, chain_manager, - seed_entropy, + signer, }; while let Some(request) = rx.recv().await { @@ -102,13 +101,15 @@ impl State { StateAccessRequest::OrderPaid(id) => { // Only perform actions if the record is saved in ledger match state.db.mark_paid(id.clone()).await { - Ok(order) => { + Ok(order) => { // TODO: callback here state.chain_manager.reap(id, order).await; - }, + } Err(e) => { - tracing::error!("Order was paid but this could not be recorded! {e:?}") - }, + tracing::error!( + "Order was paid but this could not be recorded! {e:?}" + ) + } } } // Orchestrate shutdown from here @@ -174,7 +175,12 @@ impl State { } pub async fn order_paid(&self, order: String) { - if self.tx.send(StateAccessRequest::OrderPaid(order)).await.is_err() { + if self + .tx + .send(StateAccessRequest::OrderPaid(order)) + .await + .is_err() + { tracing::warn!("Data race on shutdown; please restart the daemon for cleaning up"); }; } @@ -214,7 +220,7 @@ struct StateData { server_info: ServerInfo, db: Database, chain_manager: ChainManager, - seed_entropy: Entropy, + signer: Signer, } impl StateData { @@ -240,12 +246,7 @@ impl StateData { .get(&order_query.currency) .ok_or(ErrorOrder::UnknownCurrency)?; let currency = currency.info(order_query.currency.clone()); - let payment_account = Pair::from_entropy_and_full_derivation( - &self.seed_entropy, - derivations(&self.recipient, &order_query.order), - )? - .public() - .to_base58_string(currency.ss58); + let payment_account = self.signer.public(order.clone(), currency.ss58).await?; let order_info = OrderInfo::new(order_query, currency, payment_account); match self .db @@ -253,13 +254,15 @@ impl StateData { .await? { OrderCreateResponse::New => { - self.chain_manager.add_invoice(order.clone(), order_info.clone()).await?; + self.chain_manager + .add_invoice(order.clone(), order_info.clone()) + .await?; Ok(OrderResponse::NewOrder(self.order_status( - order, - order_info, - String::new(), - ))) - }, + order, + order_info, + String::new(), + ))) + } OrderCreateResponse::Modified => Ok(OrderResponse::ModifiedOrder(self.order_status( order, order_info, From a843af03b5c2998e65034595028b09ff23edbed8 Mon Sep 17 00:00:00 2001 From: Slesarev Date: Wed, 8 May 2024 12:19:23 +0300 Subject: [PATCH 30/76] feat: signing --- src/signer.rs | 28 ++++++++++++++++++++++++---- src/state.rs | 7 ++++++- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/src/signer.rs b/src/signer.rs index 8eeca80..fa39d79 100644 --- a/src/signer.rs +++ b/src/signer.rs @@ -14,7 +14,7 @@ use crate::{definitions::Entropy, error::{Error, ErrorSigner}, TaskTracker}; use std::env; use mnemonic_external::{regular::InternalWordList, WordSet}; -use substrate_crypto_light::{common::{AccountId32, AsBase58, DeriveJunction, FullDerivation}, sr25519::Pair}; +use substrate_crypto_light::{common::{AccountId32, AsBase58, DeriveJunction, FullDerivation}, sr25519::{Pair, Signature}}; use tokio::sync::{mpsc, oneshot}; use zeroize::Zeroize; @@ -50,7 +50,18 @@ impl Signer { } ); }, - SignerRequest::Sign(request) => {todo!()}, + 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(()); @@ -73,7 +84,14 @@ impl Signer { rx.await.map_err(|_| ErrorSigner::SignerDown)? } - pub async fn sign(&self) -> Result, ErrorSigner> {todo!()} + 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(|_| ErrorSigner::SignerDown)?; + rx.await.map_err(|_| ErrorSigner::SignerDown)? + } pub async fn shutdown(&self) { let (tx, rx) = oneshot::channel(); @@ -108,7 +126,9 @@ struct PublicKeyRequest { /// Bytes to sign, with callback struct Sign { - + id: String, + signable: Vec, + res: oneshot::Sender>, } /// Read seeds from env diff --git a/src/state.rs b/src/state.rs index 3e0efba..3421461 100644 --- a/src/state.rs +++ b/src/state.rs @@ -120,8 +120,13 @@ impl State { // happens, we should record it in db. state.chain_manager.shutdown().await; - // Now that nothing happens we can wind down the ledger and shut down + // 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; } }; From 770f4129228b73baa8879c8ae0236d304eff92d4 Mon Sep 17 00:00:00 2001 From: Slesarev Date: Mon, 13 May 2024 23:01:17 +0300 Subject: [PATCH 31/76] feat: payout stub --- Cargo.lock | 62 +---- Cargo.toml | 2 +- src/chain.rs | 577 ++++++++++++++++++++++++++++++++++++++++++++- src/definitions.rs | 10 +- src/error.rs | 3 - src/main.rs | 5 +- src/payout.rs | 93 ++++++++ src/rpc.rs | 354 ++++++++++++++++++++------- src/signer.rs | 51 ++-- src/state.rs | 12 +- 10 files changed, 987 insertions(+), 182 deletions(-) create mode 100644 src/payout.rs diff --git a/Cargo.lock b/Cargo.lock index 4a5e9fc..3f9c019 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -240,15 +240,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" @@ -1130,7 +1121,7 @@ dependencies = [ "sp-crypto-hashing", "substrate-constructor", "substrate-crypto-light", - "substrate_parser 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", + "substrate_parser", "thiserror", "tokio", "tokio-util", @@ -2101,20 +2092,6 @@ dependencies = [ "sha-1", ] -[[package]] -name = "sp-arithmetic" -version = "24.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afa823ca5adc490d47dccb41d69ad482bc57a317bd341de275868378f48f131c" -dependencies = [ - "integer-sqrt", - "num-traits", - "parity-scale-codec", - "scale-info", - "sp-std", - "static_assertions", -] - [[package]] name = "sp-arithmetic" version = "25.0.0" @@ -2129,15 +2106,6 @@ dependencies = [ "static_assertions", ] -[[package]] -name = "sp-core-hashing" -version = "16.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f812cb2dff962eb378c507612a50f1c59f52d92eb97b710f35be3c2346a3cd7" -dependencies = [ - "sp-crypto-hashing", -] - [[package]] name = "sp-crypto-hashing" version = "0.1.0" @@ -2199,10 +2167,10 @@ dependencies = [ "parity-scale-codec", "primitive-types", "scale-info", - "sp-arithmetic 25.0.0", + "sp-arithmetic", "sp-crypto-hashing", "substrate-crypto-light", - "substrate_parser 0.6.1 (git+https://github.com/Alzymologist/substrate-parser)", + "substrate_parser", ] [[package]] @@ -2225,27 +2193,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "substrate_parser" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3692eaf5785c67a07d884a3308c783916307300085fe160b8d9277b389505fb9" -dependencies = [ - "base58", - "bitvec", - "blake2", - "external-memory-tools", - "frame-metadata", - "hex", - "num-bigint", - "parity-scale-codec", - "plot_icon", - "primitive-types", - "scale-info", - "sp-arithmetic 24.0.0", - "sp-core-hashing", -] - [[package]] name = "substrate_parser" version = "0.6.1" @@ -2257,9 +2204,10 @@ dependencies = [ "hex", "num-bigint", "parity-scale-codec", + "plot_icon", "primitive-types", "scale-info", - "sp-arithmetic 25.0.0", + "sp-arithmetic", "sp-crypto-hashing", "substrate-crypto-light", ] diff --git a/Cargo.toml b/Cargo.toml index ed961c1..e058e9d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,7 +29,7 @@ substrate-crypto-light = { git = "https://github.com/Alzymologist/substrate-cryp jsonrpsee = { version = "0.22.4", features = ["ws-client"] } mnemonic-external = { git = "https://github.com/Alzymologist/mnemonic-external" } thiserror = "1.0.58" -substrate_parser = "0.6.1" #{ git = "https://github.com/Alzymologist/substrate-parser" } +substrate_parser = { git = "https://github.com/Alzymologist/substrate-parser" } substrate-constructor = { git = "https://github.com/Alzymologist/substrate-constructor" } frame-metadata = "16.0.0" hex = "0.4.3" diff --git a/src/chain.rs b/src/chain.rs index 6de17e8..3b43b20 100644 --- a/src/chain.rs +++ b/src/chain.rs @@ -7,11 +7,17 @@ use frame_metadata::{ v15::{RuntimeMetadataV15, StorageEntryMetadata, StorageEntryType}, }; use parity_scale_codec::{Decode, Encode}; +use primitive_types::H256; use scale_info::{form::PortableForm, TypeDef, TypeDefPrimitive}; use serde_json::{Map, Number, Value}; use sp_crypto_hashing::{blake2_128, blake2_256, twox_128, twox_256, twox_64}; use substrate_constructor::{ - fill_prepare::{PrimitiveToFill, SpecialTypeToFill, TypeContentToFill, UnsignedToFill}, + 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, @@ -20,9 +26,575 @@ use substrate_constructor::{ use substrate_crypto_light::common::{AccountId32, DeriveJunction, FullDerivation}; use substrate_parser::{ cards::{ExtendedData, FieldData, ParsedData}, - decode_all_as_type, AsMetadata, ShortSpecs, + decode_all_as_type, + decoding_sci::Ty, + propagated::Propagated, + special_indicators::SpecialtyUnsignedInteger, + AsMetadata, 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, +) -> CallToFill { + let mut call = prepare_type::<(), RuntimeMetadataV15>( + &Ty::Symbol(&metadata.extrinsic.call_ty), + &mut (), + &metadata.types, + Propagated::new(), + ) + .unwrap(); + + 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; + } + } + + let index_assets_in_pallets = index_assets_in_pallets.unwrap(); + + *pallet_selector = VariantSelector::new_at::<(), RuntimeMetadataV15>( + &pallet_selector.available_variants, + &mut (), + &metadata.types, + index_assets_in_pallets, + ) + .unwrap(); + + 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; + } + } + + let index_transfer_in_methods = index_transfer_in_methods.unwrap(); + + *method_selector = VariantSelector::new_at::<(), RuntimeMetadataV15>( + &method_selector.available_variants, + &mut (), + &metadata.types, + index_transfer_in_methods, + ) + .unwrap(); + + 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; + } + } + + let index_account_id_in_dest_selector = + index_account_id_in_dest_selector.unwrap(); + + *dest_selector = + VariantSelector::new_at::<(), RuntimeMetadataV15>( + &dest_selector.available_variants, + &mut (), + &metadata.types, + index_account_id_in_dest_selector, + ) + .unwrap(); + + 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); + } + _ => {} + }, + _ => {} + } + } + } + } + } + } + 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: H256, + author: AccountId32, + call_set: &[CallToFill], + block_hash: H256, + block_number: u32, + nonce: u32, +) -> TransactionToFill { + let mut transaction_to_fill = TransactionToFill::init(&mut (), metadata, genesis_hash).unwrap(); + + // 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; + } + } + + let index_account_id = index_account_id.unwrap(); + + *variant_selector = VariantSelector::new_at::<(), RuntimeMetadataV15>( + &variant_selector.available_variants, + &mut (), + &metadata.types, + index_account_id, + ) + .unwrap(); + + 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_hash), 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:?}"); + } + } + + transaction_to_fill +} + +pub fn construct_batch_call(metadata: &RuntimeMetadataV15, call_set: &[CallToFill]) -> CallToFill { + let mut call = prepare_type::<(), RuntimeMetadataV15>( + &Ty::Symbol(&metadata.extrinsic.call_ty), + &mut (), + &metadata.types, + Propagated::new(), + ) + .unwrap(); + + 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; + } + } + + let index_utility_in_pallets = index_utility_in_pallets.unwrap(); + + *pallet_selector = VariantSelector::new_at::<(), RuntimeMetadataV15>( + &pallet_selector.available_variants, + &mut (), + &metadata.types, + index_utility_in_pallets, + ) + .unwrap(); + + 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; + } + } + + let index_batch_all_in_methods = index_batch_all_in_methods.unwrap(); + + *method_selector = VariantSelector::new_at::<(), RuntimeMetadataV15>( + &method_selector.available_variants, + &mut (), + &metadata.types, + index_batch_all_in_methods, + ) + .unwrap(); + + 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(); + } + } + } + } + } + CallToFill(call) +} + +pub fn construct_single_balance_transfer_call( + metadata: &RuntimeMetadataV15, + balance_transfer_constructor: &BalanceTransferConstructor, +) -> CallToFill { + let mut call = prepare_type::<(), RuntimeMetadataV15>( + &Ty::Symbol(&metadata.extrinsic.call_ty), + &mut (), + &metadata.types, + Propagated::new(), + ) + .unwrap(); + + 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; + } + } + + let index_balances_in_pallets = index_balances_in_pallets.unwrap(); + + *pallet_selector = VariantSelector::new_at::<(), RuntimeMetadataV15>( + &pallet_selector.available_variants, + &mut (), + &metadata.types, + index_balances_in_pallets, + ) + .unwrap(); + + 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; + } + } + + let index_transfer_in_methods = index_transfer_in_methods.unwrap(); + + *method_selector = VariantSelector::new_at::<(), RuntimeMetadataV15>( + &method_selector.available_variants, + &mut (), + &metadata.types, + index_transfer_in_methods, + ) + .unwrap(); + + 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; + } + } + + let index_account_id_in_dest_selector = + index_account_id_in_dest_selector.unwrap(); + + *dest_selector = + VariantSelector::new_at::<(), RuntimeMetadataV15>( + &dest_selector.available_variants, + &mut (), + &metadata.types, + index_account_id_in_dest_selector, + ) + .unwrap(); + + 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); + } + _ => {} + }, + _ => {} + } + } + } + } + } + } + CallToFill(call) +} + +pub fn block_number_query(metadata_v15: &RuntimeMetadataV15) -> FinalizedStorageQuery { + let storage_selector = StorageSelector::init(&mut (), metadata_v15).unwrap(); + + 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; + } + } + + let index_system_in_pallet_selector = index_system_in_pallet_selector.unwrap(); + + // 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, + ) + .unwrap(); + + 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; + } + } + let entry_index = entry_index.unwrap(); + *entry_selector_functional = EntrySelectorFunctional::new_at::<(), RuntimeMetadataV15>( + &entry_selector_functional.available_entries, + &mut (), + &metadata_v15.types, + entry_index, + ) + .unwrap(); + + storage_selector_functional + .query + .finalize() + .unwrap() + .unwrap() + } else { + panic!("no storage variants in system pallet") + } + } else { + panic!("no pallets with storage") + } +} + pub fn events_entry_metadata( metadata: &RuntimeMetadataV15, ) -> Result<&StorageEntryMetadata, ErrorChain> { @@ -247,7 +819,6 @@ pub fn system_balance_query( } } - pub fn hashed_key_element(data: &[u8], hasher: &StorageHasher) -> Vec { match hasher { StorageHasher::Blake2_128 => blake2_128(data).to_vec(), diff --git a/src/definitions.rs b/src/definitions.rs index 55a61ec..2ee0108 100644 --- a/src/definitions.rs +++ b/src/definitions.rs @@ -1,6 +1,6 @@ //! Deaf and dumb object definitions -use std::ops::Deref; +use std::ops::{Deref, Sub}; use serde::Deserialize; @@ -48,6 +48,14 @@ impl Deref for Balance { } } +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)] diff --git a/src/error.rs b/src/error.rs index 7c0a85b..ac95d3c 100644 --- a/src/error.rs +++ b/src/error.rs @@ -45,7 +45,6 @@ pub enum Error { #[error("Receiver account could not be parsed: {0:?}")] RecipientAccount(substrate_crypto_light::error::Error), - #[error("Fatal error. System is shutting down.")] Fatal, @@ -92,7 +91,6 @@ impl From for Error { } } - #[derive(Debug, thiserror::Error)] pub enum ErrorChain { #[error("Format of fetched base58 prefix {value} is not supported.")] @@ -407,7 +405,6 @@ pub enum ErrorSigner { #[error("Derivation failed: {0:?}")] InvalidDerivation(CryptoError), - } impl From for ErrorSigner { diff --git a/src/main.rs b/src/main.rs index a362ce7..60ec605 100644 --- a/src/main.rs +++ b/src/main.rs @@ -26,6 +26,7 @@ mod chain; mod database; mod definitions; mod error; +mod payout; mod rpc; mod server; mod signer; @@ -132,7 +133,7 @@ async fn main() -> Result<(), Error> { let state = State::initialise( currencies, - signer, + signer.interface(), ConfigWoChains { recipient: recipient.clone(), debug: config.debug, @@ -167,6 +168,7 @@ async fn main() -> Result<(), Error> { .send(ChainManager::ignite( config.chain, state.interface(), + signer.interface(), task_tracker.clone(), shutdown_notification.clone(), )?) @@ -285,7 +287,6 @@ fn default_filter() -> String { filter } - #[derive(Clone)] struct TaskTracker { inner: task::TaskTracker, diff --git a/src/payout.rs b/src/payout.rs new file mode 100644 index 0000000..e6f3b5d --- /dev/null +++ b/src/payout.rs @@ -0,0 +1,93 @@ +//! 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::{AssetTransferConstructor, BalanceTransferConstructor, construct_batch_transaction, construct_single_asset_transfer_call, construct_single_balance_transfer_call}, + definitions::api_v2::TokenKind, + rpc::{block_hash, current_block_number, send_stuff, ChainWatcher, Invoice}, + signer::Signer, + state::State, +}; + +use frame_metadata::v15::RuntimeMetadataV15; +use jsonrpsee::ws_client::WsClientBuilder; +use substrate_constructor::fill_prepare::{SpecialTypeToFill, TypeContentToFill}; +use substrate_crypto_light::common::AccountId32; + +/// 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) { + // 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.unwrap(); // TODO should retry instead + let block_number = current_block_number(&client, &chain.metadata, &block).await; + let balance = order.balance(&client, &chain, &block).await.unwrap(); // TODO same + let loss_tolerance = 10000; // TODO: replace with multiple of existential + let manual_intervention_amount = 1000000000000; + let currency = chain.assets.get(&order.currency).unwrap(); + + // 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.unwrap(), + 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.unwrap(), + amount: order.amount.0, + to_account: &order.recipient.unwrap(), + }; + 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; + } + _ => { + tracing::error!("Balance is out of range: {balance:?}"); + return; + }, + }; + + 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().unwrap(); + + let signature = signer.sign(order.id, sign_this).await.unwrap(); + + batch_transaction.signature.content = TypeContentToFill::SpecialType(SpecialTypeToFill::SignatureSr25519(Some(signature))); + + let extrinsic = batch_transaction.send_this_signed::<(), RuntimeMetadataV15>(&chain.metadata).unwrap().unwrap(); + + send_stuff(&client, &format!("0x{}", hex::encode(extrinsic))).await.unwrap(); + + // TODO obvious + } +} diff --git a/src/rpc.rs b/src/rpc.rs index 854a19b..af6e2fd 100644 --- a/src/rpc.rs +++ b/src/rpc.rs @@ -1,7 +1,8 @@ use crate::{ chain::{ - asset_balance_query, base58prefix, events_entry_metadata, hashed_key_element, pallet_index, - storage_key, system_balance_query, system_properties_to_short_specs, unit, + asset_balance_query, base58prefix, block_number_query, events_entry_metadata, + hashed_key_element, pallet_index, storage_key, system_balance_query, + system_properties_to_short_specs, unit, was_balance_received_at_account, }, definitions::api_v2::{CurrencyProperties, OrderInfo}, definitions::{ @@ -9,6 +10,8 @@ use crate::{ AssetInfo, Balance, BlockHash, Chain, NativeToken, Nonce, PalletIndex, Timestamp, }, error::{Error, ErrorChain, NotHex}, + payout::payout, + signer::Signer, state::State, utils::unhex, TaskTracker, @@ -151,11 +154,19 @@ async fn genesis_hash(client: &WsClient) -> Result { } } -/// fetch current block hash, to request later the metadata and specs for +/// fetch block hash, to request later the metadata and specs for /// the same block -async fn block_hash(client: &WsClient, number: String) -> Result { +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![number]) + .request("chain_getBlockHash", rpc_params) .await .map_err(ErrorChain::Client)?; match block_hash_request { @@ -252,6 +263,7 @@ impl ChainManager { pub fn ignite( chain: Vec, state: State, + signer: Signer, task_tracker: TaskTracker, cancellation_token: CancellationToken, ) -> Result { @@ -288,6 +300,7 @@ impl ChainManager { chain_tx.clone(), chain_rx, state.interface(), + signer.interface(), task_tracker.clone(), cancellation_token.clone(), )?; @@ -317,7 +330,20 @@ impl ChainManager { } } ChainRequest::Reap(request) => { - todo!() + 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(ErrorChain::InvalidChain(chain.to_string()))); + } + } else { + let _unused = request + .res + .send(Err(ErrorChain::InvalidCurrency(request.currency))); + } } ChainRequest::Shutdown(res) => { for (name, chain) in watch_chain.drain() { @@ -341,17 +367,27 @@ impl ChainManager { let (res, rx) = oneshot::channel(); self.tx .send(ChainRequest::WatchAccount(WatchAccount::new( - id, order, res, + id, order, None, res, )?)) .await .map_err(|_| ErrorChain::MessageDropped)?; rx.await.map_err(|_| ErrorChain::MessageDropped)? } - pub async fn reap(&self, id: String, order: OrderInfo) -> Result<(), ErrorChain> { + pub async fn reap( + &self, + id: String, + order: OrderInfo, + recipient: AccountId32, + ) -> Result<(), ErrorChain> { let (res, rx) = oneshot::channel(); self.tx - .send(ChainRequest::Reap(WatchAccount::new(id, order, res)?)) + .send(ChainRequest::Reap(WatchAccount::new( + id, + order, + Some(recipient), + res, + )?)) .await .map_err(|_| ErrorChain::MessageDropped)?; rx.await.map_err(|_| ErrorChain::MessageDropped)? @@ -367,14 +403,15 @@ impl ChainManager { enum ChainRequest { WatchAccount(WatchAccount), - Shutdown(oneshot::Sender<()>), Reap(WatchAccount), + Shutdown(oneshot::Sender<()>), } enum ChainTrackerRequest { WatchAccount(WatchAccount), - Shutdown(oneshot::Sender<()>), NewBlock(String), + Reap(WatchAccount), + Shutdown(oneshot::Sender<()>), } #[derive(Debug)] @@ -383,6 +420,7 @@ struct WatchAccount { address: AccountId32, currency: String, amount: Balance, + recipient: Option, res: oneshot::Sender>, } @@ -390,6 +428,7 @@ impl WatchAccount { fn new( id: String, order: OrderInfo, + recipient: Option, res: oneshot::Sender>, ) -> Result { Ok(WatchAccount { @@ -399,17 +438,19 @@ impl WatchAccount { .0, currency: order.currency.currency, amount: Balance::parse(order.amount, order.currency.decimals), + recipient, res, }) } } #[derive(Clone, Debug)] -struct Invoice { - id: String, - address: AccountId32, - currency: String, - amount: Balance, +pub struct Invoice { + pub id: String, + pub address: AccountId32, + pub currency: String, + pub amount: Balance, + pub recipient: Option, } impl Invoice { @@ -420,30 +461,46 @@ impl Invoice { address: watch_account.address, currency: watch_account.currency, amount: watch_account.amount, + recipient: watch_account.recipient, } } - async fn check( + pub async fn balance( &self, client: &WsClient, - metadata: &RuntimeMetadataV15, + chain_watcher: &ChainWatcher, block: &H256, - currency: &HashMap, - ) -> Result { - let currency = currency + ) -> Result { + let currency = chain_watcher + .assets .get(&self.currency) .ok_or(ErrorChain::InvalidCurrency(self.currency.clone()))?; if let Some(asset_id) = currency.asset_id { - let balance = - asset_balance_at_account(client, &block, &metadata, &self.address, asset_id) - .await?; - Ok(balance >= self.amount) + 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, &metadata, &self.address).await?; - Ok(balance >= self.amount) + 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: &H256, + ) -> Result { + Ok(self.balance(client, chain_watcher, block).await? >= self.amount) + } } fn start_chain_watch( @@ -452,6 +509,7 @@ fn start_chain_watch( chain_tx: mpsc::Sender, mut chain_rx: mpsc::Receiver, state: State, + signer: Signer, task_tracker: TaskTracker, cancellation_token: CancellationToken, ) -> Result<(), ErrorChain> { @@ -460,7 +518,7 @@ fn start_chain_watch( .clone() .spawn(format!("Chain {} watcher", c.name.clone()), async move { let watchdog = 30000; - let mut watched_accounts = Vec::new(); + let mut watched_accounts = HashMap::new(); let mut shutdown = false; for endpoint in c.endpoints.iter().cycle() { // not restarting chain if shutdown is in progress @@ -469,10 +527,10 @@ fn start_chain_watch( } if let Ok(client) = WsClientBuilder::default().build(endpoint).await { // prepare chain - match prepare_chain(&client, &mut watched_accounts, endpoint, chain_tx.clone(), state.interface(), task_tracker.clone()) + let watcher = match ChainWatcher::prepare_chain(&client, &mut watched_accounts, endpoint, chain_tx.clone(), state.interface(), task_tracker.clone()) .await { - Ok(_) => (), + Ok(a) => a, Err(e) => { tracing::info!( "Failed to connect to chain {}, due to {:?} switching RPC server...", @@ -481,7 +539,7 @@ fn start_chain_watch( ); continue; } - } + }; // fulfill requests @@ -490,11 +548,64 @@ fn start_chain_watch( { match request { ChainTrackerRequest::NewBlock(block) => { - let block = block_hash(&client, block); - + 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...", + c.name, + e + ); + continue; + + }, + }; + let events = events_at_block( + &client, + &block, + Some(EventFilter { + pallet: "Balances", + optional_event_variant: Some("Transfer"), + }), + events_entry_metadata(&watcher.metadata)?, + &watcher.metadata.types, + ) + .await + .unwrap(); + + let mut id_remove_list = Vec::new(); + for (id, invoice) in watched_accounts.iter() { + 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); + } + } + } + } + + for id in id_remove_list { + watched_accounts.remove(&id); + } } ChainTrackerRequest::WatchAccount(request) => { - watched_accounts.push(Invoice::from_request(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 {} terminated", id).into()) + }); } ChainTrackerRequest::Shutdown(res) => { shutdown = true; @@ -510,59 +621,79 @@ fn start_chain_watch( Ok(()) } -async fn prepare_chain( - client: &WsClient, - watched_accounts: &mut Vec, - rpc_url: &str, - chain_tx: mpsc::Sender, - state: State, - task_tracker: TaskTracker, -) -> Result<(), ErrorChain> { - let genesis_hash = genesis_hash(&client).await?; - let mut blocks: Subscription = client - .subscribe( - "chain_subscribeFinalizedHeads", - rpc_params![], - "unsubscribe blocks", - ) - .await?; - let block = next_block(client, &mut blocks).await?; - let metadata = metadata(&client, &block).await?; - let specs = specs(&client, &metadata, &block).await?; - let assets = assets_set_at_block(&client, &block, &metadata, rpc_url, specs).await?; - - // check monitored accounts - let mut new_accounts = Vec::new(); - for invoice in watched_accounts.iter() { - match invoice.check(client, &metadata, &block, &assets).await { - Ok(true) => { - state.order_paid(invoice.id.clone()); - } - Ok(false) => new_accounts.push(invoice.to_owned()), - Err(e) => { - tracing::warn!("account fetch error: {0:?}", e); - new_accounts.push(invoice.to_owned()); +#[derive(Debug, Clone)] +pub struct ChainWatcher { + pub genesis_hash: H256, + pub metadata: RuntimeMetadataV15, + pub specs: ShortSpecs, + pub assets: HashMap, +} + +impl ChainWatcher { + pub async fn prepare_chain( + client: &WsClient, + 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: Subscription = client + .subscribe( + "chain_subscribeFinalizedHeads", + rpc_params![], + "unsubscribe blocks", + ) + .await?; + let block = next_block(client, &mut blocks).await?; + let metadata = metadata(&client, &block).await?; + let specs = specs(&client, &metadata, &block).await?; + let assets = + assets_set_at_block(&client, &block, &metadata, rpc_url, specs.clone()).await?; + + let chain = ChainWatcher { + genesis_hash, + metadata, + specs, + assets, + }; + + // 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); + } } } - } - *watched_accounts = new_accounts; - - task_tracker.spawn("watching blocks at {rpc_url}", async move { - while let Ok(block) = next_block_number(&mut blocks).await { - if chain_tx - .send(ChainTrackerRequest::NewBlock(block)) - .await - .is_err() - { - break; - } + for id in id_remove_list { + watched_accounts.remove(&id); } - // this should reset chain monitor on timeout; - // but if this breaks, it meand that the latter is already down either way - Ok("Block watch at {rpc_url} stopped".into()) - }); - Ok(()) + task_tracker.spawn("watching blocks at {rpc_url}", async move { + while let Ok(block) = next_block_number(&mut blocks).await { + if chain_tx + .send(ChainTrackerRequest::NewBlock(block)) + .await + .is_err() + { + break; + } + } + // this should reset chain monitor on timeout; + // but if this breaks, it meand that the latter is already down either way + Ok("Block watch at {rpc_url} stopped".into()) + }); + + Ok(chain) + } } async fn next_block_number(blocks: &mut Subscription) -> Result { @@ -577,7 +708,7 @@ async fn next_block( client: &WsClient, blocks: &mut Subscription, ) -> Result { - block_hash(&client, next_block_number(blocks).await?).await + block_hash(&client, Some(next_block_number(blocks).await?)).await } #[derive(Deserialize, Debug)] @@ -1050,3 +1181,58 @@ pub async fn events_at_block( } Err(ErrorChain::EventsMissing) } + +pub async fn current_block_number( + client: &WsClient, + metadata: &RuntimeMetadataV15, + block_hash: &H256, +) -> u32 { + let block_hash = &hex::encode(&block_hash.0); + let block_number_query = block_number_query(metadata); + if let Value::String(hex_data) = + get_value_from_storage(client, &block_number_query.key, block_hash) + .await + .unwrap() + { + let value_data = hex::decode(hex_data.trim_start_matches("0x")).unwrap(); + let value = decode_all_as_type::<&[u8], (), RuntimeMetadataV15>( + &block_number_query.value_ty, + &value_data.as_ref(), + &mut (), + &metadata.types, + ) + .unwrap(); + if let ParsedData::PrimitiveU32 { + value, + specialty: _, + } = value.data + { + value + } else { + panic!("unexpected block number format") + } + } else { + panic!("not a string data") + } +} + +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<(), ErrorChain> { + let rpc_params = rpc_params![data]; + let mut subscription: Subscription = client + .subscribe("author_submitAndWatchExtrinsic", rpc_params, "") + .await?; + let reply = subscription.next().await.unwrap(); + //println!("{reply:?}"); // TODO! + Ok(()) +} diff --git a/src/signer.rs b/src/signer.rs index fa39d79..673aa44 100644 --- a/src/signer.rs +++ b/src/signer.rs @@ -9,12 +9,19 @@ //! //! Also this abstraction could be used to implement off-system signer -use crate::{definitions::Entropy, error::{Error, ErrorSigner}, TaskTracker}; +use crate::{ + definitions::Entropy, + error::{Error, ErrorSigner}, + TaskTracker, +}; use std::env; use mnemonic_external::{regular::InternalWordList, WordSet}; -use substrate_crypto_light::{common::{AccountId32, AsBase58, DeriveJunction, FullDerivation}, sr25519::{Pair, Signature}}; +use substrate_crypto_light::{ + common::{AccountId32, AsBase58, DeriveJunction, FullDerivation}, + sr25519::{Pair, Signature}, +}; use tokio::sync::{mpsc, oneshot}; use zeroize::Zeroize; @@ -27,15 +34,12 @@ pub struct Signer { impl Signer { /// Run once to initialize; this should do **all** secret management - pub fn init( - recipient: AccountId32, - task_tracker: TaskTracker, - ) -> Result { - let (tx, mut rx) = mpsc::channel(16); - task_tracker.spawn("Signer", async move { - let mut seed_entropy = parse_seeds()?; // TODO: shutdown on failure - while let Some(request) = rx.recv().await { - match request { + pub fn init(recipient: AccountId32, task_tracker: TaskTracker) -> Result { + let (tx, mut rx) = mpsc::channel(16); + task_tracker.spawn("Signer", async move { + let mut seed_entropy = parse_seeds()?; // 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( @@ -43,13 +47,11 @@ impl Signer { // 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)), + 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( @@ -59,26 +61,26 @@ impl Signer { ) { 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}) + 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})) + .send(SignerRequest::PublicKey(PublicKeyRequest { id, ss58, res })) .await .map_err(|_| ErrorSigner::SignerDown)?; rx.await.map_err(|_| ErrorSigner::SignerDown)? @@ -87,7 +89,7 @@ impl Signer { pub async fn sign(&self, id: String, signable: Vec) -> Result { let (res, rx) = oneshot::channel(); self.tx - .send(SignerRequest::Sign(Sign{id, signable, res})) + .send(SignerRequest::Sign(Sign { id, signable, res })) .await .map_err(|_| ErrorSigner::SignerDown)?; rx.await.map_err(|_| ErrorSigner::SignerDown)? @@ -101,7 +103,9 @@ impl Signer { /// Clone wrapper in case we need to make it more complex later pub fn interface(&self) -> Self { - Signer { tx: self.tx.clone() } + Signer { + tx: self.tx.clone(), + } } } @@ -154,4 +158,3 @@ pub fn derivations<'a>(recipient: &'a str, order: &'a str) -> FullDerivation<'a> password: None, } } - diff --git a/src/state.rs b/src/state.rs index 3421461..0a7bc95 100644 --- a/src/state.rs +++ b/src/state.rs @@ -55,8 +55,6 @@ impl State { */ let (tx, mut rx) = tokio::sync::mpsc::channel(1024); - let recipient_ss58 = recipient.to_base58_string(2); // TODO maybe but spec says use "2" - let server_info = ServerInfo { // TODO version: env!("CARGO_PKG_VERSION"), @@ -70,7 +68,7 @@ impl State { let chain_manager = chain_manager.await.map_err(|_| Error::Fatal)?; let state = StateData { currencies, - recipient: recipient_ss58, + recipient, server_info, db, chain_manager, @@ -103,7 +101,7 @@ impl State { match state.db.mark_paid(id.clone()).await { Ok(order) => { // TODO: callback here - state.chain_manager.reap(id, order).await; + drop(state.chain_manager.reap(id, order, state.recipient).await); } Err(e) => { tracing::error!( @@ -221,7 +219,7 @@ struct CreateInvoice { struct StateData { currencies: HashMap, - recipient: String, + recipient: AccountId32, server_info: ServerInfo, db: Database, chain_manager: ChainManager, @@ -235,7 +233,7 @@ impl StateData { Ok(OrderResponse::FoundOrder(OrderStatus { order, message, - recipient: self.recipient.clone(), + recipient: self.recipient.clone().to_base58_string(2), // TODO maybe but spec says use "2" server_info: self.server_info.clone(), order_info, })) @@ -287,7 +285,7 @@ impl StateData { OrderStatus { order, message, - recipient: self.recipient.clone(), + recipient: self.recipient.clone().to_base58_string(2), // TODO maybe but spec says use "2" server_info: self.server_info.clone(), order_info, } From a077c3c9af1ce8d62e1cd53bafe8e77daff041da Mon Sep 17 00:00:00 2001 From: Slesarev Date: Tue, 14 May 2024 00:07:19 +0300 Subject: [PATCH 32/76] feat: stubs for all api_v2 requests --- src/error.rs | 13 +++++++ src/payout.rs | 32 +++++++++++++----- src/server.rs | 94 +++++++++++++++++++++++++++++++++++++++++++++++++-- src/state.rs | 4 +++ 4 files changed, 132 insertions(+), 11 deletions(-) diff --git a/src/error.rs b/src/error.rs index ac95d3c..70a022f 100644 --- a/src/error.rs +++ b/src/error.rs @@ -377,6 +377,19 @@ pub enum ErrorOrder { InternalError, } +#[derive(Debug, thiserror::Error)] +pub enum ErrorForceWithdrawal { + #[error("Order parameter missing: {0}")] + MissingParameter(String), + + #[error("Order parameter invalid: {0}")] + InvalidParameter(String), + + #[error("Withdrawal failed: {0:?}")] + WithdrawalError(OrderStatus), + +} + #[derive(Debug, thiserror::Error)] pub enum ErrorServer { #[error("failed to bind the TCP listener to {0:?}")] diff --git a/src/payout.rs b/src/payout.rs index e6f3b5d..93279b6 100644 --- a/src/payout.rs +++ b/src/payout.rs @@ -5,7 +5,11 @@ //! altogether (TODO) use crate::{ - chain::{AssetTransferConstructor, BalanceTransferConstructor, construct_batch_transaction, construct_single_asset_transfer_call, construct_single_balance_transfer_call}, + chain::{ + construct_batch_transaction, construct_single_asset_transfer_call, + construct_single_balance_transfer_call, AssetTransferConstructor, + BalanceTransferConstructor, + }, definitions::api_v2::TokenKind, rpc::{block_hash, current_block_number, send_stuff, ChainWatcher, Invoice}, signer::Signer, @@ -20,7 +24,13 @@ use substrate_crypto_light::common::AccountId32; /// 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) { +pub async fn payout( + rpc: String, + order: Invoice, + state: State, + chain: ChainWatcher, + signer: Signer, +) { // TODO: make this retry and rotate RPCs maybe // // after some retries record a failure @@ -44,7 +54,7 @@ pub async fn payout(rpc: String, order: Invoice, state: State, chain: ChainWatch vec![construct_single_balance_transfer_call( &chain.metadata, &balance_transfer_constructor, - )] + )] } TokenKind::Asset => { let asset_transfer_constructor = AssetTransferConstructor { @@ -55,7 +65,7 @@ pub async fn payout(rpc: String, order: Invoice, state: State, chain: ChainWatch vec![construct_single_asset_transfer_call( &chain.metadata, &asset_transfer_constructor, - )] + )] } }, a if (loss_tolerance..=manual_intervention_amount).contains(&a) => { @@ -65,7 +75,7 @@ pub async fn payout(rpc: String, order: Invoice, state: State, chain: ChainWatch _ => { tracing::error!("Balance is out of range: {balance:?}"); return; - }, + } }; let mut batch_transaction = construct_batch_transaction( @@ -82,11 +92,17 @@ pub async fn payout(rpc: String, order: Invoice, state: State, chain: ChainWatch let signature = signer.sign(order.id, sign_this).await.unwrap(); - batch_transaction.signature.content = TypeContentToFill::SpecialType(SpecialTypeToFill::SignatureSr25519(Some(signature))); + batch_transaction.signature.content = + TypeContentToFill::SpecialType(SpecialTypeToFill::SignatureSr25519(Some(signature))); - let extrinsic = batch_transaction.send_this_signed::<(), RuntimeMetadataV15>(&chain.metadata).unwrap().unwrap(); + let extrinsic = batch_transaction + .send_this_signed::<(), RuntimeMetadataV15>(&chain.metadata) + .unwrap() + .unwrap(); - send_stuff(&client, &format!("0x{}", hex::encode(extrinsic))).await.unwrap(); + send_stuff(&client, &format!("0x{}", hex::encode(extrinsic))) + .await + .unwrap(); // TODO obvious } diff --git a/src/server.rs b/src/server.rs index 88375a0..2dd0dc5 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,6 +1,6 @@ use crate::{ definitions::api_v2::*, - error::{Error, ErrorOrder, ErrorServer}, + error::{Error, ErrorForceWithdrawal, ErrorOrder, ErrorServer}, state::State, }; use axum::{ @@ -25,8 +25,21 @@ pub async fn new( ) -> Result, Error>>, ErrorServer> { let v2: Router = Router::new() .route("/order/:order_id", routing::post(order)) - .route("/status", routing::get(status)); - let app = Router::new().nest("/v2", v2).with_state(state); + .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( + "/public/v2/payment/:paymentAccount", + routing::post(public_payment_account), + ) + .nest("/v2", v2) + .with_state(state); let listener = TcpListener::bind(host) .await @@ -158,6 +171,51 @@ async fn order( } } +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(|_| ErrorForceWithdrawal::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(|| ErrorForceWithdrawal::MissingParameter(ORDER_ID.into()))? + .to_owned(); + state.force_withdrawal(order).await.map_err(ErrorForceWithdrawal::WithdrawalError) +} + +#[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(ErrorForceWithdrawal::WithdrawalError(a)) => (StatusCode::BAD_REQUEST, Json(a)).into_response(), + Err(ErrorForceWithdrawal::MissingParameter(parameter)) => ( + StatusCode::BAD_REQUEST, + Json([InvalidParameter { + parameter, + message: "parameter wasn't found".into(), + }]), + ) + .into_response(), + Err(ErrorForceWithdrawal::InvalidParameter(parameter)) => ( + StatusCode::BAD_REQUEST, + Json([InvalidParameter { + parameter, + message: "parameter's format is invalid".into(), + }]), + ) + .into_response(), + } +} + async fn status( extract::State(state): extract::State, ) -> ([(HeaderName, &'static str); 1], Json) { @@ -166,3 +224,33 @@ async fn status( Err(_e) => panic!("db connection is down, state is lost"), //TODO tell this to client } } + +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!() +} + +#[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/state.rs b/src/state.rs index 0a7bc95..f464b15 100644 --- a/src/state.rs +++ b/src/state.rs @@ -188,6 +188,10 @@ impl State { }; } + pub async fn force_withdrawal(&self, order: String) -> Result { + todo!() + } + pub fn interface(&self) -> Self { State { tx: self.tx.clone(), From 9663d7875e29e48b3b8385b9be5dcd32f94485e8 Mon Sep 17 00:00:00 2001 From: Slesarev Date: Tue, 14 May 2024 00:44:32 +0300 Subject: [PATCH 33/76] feat: conform schemas to api_v2.1 --- src/definitions.rs | 8 +++++--- src/state.rs | 6 +++++- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/definitions.rs b/src/definitions.rs index 2ee0108..85ed263 100644 --- a/src/definitions.rs +++ b/src/definitions.rs @@ -118,6 +118,8 @@ pub mod api_v2 { pub server_info: ServerInfo, #[serde(flatten)] pub order_info: OrderInfo, + pub payment_page: String, + pub redirect_url: String, } #[derive(Clone, Debug, Serialize, Encode, Decode)] @@ -168,13 +170,13 @@ pub mod api_v2 { #[derive(Clone, Debug, Serialize)] pub struct ServerStatus { - pub description: ServerInfo, + pub server_info: ServerInfo, pub supported_currencies: HashMap, } #[derive(Debug, Serialize)] struct ServerHealth { - description: ServerInfo, + server_info: ServerInfo, connected_rpcs: Vec, status: Health, } @@ -264,7 +266,7 @@ pub mod api_v2 { #[derive(Clone, Debug, Serialize, Decode, Encode)] pub struct TransactionInfo { #[serde(skip_serializing_if = "Option::is_none", flatten)] - finalized_tx: Option, + finalized_tx: Option, // Clearly undefined in v2.1 - TODO transaction_bytes: String, sender: String, recipient: String, diff --git a/src/state.rs b/src/state.rs index f464b15..d771c12 100644 --- a/src/state.rs +++ b/src/state.rs @@ -91,7 +91,7 @@ impl State { } StateAccessRequest::ServerStatus(res) => { let server_status = ServerStatus { - description: state.server_info.clone(), + server_info: state.server_info.clone(), supported_currencies: state.currencies.clone(), }; res.send(server_status).map_err(|_| Error::Fatal)?; @@ -240,6 +240,8 @@ impl StateData { 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) @@ -292,6 +294,8 @@ impl StateData { 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(), } } } From a8605042239d8b7e14ab39de58695fdf65231ee3 Mon Sep 17 00:00:00 2001 From: Slesarev Date: Tue, 14 May 2024 10:17:26 +0300 Subject: [PATCH 34/76] feat: recover pending accounts from shutdown --- src/database.rs | 24 ++++++++++++++++++++++++ src/state.rs | 16 ++++++++++++++-- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/src/database.rs b/src/database.rs index f2f8047..90ae055 100644 --- a/src/database.rs +++ b/src/database.rs @@ -191,6 +191,20 @@ impl Database { // 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, @@ -225,6 +239,15 @@ impl Database { Ok(Self { tx }) } + pub async fn order_list(&self) -> Result, ErrorDb> { + let (res, rx) = oneshot::channel(); + let _unused = self + .tx + .send(DbRequest::ActiveOrderList(res)) + .await; + rx.await.map_err(|_| ErrorDb::DbEngineDown)? + } + pub async fn create_order( &self, order: String, @@ -287,6 +310,7 @@ impl Database { enum DbRequest { CreateOrder(CreateOrder), + ActiveOrderList(oneshot::Sender, ErrorDb>>), ReadOrder(ReadOrder), MarkPaid(MarkPaid), MarkWithdrawn(ModifyOrder), diff --git a/src/state.rs b/src/state.rs index d771c12..d2a4a21 100644 --- a/src/state.rs +++ b/src/state.rs @@ -63,9 +63,12 @@ impl State { kalatori_remark: remark.clone(), }; + // Remember to always spawn async here or things might deadlock - task_tracker.spawn("State Handler", async move { + 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 state = StateData { currencies, recipient, @@ -75,6 +78,15 @@ impl State { 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).await; + } + Ok("All saved orders restored".into()) + }); + while let Some(request) = rx.recv().await { match request { StateAccessRequest::GetInvoiceStatus(request) => { @@ -283,7 +295,7 @@ impl StateData { order_info, String::from("Order with this ID was already processed"), ))) - } + } } } From f212f62de9762dac2fc38596db0864d01d68bc44 Mon Sep 17 00:00:00 2001 From: Slesarew <33295157+Slesarew@users.noreply.github.com> Date: Tue, 14 May 2024 23:05:42 +0300 Subject: [PATCH 35/76] Update src/definitions.rs Co-authored-by: Vova Lando --- src/definitions.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/definitions.rs b/src/definitions.rs index 85ed263..84d66cd 100644 --- a/src/definitions.rs +++ b/src/definitions.rs @@ -314,7 +314,7 @@ pub mod api_v2 { )] fn balance_insufficient_precision() { - const DECIMALS: Decimals = 10; + const DECIMALS: api_v2::Decimals = 10; let float = 931395.862219815_3; let parsed = Balance::parse(float, DECIMALS); From b83a26dfb32a7ff4490d588ee5253c19c01a6f85 Mon Sep 17 00:00:00 2001 From: Slesarev Date: Wed, 15 May 2024 00:16:21 +0300 Subject: [PATCH 36/76] refactor: split rpc module --- src/chain/definitions.rs | 168 ++++++++++ src/chain/mod.rs | 176 +++++++++++ src/{ => chain}/payout.rs | 6 +- src/{ => chain}/rpc.rs | 522 ++----------------------------- src/chain/tracker.rs | 218 +++++++++++++ src/{chain.rs => chain/utils.rs} | 0 src/main.rs | 6 +- src/state.rs | 2 +- 8 files changed, 590 insertions(+), 508 deletions(-) create mode 100644 src/chain/definitions.rs create mode 100644 src/chain/mod.rs rename src/{ => chain}/payout.rs (96%) rename src/{ => chain}/rpc.rs (64%) create mode 100644 src/chain/tracker.rs rename src/{chain.rs => chain/utils.rs} (100%) diff --git a/src/chain/definitions.rs b/src/chain/definitions.rs new file mode 100644 index 0000000..c1a3b17 --- /dev/null +++ b/src/chain/definitions.rs @@ -0,0 +1,168 @@ +//! Common objects for chain interaction system + +use frame_metadata::v15::RuntimeMetadataV15; +use jsonrpsee::ws_client::{WsClient, WsClientBuilder}; +use primitive_types::H256; +use substrate_crypto_light::common::{AccountId32, AsBase58}; +use substrate_parser::ShortSpecs; +use tokio::sync::oneshot; + +use crate::{ + chain::{ + rpc::{asset_balance_at_account, system_balance_at_account}, + tracker::ChainWatcher, + }, + definitions::{ + Balance, + Chain, + api_v2::{OrderInfo}, + }, + error::ErrorChain, +}; + +#[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: Option, + pub res: oneshot::Sender>, +} + +impl WatchAccount { + pub fn new( + id: String, + order: OrderInfo, + recipient: Option, + res: oneshot::Sender>, + ) -> Result { + Ok(WatchAccount { + id, + address: AccountId32::from_base58_string(&order.payment_account) + .map_err(ErrorChain::InvoiceAccount)? + .0, + currency: order.currency.currency, + amount: Balance::parse(order.amount, order.currency.decimals), + recipient, + res, + }) + } +} + +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: Option, +} + +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, + } + } + + pub async fn balance( + &self, + client: &WsClient, + chain_watcher: &ChainWatcher, + block: &H256, + ) -> Result { + let currency = chain_watcher + .assets + .get(&self.currency) + .ok_or(ErrorChain::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: &H256, + ) -> 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..7be8ab5 --- /dev/null +++ b/src/chain/mod.rs @@ -0,0 +1,176 @@ +//! 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::{Chain, api_v2::OrderInfo}, + error::{Error, ErrorChain}, + Signer, State, + TaskTracker, +}; + +pub mod definitions; +pub mod utils; +pub mod rpc; +pub mod payout; +pub mod tracker; + +use definitions::{ChainRequest, ChainTrackerRequest, WatchAccount}; +use tracker::start_chain_watch; + +/// Logging filter +pub const MODULE: &str = module_path!(); + +/// 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 { + let (chain_tx, mut chain_rx) = mpsc::channel(1024); + watch_chain.insert(c.name.clone(), chain_tx.clone()); + 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, + ¤cy_map, + 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(ErrorChain::InvalidChain(chain.to_string()))); + } + } else { + let _unused = request + .res + .send(Err(ErrorChain::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(ErrorChain::InvalidChain(chain.to_string()))); + } + } else { + let _unused = request + .res + .send(Err(ErrorChain::InvalidCurrency(request.currency))); + } + } + ChainRequest::Shutdown(res) => { + for (name, chain) in watch_chain.drain() { + let (tx, rx) = oneshot::channel(); + let _unused = chain.send(ChainTrackerRequest::Shutdown(tx)).await; + let _ = rx.await; + } + let _ = res.send(()); + break; + } + } + } + + Ok("Chain manager is shutting down".into()) + }); + + Ok(Self { tx }) + } + + pub async fn add_invoice(&self, id: String, order: OrderInfo) -> Result<(), ErrorChain> { + let (res, rx) = oneshot::channel(); + self.tx + .send(ChainRequest::WatchAccount(WatchAccount::new( + id, order, None, res, + )?)) + .await + .map_err(|_| ErrorChain::MessageDropped)?; + rx.await.map_err(|_| ErrorChain::MessageDropped)? + } + + pub async fn reap( + &self, + id: String, + order: OrderInfo, + recipient: AccountId32, + ) -> Result<(), ErrorChain> { + let (res, rx) = oneshot::channel(); + self.tx + .send(ChainRequest::Reap(WatchAccount::new( + id, + order, + Some(recipient), + res, + )?)) + .await + .map_err(|_| ErrorChain::MessageDropped)?; + rx.await.map_err(|_| ErrorChain::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/payout.rs b/src/chain/payout.rs similarity index 96% rename from src/payout.rs rename to src/chain/payout.rs index 93279b6..aab2530 100644 --- a/src/payout.rs +++ b/src/chain/payout.rs @@ -6,12 +6,16 @@ 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, - rpc::{block_hash, current_block_number, send_stuff, ChainWatcher, Invoice}, signer::Signer, state::State, }; diff --git a/src/rpc.rs b/src/chain/rpc.rs similarity index 64% rename from src/rpc.rs rename to src/chain/rpc.rs index af6e2fd..280098a 100644 --- a/src/rpc.rs +++ b/src/chain/rpc.rs @@ -1,8 +1,11 @@ +//! Blockchain operations that actually require calling the chain + use crate::{ chain::{ - asset_balance_query, base58prefix, block_number_query, events_entry_metadata, + definitions::{EventFilter, WatchAccount,}, + utils::{asset_balance_query, base58prefix, block_number_query, events_entry_metadata, hashed_key_element, pallet_index, storage_key, system_balance_query, - system_properties_to_short_specs, unit, was_balance_received_at_account, + system_properties_to_short_specs, unit, was_balance_received_at_account}, }, definitions::api_v2::{CurrencyProperties, OrderInfo}, definitions::{ @@ -10,7 +13,6 @@ use crate::{ AssetInfo, Balance, BlockHash, Chain, NativeToken, Nonce, PalletIndex, Timestamp, }, error::{Error, ErrorChain, NotHex}, - payout::payout, signer::Signer, state::State, utils::unhex, @@ -43,13 +45,7 @@ use substrate_parser::{ storage_data::{KeyData, KeyPart}, AsMetadata, ShortSpecs, }; -use tokio::{ - sync::{mpsc, oneshot}, - time::{timeout, Duration}, -}; -use tokio_util::sync::CancellationToken; -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"; @@ -69,33 +65,14 @@ const TRANSFER: &str = "Transfer"; const AURA: &str = "AuraApi"; -#[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, +pub async fn subscribe_blocks(client: &WsClient) -> Result, ErrorChain> { + Ok(client + .subscribe( + "chain_subscribeFinalizedHeads", + rpc_params![], + "unsubscribe blocks", + ) + .await?) } pub async fn get_value_from_storage( @@ -133,7 +110,7 @@ pub async fn get_keys_from_storage( /// fetch genesis hash, must be a hexadecimal string transformable into /// H256 format -async fn genesis_hash(client: &WsClient) -> Result { +pub async fn genesis_hash(client: &WsClient) -> Result { let genesis_hash_request: Value = client .request( "chain_getBlockHash", @@ -183,7 +160,7 @@ pub async fn block_hash( } /// fetch metadata at known block -async fn metadata(client: &WsClient, block: &BlockHash) -> Result { +pub async fn metadata(client: &WsClient, block: &BlockHash) -> Result { let block_hash_string = block.to_string(); let metadata_request: Value = client .request( @@ -222,7 +199,7 @@ async fn metadata(client: &WsClient, block: &BlockHash) -> Result, -} - -#[derive(Debug)] -pub struct ConnectedChain { - rpc: String, - client: WsClient, - genesis: BlockHash, - properties: ChainProperties, -} - -/// 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 client = WsClientBuilder::default() - .build(rpc.clone()) - .await - .map_err(ErrorChain::Client)?; - */ - 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 { - let (chain_tx, mut chain_rx) = mpsc::channel(1024); - watch_chain.insert(c.name.clone(), chain_tx.clone()); - 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, - ¤cy_map, - 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(ErrorChain::InvalidChain(chain.to_string()))); - } - } else { - let _unused = request - .res - .send(Err(ErrorChain::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(ErrorChain::InvalidChain(chain.to_string()))); - } - } else { - let _unused = request - .res - .send(Err(ErrorChain::InvalidCurrency(request.currency))); - } - } - ChainRequest::Shutdown(res) => { - for (name, chain) in watch_chain.drain() { - let (tx, rx) = oneshot::channel(); - let _unused = chain.send(ChainTrackerRequest::Shutdown(tx)).await; - let _ = rx.await; - } - let _ = res.send(()); - break; - } - } - } - - Ok("Chain manager is shutting down".into()) - }); - - Ok(Self { tx }) - } - - pub async fn add_invoice(&self, id: String, order: OrderInfo) -> Result<(), ErrorChain> { - let (res, rx) = oneshot::channel(); - self.tx - .send(ChainRequest::WatchAccount(WatchAccount::new( - id, order, None, res, - )?)) - .await - .map_err(|_| ErrorChain::MessageDropped)?; - rx.await.map_err(|_| ErrorChain::MessageDropped)? - } - - pub async fn reap( - &self, - id: String, - order: OrderInfo, - recipient: AccountId32, - ) -> Result<(), ErrorChain> { - let (res, rx) = oneshot::channel(); - self.tx - .send(ChainRequest::Reap(WatchAccount::new( - id, - order, - Some(recipient), - res, - )?)) - .await - .map_err(|_| ErrorChain::MessageDropped)?; - rx.await.map_err(|_| ErrorChain::MessageDropped)? - } - - pub async fn shutdown(&self) -> () { - let (tx, rx) = oneshot::channel(); - let _unused = self.tx.send(ChainRequest::Shutdown(tx)).await; - let _ = rx.await; - () - } -} - -enum ChainRequest { - WatchAccount(WatchAccount), - Reap(WatchAccount), - Shutdown(oneshot::Sender<()>), -} - -enum ChainTrackerRequest { - WatchAccount(WatchAccount), - NewBlock(String), - Reap(WatchAccount), - Shutdown(oneshot::Sender<()>), -} - -#[derive(Debug)] -struct WatchAccount { - id: String, - address: AccountId32, - currency: String, - amount: Balance, - recipient: Option, - res: oneshot::Sender>, -} - -impl WatchAccount { - fn new( - id: String, - order: OrderInfo, - recipient: Option, - res: oneshot::Sender>, - ) -> Result { - Ok(WatchAccount { - id, - address: AccountId32::from_base58_string(&order.payment_account) - .map_err(ErrorChain::InvoiceAccount)? - .0, - currency: order.currency.currency, - amount: Balance::parse(order.amount, order.currency.decimals), - recipient, - res, - }) - } -} - -#[derive(Clone, Debug)] -pub struct Invoice { - pub id: String, - pub address: AccountId32, - pub currency: String, - pub amount: Balance, - pub recipient: Option, -} - -impl Invoice { - 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, - } - } - - pub async fn balance( - &self, - client: &WsClient, - chain_watcher: &ChainWatcher, - block: &H256, - ) -> Result { - let currency = chain_watcher - .assets - .get(&self.currency) - .ok_or(ErrorChain::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: &H256, - ) -> Result { - Ok(self.balance(client, chain_watcher, block).await? >= self.amount) - } -} - -fn start_chain_watch( - c: Chain, - currency_map: &HashMap, - chain_tx: mpsc::Sender, - mut chain_rx: mpsc::Receiver, - state: State, - signer: Signer, - task_tracker: TaskTracker, - cancellation_token: CancellationToken, -) -> Result<(), ErrorChain> { - //let (block_source_tx, mut block_source_rx) = mpsc::channel(16); - task_tracker - .clone() - .spawn(format!("Chain {} watcher", c.name.clone()), async move { - let watchdog = 30000; - let mut watched_accounts = HashMap::new(); - let mut shutdown = false; - for endpoint in c.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, &mut watched_accounts, endpoint, chain_tx.clone(), state.interface(), task_tracker.clone()) - .await - { - Ok(a) => a, - Err(e) => { - tracing::info!( - "Failed to connect to chain {}, due to {:?} switching RPC server...", - c.name, - e - ); - continue; - } - }; - - - // fulfill requests - while let Ok(Some(request)) = - timeout(Duration::from_millis(watchdog), chain_rx.recv()).await - { - match request { - ChainTrackerRequest::NewBlock(block) => { - 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...", - c.name, - e - ); - continue; - - }, - }; - let events = events_at_block( - &client, - &block, - Some(EventFilter { - pallet: "Balances", - optional_event_variant: Some("Transfer"), - }), - events_entry_metadata(&watcher.metadata)?, - &watcher.metadata.types, - ) - .await - .unwrap(); - - let mut id_remove_list = Vec::new(); - for (id, invoice) in watched_accounts.iter() { - 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); - } - } - } - } - - for id in id_remove_list { - watched_accounts.remove(&id); - } - } - 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 {} terminated", id).into()) - }); - } - ChainTrackerRequest::Shutdown(res) => { - shutdown = true; - let _ = res.send(()); - break; - } - } - } - } - } - Ok(format!("Chain {} monitor shut down", c.name).into()) - }); - Ok(()) -} - -#[derive(Debug, Clone)] -pub struct ChainWatcher { - pub genesis_hash: H256, - pub metadata: RuntimeMetadataV15, - pub specs: ShortSpecs, - pub assets: HashMap, -} - -impl ChainWatcher { - pub async fn prepare_chain( - client: &WsClient, - 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: Subscription = client - .subscribe( - "chain_subscribeFinalizedHeads", - rpc_params![], - "unsubscribe blocks", - ) - .await?; - let block = next_block(client, &mut blocks).await?; - let metadata = metadata(&client, &block).await?; - let specs = specs(&client, &metadata, &block).await?; - let assets = - assets_set_at_block(&client, &block, &metadata, rpc_url, specs.clone()).await?; - - let chain = ChainWatcher { - genesis_hash, - metadata, - specs, - assets, - }; - - // 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); - } - - task_tracker.spawn("watching blocks at {rpc_url}", async move { - while let Ok(block) = next_block_number(&mut blocks).await { - if chain_tx - .send(ChainTrackerRequest::NewBlock(block)) - .await - .is_err() - { - break; - } - } - // this should reset chain monitor on timeout; - // but if this breaks, it meand that the latter is already down either way - Ok("Block watch at {rpc_url} stopped".into()) - }); - - Ok(chain) - } -} - -async fn next_block_number(blocks: &mut Subscription) -> Result { +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()), @@ -704,7 +222,7 @@ async fn next_block_number(blocks: &mut Subscription) -> Result, ) -> Result { @@ -713,7 +231,7 @@ async fn next_block( #[derive(Deserialize, Debug)] #[serde(rename_all = "kebab-case")] -struct BlockHead { +pub struct BlockHead { //digest: Value, //extrinsics_root: String, pub number: String, @@ -1053,7 +571,7 @@ pub async fn asset_balance_at_account( } } -async fn system_balance_at_account( +pub async fn system_balance_at_account( client: &WsClient, block_hash: &H256, metadata_v15: &RuntimeMetadataV15, diff --git a/src/chain/tracker.rs b/src/chain/tracker.rs new file mode 100644 index 0000000..1c48254 --- /dev/null +++ b/src/chain/tracker.rs @@ -0,0 +1,218 @@ +//! A tracker that follows individual chain + +use std::collections::HashMap; + +use frame_metadata::v15::RuntimeMetadataV15; +use jsonrpsee::ws_client::{WsClient, WsClientBuilder}; +use jsonrpsee::core::client::{ClientT, Subscription, SubscriptionClientT}; +use primitive_types::H256; +use substrate_parser::ShortSpecs; +use tokio::{ + sync::{mpsc, oneshot}, + time::{timeout, Duration}, +}; +use tokio_util::sync::CancellationToken; + +use crate::{ + TaskTracker, + chain::{ + definitions::{ChainRequest, ChainTrackerRequest, EventFilter, Invoice}, + payout::payout, + rpc::{BlockHead, assets_set_at_block, block_hash, events_at_block, genesis_hash, metadata, next_block, next_block_number, specs, subscribe_blocks}, + utils::{events_entry_metadata, was_balance_received_at_account}, + }, + definitions::{Chain, api_v2::CurrencyProperties,}, + error::ErrorChain, + signer::Signer, + state::State, +}; + +pub fn start_chain_watch( + c: Chain, + currency_map: &HashMap, + chain_tx: mpsc::Sender, + mut chain_rx: mpsc::Receiver, + state: State, + signer: Signer, + task_tracker: TaskTracker, + cancellation_token: CancellationToken, +) -> Result<(), ErrorChain> { + //let (block_source_tx, mut block_source_rx) = mpsc::channel(16); + task_tracker + .clone() + .spawn(format!("Chain {} watcher", c.name.clone()), async move { + let watchdog = 30000; + let mut watched_accounts = HashMap::new(); + let mut shutdown = false; + for endpoint in c.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, &mut watched_accounts, endpoint, chain_tx.clone(), state.interface(), task_tracker.clone()) + .await + { + Ok(a) => a, + Err(e) => { + tracing::info!( + "Failed to connect to chain {}, due to {:?} switching RPC server...", + c.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...", + c.name, + e + ); + continue; + + }, + }; + let events = events_at_block( + &client, + &block, + Some(EventFilter { + pallet: "Balances", + optional_event_variant: Some("Transfer"), + }), + events_entry_metadata(&watcher.metadata)?, + &watcher.metadata.types, + ) + .await + .unwrap(); + + let mut id_remove_list = Vec::new(); + for (id, invoice) in watched_accounts.iter() { + 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); + } + } + } + } + + for id in id_remove_list { + watched_accounts.remove(&id); + } + } + 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 {} terminated", id).into()) + }); + } + ChainTrackerRequest::Shutdown(res) => { + shutdown = true; + let _ = res.send(()); + break; + } + } + } + } + } + Ok(format!("Chain {} monitor shut down", c.name).into()) + }); + Ok(()) +} + +#[derive(Debug, Clone)] +pub struct ChainWatcher { + pub genesis_hash: H256, + pub metadata: RuntimeMetadataV15, + pub specs: ShortSpecs, + pub assets: HashMap, +} + +impl ChainWatcher { + pub async fn prepare_chain( + client: &WsClient, + 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 metadata = metadata(&client, &block).await?; + let specs = specs(&client, &metadata, &block).await?; + let assets = + assets_set_at_block(&client, &block, &metadata, rpc_url, specs.clone()).await?; + + let chain = ChainWatcher { + genesis_hash, + metadata, + specs, + assets, + }; + + // 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); + } + + task_tracker.spawn("watching blocks at {rpc_url}", async move { + while let Ok(block) = next_block_number(&mut blocks).await { + if chain_tx + .send(ChainTrackerRequest::NewBlock(block)) + .await + .is_err() + { + break; + } + } + // this should reset chain monitor on timeout; + // but if this breaks, it meand that the latter is already down either way + Ok("Block watch at {rpc_url} stopped".into()) + }); + + Ok(chain) + } +} + + diff --git a/src/chain.rs b/src/chain/utils.rs similarity index 100% rename from src/chain.rs rename to src/chain/utils.rs diff --git a/src/main.rs b/src/main.rs index 60ec605..24e015d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -26,8 +26,6 @@ mod chain; mod database; mod definitions; mod error; -mod payout; -mod rpc; mod server; mod signer; mod state; @@ -36,7 +34,7 @@ mod utils; use crate::definitions::{Chain, Entropy, Timestamp, Version}; use database::ConfigWoChains; use error::Error; -use rpc::ChainManager; +use chain::ChainManager; use signer::Signer; use state::State; @@ -254,7 +252,7 @@ fn default_filter() -> String { const TARGETS: &[&str] = &[ callback::MODULE, database::MODULE, - rpc::MODULE, + chain::MODULE, server::MODULE, env!("CARGO_PKG_NAME"), ]; diff --git a/src/state.rs b/src/state.rs index d2a4a21..d5d3efb 100644 --- a/src/state.rs +++ b/src/state.rs @@ -8,7 +8,7 @@ use crate::{ Entropy, }, error::{Error, ErrorOrder}, - rpc::ChainManager, + chain::ChainManager, signer::Signer, ConfigWoChains, TaskTracker, }; From cbfb927133297c4ab383e4c2c778dbfe867e018b Mon Sep 17 00:00:00 2001 From: Slesarev Date: Thu, 16 May 2024 00:48:00 +0300 Subject: [PATCH 37/76] fix: properly typesafe BlockHash fixes a lot! --- src/chain/definitions.rs | 31 +++++++++- src/chain/mod.rs | 5 +- src/chain/payout.rs | 2 +- src/chain/rpc.rs | 120 +++++++++++++++------------------------ src/chain/tracker.rs | 11 ++-- src/chain/utils.rs | 10 ++-- src/definitions.rs | 2 - src/error.rs | 49 +++++++--------- 8 files changed, 109 insertions(+), 121 deletions(-) diff --git a/src/chain/definitions.rs b/src/chain/definitions.rs index c1a3b17..515e942 100644 --- a/src/chain/definitions.rs +++ b/src/chain/definitions.rs @@ -17,9 +17,34 @@ use crate::{ Chain, api_v2::{OrderInfo}, }, - error::ErrorChain, + error::{ErrorChain, 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 { + 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(|_| ErrorChain::BlockHashLength)?, + ))) + } +} + + #[derive(Debug)] pub struct EventFilter<'a> { pub pallet: &'a str, @@ -132,7 +157,7 @@ impl Invoice { &self, client: &WsClient, chain_watcher: &ChainWatcher, - block: &H256, + block: &BlockHash, ) -> Result { let currency = chain_watcher .assets @@ -160,7 +185,7 @@ impl Invoice { &self, client: &WsClient, chain_watcher: &ChainWatcher, - block: &H256, + 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 index 7be8ab5..2201b46 100644 --- a/src/chain/mod.rs +++ b/src/chain/mod.rs @@ -119,8 +119,9 @@ impl ChainManager { ChainRequest::Shutdown(res) => { for (name, chain) in watch_chain.drain() { let (tx, rx) = oneshot::channel(); - let _unused = chain.send(ChainTrackerRequest::Shutdown(tx)).await; - let _ = rx.await; + if chain.send(ChainTrackerRequest::Shutdown(tx)).await.is_ok() { + let _ = rx.await; + } } let _ = res.send(()); break; diff --git a/src/chain/payout.rs b/src/chain/payout.rs index aab2530..a23632f 100644 --- a/src/chain/payout.rs +++ b/src/chain/payout.rs @@ -40,7 +40,7 @@ pub async fn payout( // after some retries record a failure if let Ok(client) = WsClientBuilder::default().build(rpc).await { let block = block_hash(&client, None).await.unwrap(); // TODO should retry instead - let block_number = current_block_number(&client, &chain.metadata, &block).await; + let block_number = current_block_number(&client, &chain.metadata, &block).await.unwrap(); let balance = order.balance(&client, &chain, &block).await.unwrap(); // TODO same let loss_tolerance = 10000; // TODO: replace with multiple of existential let manual_intervention_amount = 1000000000000; diff --git a/src/chain/rpc.rs b/src/chain/rpc.rs index 280098a..8a8639f 100644 --- a/src/chain/rpc.rs +++ b/src/chain/rpc.rs @@ -2,7 +2,7 @@ use crate::{ chain::{ - definitions::{EventFilter, WatchAccount,}, + definitions::{BlockHash, EventFilter, WatchAccount,}, utils::{asset_balance_query, base58prefix, block_number_query, events_entry_metadata, hashed_key_element, pallet_index, storage_key, system_balance_query, system_properties_to_short_specs, unit, was_balance_received_at_account}, @@ -10,7 +10,7 @@ use crate::{ definitions::api_v2::{CurrencyProperties, OrderInfo}, definitions::{ api_v2::{AssetId, BlockNumber, CurrencyInfo, Decimals, TokenKind}, - AssetInfo, Balance, BlockHash, Chain, NativeToken, Nonce, PalletIndex, Timestamp, + AssetInfo, Balance, Chain, NativeToken, Nonce, PalletIndex, Timestamp, }, error::{Error, ErrorChain, NotHex}, signer::Signer, @@ -26,7 +26,6 @@ use jsonrpsee::core::client::{ClientT, Subscription, SubscriptionClientT}; use jsonrpsee::rpc_params; use jsonrpsee::ws_client::{WsClient, WsClientBuilder}; use parity_scale_codec::{Decode, DecodeAll, Encode}; -use primitive_types::H256; use scale_info::{form::PortableForm, PortableRegistry, TypeDef, TypeDefPrimitive}; use serde::{Deserialize, Deserializer}; use serde_json::{Map, Number, Value}; @@ -78,10 +77,10 @@ pub async fn subscribe_blocks(client: &WsClient) -> Result Result { let value: Value = client - .request("state_getStorage", rpc_params![whole_key, block_hash]) + .request("state_getStorage", rpc_params![whole_key, block.to_string()]) .await?; Ok(value) } @@ -90,7 +89,7 @@ pub async fn get_keys_from_storage( client: &WsClient, prefix: &str, storage_name: &str, - block_hash: &str, + block: &BlockHash, ) -> Result { let keys: Value = client .request( @@ -101,7 +100,7 @@ pub async fn get_keys_from_storage( hex::encode(twox_128(prefix.as_bytes())), hex::encode(twox_128(storage_name.as_bytes())) ), - block_hash + block.to_string() ], ) .await?; @@ -120,12 +119,7 @@ pub async fn genesis_hash(client: &WsClient) -> Result { .map_err(ErrorChain::Client)?; match genesis_hash_request { Value::String(x) => { - let genesis_hash_raw = unhex(&x, NotHex::GenesisHash)?; - Ok(H256( - genesis_hash_raw - .try_into() - .map_err(|_| ErrorChain::GenesisHashLength)?, - )) + BlockHash::from_str(&x) } _ => return Err(ErrorChain::GenesisHashFormat), } @@ -148,12 +142,7 @@ pub async fn block_hash( .map_err(ErrorChain::Client)?; match block_hash_request { Value::String(x) => { - let block_hash_raw = unhex(&x, NotHex::BlockHash)?; - Ok(H256( - block_hash_raw - .try_into() - .map_err(|_| ErrorChain::BlockHashLength)?, - )) + BlockHash::from_str(&x) } _ => return Err(ErrorChain::BlockHashFormat), } @@ -161,14 +150,13 @@ pub async fn block_hash( /// fetch metadata at known block pub async fn metadata(client: &WsClient, block: &BlockHash) -> Result { - let block_hash_string = block.to_string(); let metadata_request: Value = client .request( "state_call", rpc_params![ "Metadata_metadata_at_version", "0f000000", - &block_hash_string + &block.to_string() ], ) .await @@ -202,10 +190,10 @@ pub async fn metadata(client: &WsClient, block: &BlockHash) -> Result Result { let specs_request: Value = client - .request("system_properties", rpc_params![hex::encode(&block_hash.0)]) + .request("system_properties", rpc_params![block.to_string()]) .await?; //.map_err(ErrorChain::Client)?; match specs_request { @@ -225,7 +213,7 @@ pub async fn next_block_number(blocks: &mut Subscription) -> Result, -) -> Result { +) -> Result { block_hash(&client, Some(next_block_number(blocks).await?)).await } @@ -264,12 +252,11 @@ where /// Get all sufficient assets from a chain pub async fn assets_set_at_block( client: &WsClient, - block_hash: &H256, + block: &BlockHash, metadata_v15: &RuntimeMetadataV15, rpc_url: &str, specs: ShortSpecs, ) -> Result, ErrorChain> { - let block_hash = &hex::encode(&block_hash.0); let mut assets_set = HashMap::new(); let chain_name = >::spec_name_version(metadata_v15)?.spec_name; @@ -309,15 +296,14 @@ pub async fn assets_set_at_block( } } - let assets_asset_storage_metadata = assets_asset_storage_metadata.unwrap(); - let assets_metadata_storage_metadata = assets_metadata_storage_metadata.unwrap(); + 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 = - get_keys_from_storage(client, "Assets", "Asset", block_hash).await?; + get_keys_from_storage(client, "Assets", "Asset", block).await?; 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_hash) + let value_fetch = get_value_from_storage(client, string_key, block) .await .unwrap(); if let Value::String(ref string_value) = value_fetch { @@ -339,18 +325,17 @@ pub async fn assets_set_at_block( specialty: _, } = extended_data.data { - //println!("got asset id: {value}"); - value + Ok(value) } else { - panic!("asset id not u32") + Err(ErrorChain::AssetIdFormat) } } else { - panic!("assets asset key has no parseable part") + Err(ErrorChain::AssetKeyEmpty) } } else { - panic!("assets asset key is not a single hash entity") + Err(ErrorChain::AssetKeyNotSingleHash) } - }; + }?; let mut verified_sufficient = false; if let ParsedData::Composite(fields) = storage_entry.value.data { for field_data in fields.iter() { @@ -390,15 +375,11 @@ pub async fn assets_set_at_block( let value_fetch = get_value_from_storage( client, &key_assets_metadata, - block_hash, + block, ) - .await - .unwrap(); + .await?; if let Value::String(ref string_value) = value_fetch { - let value_data = hex::decode( - string_value.trim_start_matches("0x"), - ) - .unwrap(); + let value_data = unhex(string_value, NotHex::StorageValue)?; let value = decode_all_as_type::< &[u8], (), @@ -408,8 +389,7 @@ pub async fn assets_set_at_block( &value_data.as_ref(), &mut (), &metadata_v15.types, - ) - .unwrap(); + )?; let mut name = None; let mut symbol = None; @@ -527,31 +507,29 @@ pub async fn assets_set_at_block( } } } + } Ok(assets_set) } pub async fn asset_balance_at_account( client: &WsClient, - block_hash: &H256, + block: &BlockHash, metadata_v15: &RuntimeMetadataV15, account_id: &AccountId32, asset_id: AssetId, ) -> Result { - let block_hash = &hex::encode(&block_hash.0); let query = asset_balance_query(metadata_v15, account_id, asset_id)?; - let value_fetch = get_value_from_storage(client, &query.key, block_hash) - .await - .unwrap(); + let value_fetch = get_value_from_storage(client, &query.key, block) + .await?; if let Value::String(ref string_value) = value_fetch { - let value_data = hex::decode(string_value.trim_start_matches("0x")).unwrap(); + 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, - ) - .unwrap(); + )?; if let ParsedData::Composite(fields) = value.data { for field in fields.iter() { if let ParsedData::PrimitiveU128 { @@ -573,16 +551,14 @@ pub async fn asset_balance_at_account( pub async fn system_balance_at_account( client: &WsClient, - block_hash: &H256, + block: &BlockHash, metadata_v15: &RuntimeMetadataV15, account_id: &AccountId32, ) -> Result { - let block_hash = &hex::encode(&block_hash.0); let query = system_balance_query(metadata_v15, account_id)?; - let value_fetch = get_value_from_storage(client, &query.key, block_hash) - .await - .unwrap(); + let value_fetch = get_value_from_storage(client, &query.key, block) + .await?; if let Value::String(ref string_value) = value_fetch { let value_data = hex::decode(string_value.trim_start_matches("0x")).unwrap(); let value = decode_all_as_type::<&[u8], (), RuntimeMetadataV15>( @@ -590,8 +566,7 @@ pub async fn system_balance_at_account( &value_data.as_ref(), &mut (), &metadata_v15.types, - ) - .unwrap(); + )?; if let ParsedData::Composite(fields) = value.data { for field in fields.iter() { if field.field_name == Some("data".to_string()) { @@ -618,7 +593,7 @@ pub async fn system_balance_at_account( async fn transfer_events( client: &WsClient, - block: &H256, + block: &BlockHash, metadata_v15: &RuntimeMetadataV15, ) -> Result, ErrorChain> { let events_entry_metadata = events_entry_metadata(&metadata_v15)?; @@ -638,18 +613,17 @@ async fn transfer_events( pub async fn events_at_block( client: &WsClient, - block_hash: &H256, + block: &BlockHash, optional_filter: Option>, events_entry_metadata: &StorageEntryMetadata, types: &PortableRegistry, ) -> Result, ErrorChain> { - let block_hash = &hex::encode(&block_hash.0); - let keys_from_storage = get_keys_from_storage(client, "System", "Events", block_hash).await?; + let keys_from_storage = get_keys_from_storage(client, "System", "Events", block).await?; let mut out = Vec::new(); if let Value::Array(ref keys_array) = keys_from_storage { for key in keys_array { if let Value::String(key) = key { - let data_from_storage = get_value_from_storage(client, &key, block_hash).await?; + 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)? @@ -703,14 +677,12 @@ pub async fn events_at_block( pub async fn current_block_number( client: &WsClient, metadata: &RuntimeMetadataV15, - block_hash: &H256, -) -> u32 { - let block_hash = &hex::encode(&block_hash.0); + block: &BlockHash, +) -> Result { let block_number_query = block_number_query(metadata); if let Value::String(hex_data) = - get_value_from_storage(client, &block_number_query.key, block_hash) - .await - .unwrap() + get_value_from_storage(client, &block_number_query.key, block) + .await? { let value_data = hex::decode(hex_data.trim_start_matches("0x")).unwrap(); let value = decode_all_as_type::<&[u8], (), RuntimeMetadataV15>( @@ -725,12 +697,12 @@ pub async fn current_block_number( specialty: _, } = value.data { - value + Ok(value) } else { - panic!("unexpected block number format") + Err(ErrorChain::BlockNumberFormat) } } else { - panic!("not a string data") + Err(ErrorChain::StorageFormatError) } } diff --git a/src/chain/tracker.rs b/src/chain/tracker.rs index 1c48254..d425371 100644 --- a/src/chain/tracker.rs +++ b/src/chain/tracker.rs @@ -5,7 +5,6 @@ use std::collections::HashMap; use frame_metadata::v15::RuntimeMetadataV15; use jsonrpsee::ws_client::{WsClient, WsClientBuilder}; use jsonrpsee::core::client::{ClientT, Subscription, SubscriptionClientT}; -use primitive_types::H256; use substrate_parser::ShortSpecs; use tokio::{ sync::{mpsc, oneshot}, @@ -16,7 +15,7 @@ use tokio_util::sync::CancellationToken; use crate::{ TaskTracker, chain::{ - definitions::{ChainRequest, ChainTrackerRequest, EventFilter, Invoice}, + definitions::{BlockHash, ChainRequest, ChainTrackerRequest, EventFilter, Invoice}, payout::payout, rpc::{BlockHead, assets_set_at_block, block_hash, events_at_block, genesis_hash, metadata, next_block, next_block_number, specs, subscribe_blocks}, utils::{events_entry_metadata, was_balance_received_at_account}, @@ -57,7 +56,7 @@ pub fn start_chain_watch( Ok(a) => a, Err(e) => { tracing::info!( - "Failed to connect to chain {}, due to {:?} switching RPC server...", + "Failed to connect to chain {}, due to {} switching RPC server...", c.name, e ); @@ -77,7 +76,7 @@ pub fn start_chain_watch( Ok(a) => a, Err(e) => { tracing::info!( - "Failed to receive block in chain {}, due to {:?} switching RPC server...", + "Failed to receive block in chain {}, due to {} switching RPC server...", c.name, e ); @@ -148,7 +147,7 @@ pub fn start_chain_watch( #[derive(Debug, Clone)] pub struct ChainWatcher { - pub genesis_hash: H256, + pub genesis_hash: BlockHash, pub metadata: RuntimeMetadataV15, pub specs: ShortSpecs, pub assets: HashMap, @@ -188,7 +187,7 @@ impl ChainWatcher { } Ok(false) => (), Err(e) => { - tracing::warn!("account fetch error: {0:?}", e); + tracing::warn!("account fetch error: {0}", e); } } } diff --git a/src/chain/utils.rs b/src/chain/utils.rs index 3b43b20..84c8ede 100644 --- a/src/chain/utils.rs +++ b/src/chain/utils.rs @@ -1,7 +1,7 @@ //! Utils to process chain data without accessing the chain //TransactionToFill::init(&mut (), metadata, genesis_hash).unwrap(); -use crate::{definitions::api_v2::AssetId, error::ErrorChain}; +use crate::{chain::definitions::BlockHash, definitions::api_v2::AssetId, error::ErrorChain}; use frame_metadata::{ v14::StorageHasher, v15::{RuntimeMetadataV15, StorageEntryMetadata, StorageEntryType}, @@ -202,14 +202,14 @@ pub struct CallToFill(pub TypeToFill); pub fn construct_batch_transaction( metadata: &RuntimeMetadataV15, - genesis_hash: H256, + genesis_hash: BlockHash, author: AccountId32, call_set: &[CallToFill], - block_hash: H256, + block: BlockHash, block_number: u32, nonce: u32, ) -> TransactionToFill { - let mut transaction_to_fill = TransactionToFill::init(&mut (), metadata, genesis_hash).unwrap(); + let mut transaction_to_fill = TransactionToFill::init(&mut (), metadata, genesis_hash.0).unwrap(); // deal with author match transaction_to_fill.author.content { @@ -288,7 +288,7 @@ pub fn construct_batch_transaction( } } - transaction_to_fill.populate_block_info(Some(block_hash), Some(block_number.into())); + 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() { diff --git a/src/definitions.rs b/src/definitions.rs index 84d66cd..247b215 100644 --- a/src/definitions.rs +++ b/src/definitions.rs @@ -9,8 +9,6 @@ pub type Nonce = u32; pub type Timestamp = u64; pub type PalletIndex = u8; -pub type BlockHash = primitive_types::H256; - pub type Entropy = Vec; // TODO: maybe enforce something here #[derive(Deserialize)] diff --git a/src/error.rs b/src/error.rs index 70a022f..42ad0bd 100644 --- a/src/error.rs +++ b/src/error.rs @@ -93,12 +93,24 @@ impl From for Error { #[derive(Debug, thiserror::Error)] pub enum ErrorChain { + #[error("Asset id is not u32")] + AssetIdFormat, + + #[error("Asset key has no parceable part")] + AssetKeyEmpty, + + #[error("Asset key is not single hash")] + AssetKeyNotSingleHash, + #[error("Format of fetched base58 prefix {value} is not supported.")] Base58PrefixFormatNotSupported { value: 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, @@ -111,43 +123,15 @@ pub enum ErrorChain { #[error("Threading error. {0}")] Tokio(JoinError), - // #[error("Internal database error. {0}")] - // DbInternal(sled::Error), - - // #[error("Database error recording transaction. {0}")] - // DbTransaction(sled::transaction::TransactionError), #[error("Format of fetched decimals {value} is not supported.")] DecimalsFormatNotSupported { value: String }, - #[error("Fetch address in the database for genesis hash {} got damaged, and could not be decoded.", hex::encode(.0))] - DecodeDbAddress(H256), - - //#[error("Key in the database {} is damaged, and could not be decoded.", hex::encode(.0))] - //DecodeDbKey(IVec), - #[error("MetadataSpecs in the database for genesis hash {} got damaged, and could not be decoded.", hex::encode(.0))] - DecodeDbMetadataSpecs(H256), - #[error("Unexpected genesis hash format.")] GenesisHashFormat, #[error("Unexpected genesis hash length.")] GenesisHashLength, - #[error("Key {0} got damaged on the interface.")] - InterfaceKey(String), - - #[error( - "No metadata and specs for chain with genesis hash {} in the database.", - hex::encode(genesis_hash) - )] - LoadSpecsMetadata { genesis_hash: H256 }, - - #[error("Address for chain with genesis hash {} is not in the database.", hex::encode(.0))] - LostAddress(H256), - - #[error("Metadata fetch was somehow done with no pre-existing entry for genesis hash {}. This is a bug, please report it.", hex::encode(.0))] - MetadataFetchWithoutExistingEntry(H256), - #[error("...")] MetadataFormat, @@ -273,6 +257,9 @@ pub enum ErrorChain { #[error("Events do not exist in this chain")] EventsNonexistant, + + #[error("Substrate parser error: {0}")] + ParserError(ParserError<()>), } impl From for ErrorChain { @@ -305,6 +292,12 @@ impl From for ErrorChain { } } +impl From> for ErrorChain { + fn from(e: ParserError<()>) -> Self { + ErrorChain::ParserError(e) + } +} + #[derive(Debug, thiserror::Error)] pub enum ErrorDb { #[error("Currency key is not found")] From 1260002be3091a8b69316c78d27cb0fd2c68aea7 Mon Sep 17 00:00:00 2001 From: Slesarev Date: Thu, 16 May 2024 01:11:24 +0300 Subject: [PATCH 38/76] fix: some errors and fmt --- src/chain/definitions.rs | 18 +-- src/chain/mod.rs | 12 +- src/chain/payout.rs | 10 +- src/chain/rpc.rs | 328 +++++++++++++++++++-------------------- src/chain/tracker.rs | 15 +- src/chain/utils.rs | 3 +- src/database.rs | 29 ++-- src/error.rs | 22 ++- src/main.rs | 2 +- src/server.rs | 9 +- src/state.rs | 9 +- 11 files changed, 234 insertions(+), 223 deletions(-) diff --git a/src/chain/definitions.rs b/src/chain/definitions.rs index 515e942..a4e5735 100644 --- a/src/chain/definitions.rs +++ b/src/chain/definitions.rs @@ -12,18 +12,14 @@ use crate::{ rpc::{asset_balance_at_account, system_balance_at_account}, tracker::ChainWatcher, }, - definitions::{ - Balance, - Chain, - api_v2::{OrderInfo}, - }, + definitions::{api_v2::OrderInfo, Balance, Chain}, error::{ErrorChain, NotHex}, utils::unhex, }; /// Abstraction to distinguish block hash from many other H256 things #[derive(Debug, Clone)] -pub struct BlockHash (pub primitive_types::H256); +pub struct BlockHash(pub primitive_types::H256); impl BlockHash { /// Convert block hash to RPC-friendly format @@ -37,14 +33,13 @@ impl BlockHash { 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(|_| ErrorChain::BlockHashLength)?, - ))) + block_hash_raw + .try_into() + .map_err(|_| ErrorChain::BlockHashLength)?, + ))) } } - #[derive(Debug)] pub struct EventFilter<'a> { pub pallet: &'a str, @@ -190,4 +185,3 @@ impl Invoice { Ok(self.balance(client, chain_watcher, block).await? >= self.amount) } } - diff --git a/src/chain/mod.rs b/src/chain/mod.rs index 2201b46..7100118 100644 --- a/src/chain/mod.rs +++ b/src/chain/mod.rs @@ -10,17 +10,16 @@ use tokio::{ use tokio_util::sync::CancellationToken; use crate::{ - definitions::{Chain, api_v2::OrderInfo}, + definitions::{api_v2::OrderInfo, Chain}, error::{Error, ErrorChain}, - Signer, State, - TaskTracker, + Signer, State, TaskTracker, }; pub mod definitions; -pub mod utils; -pub mod rpc; pub mod payout; +pub mod rpc; pub mod tracker; +pub mod utils; use definitions::{ChainRequest, ChainTrackerRequest, WatchAccount}; use tracker::start_chain_watch; @@ -172,6 +171,3 @@ impl ChainManager { () } } - - - diff --git a/src/chain/payout.rs b/src/chain/payout.rs index a23632f..aba8de0 100644 --- a/src/chain/payout.rs +++ b/src/chain/payout.rs @@ -10,9 +10,9 @@ use crate::{ 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, + construct_batch_transaction, construct_single_asset_transfer_call, + construct_single_balance_transfer_call, AssetTransferConstructor, + BalanceTransferConstructor, }, }, definitions::api_v2::TokenKind, @@ -40,7 +40,9 @@ pub async fn payout( // after some retries record a failure if let Ok(client) = WsClientBuilder::default().build(rpc).await { let block = block_hash(&client, None).await.unwrap(); // TODO should retry instead - let block_number = current_block_number(&client, &chain.metadata, &block).await.unwrap(); + let block_number = current_block_number(&client, &chain.metadata, &block) + .await + .unwrap(); let balance = order.balance(&client, &chain, &block).await.unwrap(); // TODO same let loss_tolerance = 10000; // TODO: replace with multiple of existential let manual_intervention_amount = 1000000000000; diff --git a/src/chain/rpc.rs b/src/chain/rpc.rs index 8a8639f..69b744c 100644 --- a/src/chain/rpc.rs +++ b/src/chain/rpc.rs @@ -2,10 +2,12 @@ use crate::{ chain::{ - definitions::{BlockHash, EventFilter, WatchAccount,}, - utils::{asset_balance_query, base58prefix, block_number_query, events_entry_metadata, - hashed_key_element, pallet_index, storage_key, system_balance_query, - system_properties_to_short_specs, unit, was_balance_received_at_account}, + definitions::{BlockHash, EventFilter, WatchAccount}, + utils::{ + asset_balance_query, base58prefix, block_number_query, events_entry_metadata, + hashed_key_element, pallet_index, storage_key, system_balance_query, + system_properties_to_short_specs, unit, was_balance_received_at_account, + }, }, definitions::api_v2::{CurrencyProperties, OrderInfo}, definitions::{ @@ -45,7 +47,6 @@ use substrate_parser::{ AsMetadata, 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"; @@ -66,12 +67,12 @@ const AURA: &str = "AuraApi"; pub async fn subscribe_blocks(client: &WsClient) -> Result, ErrorChain> { Ok(client - .subscribe( - "chain_subscribeFinalizedHeads", - rpc_params![], - "unsubscribe blocks", - ) - .await?) + .subscribe( + "chain_subscribeFinalizedHeads", + rpc_params![], + "unsubscribe blocks", + ) + .await?) } pub async fn get_value_from_storage( @@ -80,7 +81,10 @@ pub async fn get_value_from_storage( block: &BlockHash, ) -> Result { let value: Value = client - .request("state_getStorage", rpc_params![whole_key, block.to_string()]) + .request( + "state_getStorage", + rpc_params![whole_key, block.to_string()], + ) .await?; Ok(value) } @@ -118,9 +122,7 @@ pub async fn genesis_hash(client: &WsClient) -> Result { .await .map_err(ErrorChain::Client)?; match genesis_hash_request { - Value::String(x) => { - BlockHash::from_str(&x) - } + Value::String(x) => BlockHash::from_str(&x), _ => return Err(ErrorChain::GenesisHashFormat), } } @@ -141,15 +143,16 @@ pub async fn block_hash( .await .map_err(ErrorChain::Client)?; match block_hash_request { - Value::String(x) => { - BlockHash::from_str(&x) - } + Value::String(x) => BlockHash::from_str(&x), _ => return Err(ErrorChain::BlockHashFormat), } } /// fetch metadata at known block -pub async fn metadata(client: &WsClient, block: &BlockHash) -> Result { +pub async fn metadata( + client: &WsClient, + block: &BlockHash, +) -> Result { let metadata_request: Value = client .request( "state_call", @@ -226,24 +229,6 @@ pub struct BlockHead { //parent_hash: String, //state_root: String, } -/* -#[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: u128, -}*/ -/* -fn account_deserializer<'de, D>(deserializer: D) -> Result -where - D: Deserializer<'de>, -{ - <([u8; 32],)>::deserialize(deserializer).map(|address| AccountId32(address.0)) -}*/ // TODO: add proper errors // @@ -296,111 +281,122 @@ pub async fn assets_set_at_block( } } - 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 = - get_keys_from_storage(client, "Assets", "Asset", block).await?; - 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 - .unwrap(); - if let Value::String(ref string_value) = value_fetch { - let key_data = hex::decode(string_key.trim_start_matches("0x")).unwrap(); - let value_data = hex::decode(string_value.trim_start_matches("0x")).unwrap(); - 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, - ) - .unwrap(); - 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) + 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 = + get_keys_from_storage(client, "Assets", "Asset", block).await?; + 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 = hex::decode(string_key.trim_start_matches("0x")).unwrap(); + let value_data = + hex::decode(string_value.trim_start_matches("0x")).unwrap(); + 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(ErrorChain::AssetIdFormat) + } } else { - Err(ErrorChain::AssetIdFormat) + Err(ErrorChain::AssetKeyEmpty) } } else { - Err(ErrorChain::AssetKeyEmpty) + Err(ErrorChain::AssetKeyNotSingleHash) } - } else { - Err(ErrorChain::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; + }?; + 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; } - break; } } } - } - if verified_sufficient { - match &assets_metadata_storage_metadata.ty { - StorageEntryType::Plain(_) => { - panic!("expected map with single entry, got plain") - } - StorageEntryType::Map { - hashers, - key: key_ty, - value: value_ty, - } => { - if hashers.len() == 1 { - let hasher = &hashers[0]; - match metadata_v15.types.resolve(key_ty.id).unwrap().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() { + if verified_sufficient { + match &assets_metadata_storage_metadata.ty { + StorageEntryType::Plain(_) => { + return Err(ErrorChain::AssetMetadataPlain) + } + StorageEntryType::Map { + hashers, + key: key_ty, + value: value_ty, + } => { + if hashers.len() == 1 { + let hasher = &hashers[0]; + match metadata_v15 + .types + .resolve(key_ty.id) + .unwrap() + .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()); @@ -468,37 +464,41 @@ pub async fn assets_set_at_block( }, _ => {}, } + } + if name.is_some() + && symbol.is_some() + && decimals.is_some() + { + break; + } } - if name.is_some() - && symbol.is_some() - && decimals.is_some() - { - break; - } + //let name = name.unwrap(); + let symbol = symbol.unwrap(); + let decimals = decimals.unwrap(); + 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( + ErrorChain::AssetMetadataUnexpected, + ); } - //let name = name.unwrap(); - let symbol = symbol.unwrap(); - let decimals = decimals.unwrap(); - 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 { - panic!("unexpected assets metadata value structure") } } + + _ => return Err(ErrorChain::AssetMetadataType), } - _ => panic!("wrong data type"), + } else { + return Err(ErrorChain::AssetMetadataMapSize); } - } else { - panic!("expected map with single entry, got multiple entries") } } } @@ -507,7 +507,6 @@ pub async fn assets_set_at_block( } } } - } Ok(assets_set) } @@ -520,8 +519,7 @@ pub async fn asset_balance_at_account( ) -> Result { let query = asset_balance_query(metadata_v15, account_id, asset_id)?; - let value_fetch = get_value_from_storage(client, &query.key, block) - .await?; + 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>( @@ -557,8 +555,7 @@ pub async fn system_balance_at_account( ) -> Result { let query = system_balance_query(metadata_v15, account_id)?; - let value_fetch = get_value_from_storage(client, &query.key, block) - .await?; + let value_fetch = get_value_from_storage(client, &query.key, block).await?; if let Value::String(ref string_value) = value_fetch { let value_data = hex::decode(string_value.trim_start_matches("0x")).unwrap(); let value = decode_all_as_type::<&[u8], (), RuntimeMetadataV15>( @@ -681,8 +678,7 @@ pub async fn current_block_number( ) -> Result { let block_number_query = block_number_query(metadata); if let Value::String(hex_data) = - get_value_from_storage(client, &block_number_query.key, block) - .await? + get_value_from_storage(client, &block_number_query.key, block).await? { let value_data = hex::decode(hex_data.trim_start_matches("0x")).unwrap(); let value = decode_all_as_type::<&[u8], (), RuntimeMetadataV15>( diff --git a/src/chain/tracker.rs b/src/chain/tracker.rs index d425371..32fde65 100644 --- a/src/chain/tracker.rs +++ b/src/chain/tracker.rs @@ -3,8 +3,8 @@ use std::collections::HashMap; use frame_metadata::v15::RuntimeMetadataV15; -use jsonrpsee::ws_client::{WsClient, WsClientBuilder}; use jsonrpsee::core::client::{ClientT, Subscription, SubscriptionClientT}; +use jsonrpsee::ws_client::{WsClient, WsClientBuilder}; use substrate_parser::ShortSpecs; use tokio::{ sync::{mpsc, oneshot}, @@ -13,17 +13,20 @@ use tokio::{ use tokio_util::sync::CancellationToken; use crate::{ - TaskTracker, chain::{ definitions::{BlockHash, ChainRequest, ChainTrackerRequest, EventFilter, Invoice}, - payout::payout, - rpc::{BlockHead, assets_set_at_block, block_hash, events_at_block, genesis_hash, metadata, next_block, next_block_number, specs, subscribe_blocks}, + payout::payout, + rpc::{ + assets_set_at_block, block_hash, events_at_block, genesis_hash, metadata, next_block, + next_block_number, specs, subscribe_blocks, BlockHead, + }, utils::{events_entry_metadata, was_balance_received_at_account}, }, - definitions::{Chain, api_v2::CurrencyProperties,}, + definitions::{api_v2::CurrencyProperties, Chain}, error::ErrorChain, signer::Signer, state::State, + TaskTracker, }; pub fn start_chain_watch( @@ -213,5 +216,3 @@ impl ChainWatcher { Ok(chain) } } - - diff --git a/src/chain/utils.rs b/src/chain/utils.rs index 84c8ede..6741e22 100644 --- a/src/chain/utils.rs +++ b/src/chain/utils.rs @@ -209,7 +209,8 @@ pub fn construct_batch_transaction( block_number: u32, nonce: u32, ) -> TransactionToFill { - let mut transaction_to_fill = TransactionToFill::init(&mut (), metadata, genesis_hash.0).unwrap(); + let mut transaction_to_fill = + TransactionToFill::init(&mut (), metadata, genesis_hash.0).unwrap(); // deal with author match transaction_to_fill.author.content { diff --git a/src/database.rs b/src/database.rs index 90ae055..cf091e8 100644 --- a/src/database.rs +++ b/src/database.rs @@ -192,18 +192,18 @@ impl Database { 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()) - ); + 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( @@ -241,10 +241,7 @@ impl Database { pub async fn order_list(&self) -> Result, ErrorDb> { let (res, rx) = oneshot::channel(); - let _unused = self - .tx - .send(DbRequest::ActiveOrderList(res)) - .await; + let _unused = self.tx.send(DbRequest::ActiveOrderList(res)).await; rx.await.map_err(|_| ErrorDb::DbEngineDown)? } diff --git a/src/error.rs b/src/error.rs index 42ad0bd..c5a4f70 100644 --- a/src/error.rs +++ b/src/error.rs @@ -102,6 +102,18 @@ pub enum ErrorChain { #[error("Asset key is not single hash")] AssetKeyNotSingleHash, + #[error("Asset metadata is not a map")] + AssetMetadataPlain, + + #[error("unexpected assets metadata value structure")] + AssetMetadataUnexpected, + + #[error("wrong data type")] + AssetMetadataType, + + #[error("expected map with single entry, got multiple entries")] + AssetMetadataMapSize, + #[error("Format of fetched base58 prefix {value} is not supported.")] Base58PrefixFormatNotSupported { value: String }, @@ -260,6 +272,9 @@ pub enum ErrorChain { #[error("Substrate parser error: {0}")] ParserError(ParserError<()>), + + #[error("Storage entry decoding error: {0}")] + StorageDecodeError(StorageError<()>), } impl From for ErrorChain { @@ -298,6 +313,12 @@ impl From> for ErrorChain { } } +impl From> for ErrorChain { + fn from(e: StorageError<()>) -> Self { + ErrorChain::StorageDecodeError(e) + } +} + #[derive(Debug, thiserror::Error)] pub enum ErrorDb { #[error("Currency key is not found")] @@ -380,7 +401,6 @@ pub enum ErrorForceWithdrawal { #[error("Withdrawal failed: {0:?}")] WithdrawalError(OrderStatus), - } #[derive(Debug, thiserror::Error)] diff --git a/src/main.rs b/src/main.rs index 24e015d..fe64300 100644 --- a/src/main.rs +++ b/src/main.rs @@ -32,9 +32,9 @@ mod state; mod utils; use crate::definitions::{Chain, Entropy, Timestamp, Version}; +use chain::ChainManager; use database::ConfigWoChains; use error::Error; -use chain::ChainManager; use signer::Signer; use state::State; diff --git a/src/server.rs b/src/server.rs index 2dd0dc5..c6d6ef9 100644 --- a/src/server.rs +++ b/src/server.rs @@ -185,7 +185,10 @@ async fn process_force_withdrawal( .find_map(|(key, value)| (key == ORDER_ID).then_some(value)) .ok_or_else(|| ErrorForceWithdrawal::MissingParameter(ORDER_ID.into()))? .to_owned(); - state.force_withdrawal(order).await.map_err(ErrorForceWithdrawal::WithdrawalError) + state + .force_withdrawal(order) + .await + .map_err(ErrorForceWithdrawal::WithdrawalError) } #[debug_handler] @@ -196,7 +199,9 @@ async fn force_withdrawal( ) -> Response { match process_force_withdrawal(state, &matched_path, path_result).await { Ok(a) => (StatusCode::CREATED, Json(a)).into_response(), - Err(ErrorForceWithdrawal::WithdrawalError(a)) => (StatusCode::BAD_REQUEST, Json(a)).into_response(), + Err(ErrorForceWithdrawal::WithdrawalError(a)) => { + (StatusCode::BAD_REQUEST, Json(a)).into_response() + } Err(ErrorForceWithdrawal::MissingParameter(parameter)) => ( StatusCode::BAD_REQUEST, Json([InvalidParameter { diff --git a/src/state.rs b/src/state.rs index d5d3efb..bbcc050 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,4 +1,5 @@ use crate::{ + chain::ChainManager, database::Database, definitions::{ api_v2::{ @@ -8,7 +9,6 @@ use crate::{ Entropy, }, error::{Error, ErrorOrder}, - chain::ChainManager, signer::Signer, ConfigWoChains, TaskTracker, }; @@ -63,7 +63,6 @@ impl State { 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)?; @@ -295,7 +294,7 @@ impl StateData { order_info, String::from("Order with this ID was already processed"), ))) - } + } } } @@ -306,8 +305,8 @@ impl StateData { 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(), + payment_page: String::new(), + redirect_url: String::new(), } } } From 69da5a74e8e3720fb689fd1c9ff7db8506532a78 Mon Sep 17 00:00:00 2001 From: Slesarev Date: Thu, 16 May 2024 09:23:10 +0300 Subject: [PATCH 39/76] fix: graceful unsubscribe, from comment by @Vovke --- src/chain/rpc.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/chain/rpc.rs b/src/chain/rpc.rs index 69b744c..0bb6a62 100644 --- a/src/chain/rpc.rs +++ b/src/chain/rpc.rs @@ -70,7 +70,7 @@ pub async fn subscribe_blocks(client: &WsClient) -> Result Date: Thu, 16 May 2024 09:58:04 +0300 Subject: [PATCH 40/76] fix: swat unwraps in rpc.rs --- src/chain/rpc.rs | 48 +++++++++++++++++++++++------------------------- src/error.rs | 12 ++++++++++++ 2 files changed, 35 insertions(+), 25 deletions(-) diff --git a/src/chain/rpc.rs b/src/chain/rpc.rs index 0bb6a62..9a159a7 100644 --- a/src/chain/rpc.rs +++ b/src/chain/rpc.rs @@ -44,7 +44,7 @@ use substrate_parser::{ decode_all_as_type, decode_as_storage_entry, special_indicators::SpecialtyUnsignedInteger, storage_data::{KeyData, KeyPart}, - AsMetadata, ShortSpecs, + AsMetadata, ResolveType, ShortSpecs, }; const MAX_BLOCK_NUMBER_ERROR: &str = "block number type overflow is occurred"; @@ -292,9 +292,8 @@ pub async fn assets_set_at_block( 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 = hex::decode(string_key.trim_start_matches("0x")).unwrap(); - let value_data = - hex::decode(string_value.trim_start_matches("0x")).unwrap(); + 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(), @@ -350,8 +349,7 @@ pub async fn assets_set_at_block( let hasher = &hashers[0]; match metadata_v15 .types - .resolve(key_ty.id) - .unwrap() + .resolve_ty(key_ty.id, &mut ())? .type_def { TypeDef::Primitive(TypeDefPrimitive::U32) => { @@ -456,7 +454,7 @@ pub async fn assets_set_at_block( } }, _ => {}, - }, + }, "decimals" => { if let ParsedData::PrimitiveU8{value, specialty: _} = field_data.data.data { decimals = Some(value); @@ -472,20 +470,21 @@ pub async fn assets_set_at_block( break; } } - //let name = name.unwrap(); - let symbol = symbol.unwrap(); - let decimals = decimals.unwrap(); - 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, - }, - ); + 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( ErrorChain::AssetMetadataUnexpected, @@ -557,7 +556,7 @@ pub async fn system_balance_at_account( let value_fetch = get_value_from_storage(client, &query.key, block).await?; if let Value::String(ref string_value) = value_fetch { - let value_data = hex::decode(string_value.trim_start_matches("0x")).unwrap(); + let value_data = unhex(string_value, NotHex::StorageValue)?; let value = decode_all_as_type::<&[u8], (), RuntimeMetadataV15>( &query.value_ty, &value_data.as_ref(), @@ -680,14 +679,13 @@ pub async fn current_block_number( if let Value::String(hex_data) = get_value_from_storage(client, &block_number_query.key, block).await? { - let value_data = hex::decode(hex_data.trim_start_matches("0x")).unwrap(); + 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, - ) - .unwrap(); + )?; if let ParsedData::PrimitiveU32 { value, specialty: _, diff --git a/src/error.rs b/src/error.rs index c5a4f70..191c84a 100644 --- a/src/error.rs +++ b/src/error.rs @@ -275,6 +275,9 @@ pub enum ErrorChain { #[error("Storage entry decoding error: {0}")] StorageDecodeError(StorageError<()>), + + #[error("Type registry error: {0}")] + RegistryError(RegistryError<()>), } impl From for ErrorChain { @@ -319,6 +322,12 @@ impl From> for ErrorChain { } } +impl From> for ErrorChain { + fn from(e: RegistryError<()>) -> Self { + ErrorChain::RegistryError(e) + } +} + #[derive(Debug, thiserror::Error)] pub enum ErrorDb { #[error("Currency key is not found")] @@ -456,6 +465,9 @@ pub enum NotHex { #[error("Encoded metadata string is not a valid hexadecimal.")] Metadata, + #[error("Encoded storage key string is not a valid hexadecimal.")] + StorageKey, + #[error("Encoded storage value string is not a valid hexadecimal.")] StorageValue, } From 3714fb59552b3e863b20e19b495984667682763d Mon Sep 17 00:00:00 2001 From: Slesarev Date: Thu, 16 May 2024 10:04:21 +0300 Subject: [PATCH 41/76] fix: swat panics in rpc.rs --- src/chain/rpc.rs | 6 +++--- src/error.rs | 6 ++++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/chain/rpc.rs b/src/chain/rpc.rs index 9a159a7..cff7108 100644 --- a/src/chain/rpc.rs +++ b/src/chain/rpc.rs @@ -537,12 +537,12 @@ pub async fn asset_balance_at_account( return Ok(Balance(value)); } } - panic!(); + Err(ErrorChain::AssetBalanceNotFound) } else { - panic!() + Err(ErrorChain::AssetBalanceFormat) } } else { - panic!() + Err(ErrorChain::StorageFormatError) } } diff --git a/src/error.rs b/src/error.rs index 191c84a..6a30b69 100644 --- a/src/error.rs +++ b/src/error.rs @@ -114,6 +114,12 @@ pub enum ErrorChain { #[error("expected map with single entry, got multiple entries")] AssetMetadataMapSize, + #[error("Asset balance format is unexpected")] + AssetBalanceFormat, + + #[error("No balance field in asset record")] + AssetBalanceNotFound, + #[error("Format of fetched base58 prefix {value} is not supported.")] Base58PrefixFormatNotSupported { value: String }, From 4cb0cce8673050a3ab6385b3911927febc2ec84e Mon Sep 17 00:00:00 2001 From: Slesarev Date: Thu, 16 May 2024 11:27:27 +0300 Subject: [PATCH 42/76] chore: reduce warnings count --- Cargo.toml | 2 +- src/asset.rs | 224 --------------------------------------- src/callback.rs | 43 +------- src/chain/definitions.rs | 6 +- src/chain/mod.rs | 11 +- src/chain/payout.rs | 1 - src/chain/rpc.rs | 51 ++++----- src/chain/tracker.rs | 20 ++-- src/chain/utils.rs | 9 +- src/error.rs | 78 +++----------- src/main.rs | 5 +- src/server.rs | 3 - src/state.rs | 2 +- 13 files changed, 67 insertions(+), 388 deletions(-) delete mode 100644 src/asset.rs diff --git a/Cargo.toml b/Cargo.toml index e058e9d..c623fe7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,7 +48,7 @@ codegen-units = 1 [lints.rust] future_incompatible = "warn" let_underscore = "warn" -rust_2018_idioms = "warn" +#rust_2018_idioms = "warn" unused = "warn" [lints.clippy] diff --git a/src/asset.rs b/src/asset.rs deleted file mode 100644 index 36d5dbe..0000000 --- a/src/asset.rs +++ /dev/null @@ -1,224 +0,0 @@ -use crate::definitions::{api_v2::AssetId, PalletIndex}; -use std::marker::PhantomData; - -#[derive(Clone, Debug)] -pub enum Asset { - Id(AssetId), - MultiLocation(PalletIndex, AssetId), -} - -impl Asset { - const ID: &'static str = "Id"; - const MULTI_LOCATION: &'static str = "MultiLocation"; -} - -/* -pub struct AssetVisitor(PhantomData); - -fn try_into_asset_id( - number: impl TryInto + ToString + Copy, -) -> Result { - number.try_into().map_err(|_| { - scale_decode::Error::new(ErrorKind::NumberOutOfRange { - value: number.to_string(), - }) - }) -} - -macro_rules! visit_number { - ($visit:ident, $number:ty) => { - fn $visit<'scale, 'resolver>( - self, - number: $number, - type_id: &TypeIdFor, - ) -> Result, Self::Error> { - self.visit_u32(try_into_asset_id(number)?, type_id) - } - }; -} - -macro_rules! visit_composite_or_tuple { - ($visit:ident, $composite_or_tuple:ident) => { - fn $visit<'scale, 'resolver>( - self, - composite_or_tuple: &mut $composite_or_tuple<'scale, 'resolver, Self::TypeResolver>, - type_id: &TypeIdFor, - ) -> Result, Self::Error> { - if composite_or_tuple.remaining() == 1 { - // Shouldn't panic. We've just checked remaining items above. - return composite_or_tuple - .decode_item(self) - .unwrap() - .map_err(|error| error.at_variant(Self::Value::ID)); - } - - let (pallet_index, asset_id) = (|| { - let MultiLocation { - interior: Junctions::X2(first, second), - .. - } = MultiLocation::into_visitor().$visit(composite_or_tuple, type_id)?; - - let Junction::PalletInstance(pallet_index) = first else { - return Err(scale_decode::Error::new(ErrorKind::CannotFindVariant { - got: first.name().into(), - expected: vec![Junction::PALLET_INSTANCE], - }) - .at_idx(0)); - }; - - let asset_id = (|| { - let Junction::GeneralIndex(general_index) = second else { - return Err(scale_decode::Error::new(ErrorKind::CannotFindVariant { - got: first.name().into(), - expected: vec![Junction::GENERAL_INDEX], - })); - }; - - let asset_id = general_index.try_into().map_err(|_| { - scale_decode::Error::new(ErrorKind::NumberOutOfRange { - value: general_index.to_string(), - }) - })?; - - Ok(asset_id) - })() - .map_err(|error| error.at_idx(1))?; - - Ok((pallet_index, asset_id)) - })() - .map_err(|error| error.at_variant(Self::Value::MULTI_LOCATION))?; - - Ok(Self::Value::MultiLocation(pallet_index, asset_id)) - } - }; -} - -impl Visitor for AssetVisitor { - type Value<'scale, 'resolver> = Asset; - type Error = scale_decode::Error; - type TypeResolver = R; - - fn visit_u32<'scale, 'resolver>( - self, - value: u32, - _: &TypeIdFor, - ) -> Result, Self::Error> { - Ok(Self::Value::Id(value)) - } - - fn visit_u8<'scale, 'resolver>( - self, - value: u8, - type_id: &TypeIdFor, - ) -> Result, Self::Error> { - self.visit_u32(value.into(), type_id) - } - - fn visit_u16<'scale, 'resolver>( - self, - value: u16, - type_id: &TypeIdFor, - ) -> Result, Self::Error> { - self.visit_u32(value.into(), type_id) - } - - visit_number!(visit_i8, i8); - visit_number!(visit_i16, i16); - visit_number!(visit_i32, i32); - visit_number!(visit_u64, u64); - visit_number!(visit_i64, i64); - visit_number!(visit_i128, i128); - visit_number!(visit_u128, u128); - - visit_composite_or_tuple!(visit_tuple, Tuple); - visit_composite_or_tuple!(visit_composite, Composite); -} - -impl IntoVisitor for Asset { - type AnyVisitor = AssetVisitor; - - fn into_visitor() -> Self::AnyVisitor { - AssetVisitor(PhantomData) - } -} - -impl EncodeAsType for Asset { - fn encode_as_type_to( - &self, - type_id: &R::TypeId, - types: &R, - out: &mut Vec, - ) -> Result<(), scale_encode::Error> { - match self { - Self::Id(id) => id.encode_as_type_to(type_id, types, out), - Self::MultiLocation(assets_pallet, asset_id) => { - MultiLocation::new(*assets_pallet, *asset_id).encode_as_type_to(type_id, types, out) - } - } - } -} - -impl Encode for Asset { - fn size_hint(&self) -> usize { - match self { - Self::Id(id) => id.size_hint(), - Self::MultiLocation(assets_pallet, asset_id) => { - MultiLocation::new(*assets_pallet, *asset_id).size_hint() - } - } - } - - fn encode_to(&self, dest: &mut T) { - match self { - Self::Id(id) => id.encode_to(dest), - Self::MultiLocation(assets_pallet, asset_id) => { - MultiLocation::new(*assets_pallet, *asset_id).encode_to(dest); - } - } - } -} - -#[derive(EncodeAsType, DecodeAsType, Encode)] -struct MultiLocation { - parents: u8, - interior: Junctions, -} - -impl MultiLocation { - fn new(assets_pallet: PalletIndex, asset_id: AssetId) -> Self { - Self { - parents: 0, - interior: Junctions::X2( - Junction::PalletInstance(assets_pallet), - Junction::GeneralIndex(asset_id.into()), - ), - } - } -} - -#[derive(EncodeAsType, DecodeAsType, Encode)] -enum Junctions { - #[codec(index = 2)] - X2(Junction, Junction), -} - -#[derive(EncodeAsType, DecodeAsType, Encode)] -enum Junction { - #[codec(index = 4)] - PalletInstance(PalletIndex), - #[codec(index = 5)] - GeneralIndex(u128), -} - -impl Junction { - const PALLET_INSTANCE: &'static str = "PalletInstance"; - const GENERAL_INDEX: &'static str = "GeneralIndex"; - - fn name(&self) -> &str { - match self { - Self::PalletInstance(_) => Self::PALLET_INSTANCE, - Self::GeneralIndex(_) => Self::GENERAL_INDEX, - } - } -} -*/ diff --git a/src/callback.rs b/src/callback.rs index e63cc52..9749435 100644 --- a/src/callback.rs +++ b/src/callback.rs @@ -1,60 +1,23 @@ use crate::definitions::{ api_v2::{ - CurrencyInfo, OrderInfo, OrderStatus, PaymentStatus, ServerInfo, TokenKind, - WithdrawalStatus, + OrderStatus }, - Balance, }; -use substrate_crypto_light::common::AccountId32; use tokio::task; pub const MODULE: &str = module_path!(); pub async fn callback( path: String, - order: String, - recipient: AccountId32, - debug: bool, - remark: String, - amount: Balance, - rpc_url: String, - paym_acc: AccountId32, + order_status: OrderStatus, ) { - /* let req = ureq::post(&path); task::spawn_blocking(move || { let _d = req - .send_json(OrderStatus { - order, - payment_status: PaymentStatus::Paid, - message: String::new(), - recipient: recipient.to_ss58check(), - server_info: ServerInfo { - version: env!("CARGO_PKG_VERSION"), - instance_id: String::new(), - debug, - kalatori_remark: remark, - }, - order_info: OrderInfo { - withdrawal_status: WithdrawalStatus::Waiting, - amount: amount.format(6), - currency: CurrencyInfo { - currency: "USDC".into(), - chain_name: "assethub-polkadot".into(), - kind: TokenKind::Asset, - decimals: 6, - rpc_url, - asset_id: Some(1337), - }, - callback: path, - transactions: vec![], - payment_account: paym_acc.to_ss58check(), - }, - }) + .send_json(order_status) .unwrap(); }) .await .unwrap(); - */ } diff --git a/src/chain/definitions.rs b/src/chain/definitions.rs index a4e5735..402fe1d 100644 --- a/src/chain/definitions.rs +++ b/src/chain/definitions.rs @@ -1,10 +1,8 @@ //! Common objects for chain interaction system -use frame_metadata::v15::RuntimeMetadataV15; -use jsonrpsee::ws_client::{WsClient, WsClientBuilder}; +use jsonrpsee::ws_client::WsClient; use primitive_types::H256; use substrate_crypto_light::common::{AccountId32, AsBase58}; -use substrate_parser::ShortSpecs; use tokio::sync::oneshot; use crate::{ @@ -12,7 +10,7 @@ use crate::{ rpc::{asset_balance_at_account, system_balance_at_account}, tracker::ChainWatcher, }, - definitions::{api_v2::OrderInfo, Balance, Chain}, + definitions::{api_v2::OrderInfo, Balance}, error::{ErrorChain, NotHex}, utils::unhex, }; diff --git a/src/chain/mod.rs b/src/chain/mod.rs index 7100118..3bf3ca0 100644 --- a/src/chain/mod.rs +++ b/src/chain/mod.rs @@ -27,6 +27,9 @@ 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 { @@ -51,7 +54,8 @@ impl ChainManager { // start network monitors for c in chain { - let (chain_tx, mut chain_rx) = mpsc::channel(1024); + 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()); if let Some(ref a) = c.native_token { if let Some(_) = currency_map.insert(a.name.clone(), c.name.clone()) { @@ -66,7 +70,6 @@ impl ChainManager { start_chain_watch( c, - ¤cy_map, chain_tx.clone(), chain_rx, state.interface(), @@ -119,7 +122,9 @@ impl ChainManager { for (name, chain) in watch_chain.drain() { let (tx, rx) = oneshot::channel(); if chain.send(ChainTrackerRequest::Shutdown(tx)).await.is_ok() { - let _ = rx.await; + 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(()); diff --git a/src/chain/payout.rs b/src/chain/payout.rs index aba8de0..f21cdc7 100644 --- a/src/chain/payout.rs +++ b/src/chain/payout.rs @@ -23,7 +23,6 @@ use crate::{ use frame_metadata::v15::RuntimeMetadataV15; use jsonrpsee::ws_client::WsClientBuilder; use substrate_constructor::fill_prepare::{SpecialTypeToFill, TypeContentToFill}; -use substrate_crypto_light::common::AccountId32; /// Single function that should completely handle payout attmept. Just do not call anything else. /// diff --git a/src/chain/rpc.rs b/src/chain/rpc.rs index cff7108..24fcafc 100644 --- a/src/chain/rpc.rs +++ b/src/chain/rpc.rs @@ -2,23 +2,20 @@ use crate::{ chain::{ - definitions::{BlockHash, EventFilter, WatchAccount}, + definitions::{BlockHash, EventFilter}, utils::{ - asset_balance_query, base58prefix, block_number_query, events_entry_metadata, - hashed_key_element, pallet_index, storage_key, system_balance_query, - system_properties_to_short_specs, unit, was_balance_received_at_account, + asset_balance_query, block_number_query, events_entry_metadata, + hashed_key_element, system_balance_query, + system_properties_to_short_specs, }, }, - definitions::api_v2::{CurrencyProperties, OrderInfo}, + definitions::api_v2::CurrencyProperties, definitions::{ - api_v2::{AssetId, BlockNumber, CurrencyInfo, Decimals, TokenKind}, - AssetInfo, Balance, Chain, NativeToken, Nonce, PalletIndex, Timestamp, + api_v2::{AssetId, TokenKind}, + Balance, }, - error::{Error, ErrorChain, NotHex}, - signer::Signer, - state::State, + error::{ErrorChain, NotHex}, utils::unhex, - TaskTracker, }; use frame_metadata::{ v15::{RuntimeMetadataV15, StorageEntryMetadata, StorageEntryType}, @@ -26,21 +23,19 @@ use frame_metadata::{ }; use jsonrpsee::core::client::{ClientT, Subscription, SubscriptionClientT}; use jsonrpsee::rpc_params; -use jsonrpsee::ws_client::{WsClient, WsClientBuilder}; -use parity_scale_codec::{Decode, DecodeAll, Encode}; +use jsonrpsee::ws_client::WsClient; +use parity_scale_codec::{DecodeAll, Encode}; use scale_info::{form::PortableForm, PortableRegistry, TypeDef, TypeDefPrimitive}; -use serde::{Deserialize, Deserializer}; -use serde_json::{Map, Number, Value}; -use sp_crypto_hashing::{blake2_128, blake2_256, twox_128, twox_256, twox_64}; +use serde::Deserialize; +use serde_json::{Number, Value}; +use sp_crypto_hashing::twox_128; use std::{ - borrow::Cow, - collections::{hash_map::Entry, HashMap}, + collections::HashMap, fmt::Debug, - num::NonZeroU64, }; -use substrate_crypto_light::common::{AccountId32, AsBase58}; +use substrate_crypto_light::common::AccountId32; use substrate_parser::{ - cards::{Event, ExtendedData, FieldData, ParsedData, Sequence}, + cards::{Event, ParsedData, Sequence}, decode_all_as_type, decode_as_storage_entry, special_indicators::SpecialtyUnsignedInteger, storage_data::{KeyData, KeyPart}, @@ -542,7 +537,7 @@ pub async fn asset_balance_at_account( Err(ErrorChain::AssetBalanceFormat) } } else { - Err(ErrorChain::StorageFormatError) + Err(ErrorChain::StorageValueFormat(value_fetch)) } } @@ -624,7 +619,7 @@ pub async fn events_at_block( let value_bytes = if let Value::String(data_from_storage) = data_from_storage { unhex(&data_from_storage, NotHex::StorageValue)? } else { - return Err(ErrorChain::StorageFormatError); + return Err(ErrorChain::StorageValueFormat(data_from_storage)); }; let storage_data = decode_as_storage_entry::<&[u8], (), RuntimeMetadataV15>( &key_bytes.as_ref(), @@ -676,9 +671,9 @@ pub async fn current_block_number( block: &BlockHash, ) -> Result { let block_number_query = block_number_query(metadata); - if let Value::String(hex_data) = - get_value_from_storage(client, &block_number_query.key, block).await? - { + 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, @@ -696,7 +691,7 @@ pub async fn current_block_number( Err(ErrorChain::BlockNumberFormat) } } else { - Err(ErrorChain::StorageFormatError) + Err(ErrorChain::StorageValueFormat(fetched_value)) } } @@ -716,7 +711,7 @@ pub async fn send_stuff(client: &WsClient, data: &str) -> Result<(), ErrorChain> let mut subscription: Subscription = client .subscribe("author_submitAndWatchExtrinsic", rpc_params, "") .await?; - let reply = subscription.next().await.unwrap(); + let _reply = subscription.next().await.unwrap(); //println!("{reply:?}"); // TODO! Ok(()) } diff --git a/src/chain/tracker.rs b/src/chain/tracker.rs index 32fde65..4e62f58 100644 --- a/src/chain/tracker.rs +++ b/src/chain/tracker.rs @@ -3,22 +3,21 @@ use std::collections::HashMap; use frame_metadata::v15::RuntimeMetadataV15; -use jsonrpsee::core::client::{ClientT, Subscription, SubscriptionClientT}; use jsonrpsee::ws_client::{WsClient, WsClientBuilder}; use substrate_parser::ShortSpecs; use tokio::{ - sync::{mpsc, oneshot}, + sync::mpsc, time::{timeout, Duration}, }; use tokio_util::sync::CancellationToken; use crate::{ chain::{ - definitions::{BlockHash, ChainRequest, ChainTrackerRequest, EventFilter, Invoice}, + definitions::{BlockHash, ChainTrackerRequest, EventFilter, Invoice}, payout::payout, rpc::{ assets_set_at_block, block_hash, events_at_block, genesis_hash, metadata, next_block, - next_block_number, specs, subscribe_blocks, BlockHead, + next_block_number, specs, subscribe_blocks, }, utils::{events_entry_metadata, was_balance_received_at_account}, }, @@ -31,7 +30,6 @@ use crate::{ pub fn start_chain_watch( c: Chain, - currency_map: &HashMap, chain_tx: mpsc::Sender, mut chain_rx: mpsc::Receiver, state: State, @@ -79,14 +77,14 @@ pub fn start_chain_watch( Ok(a) => a, Err(e) => { tracing::info!( - "Failed to receive block in chain {}, due to {} switching RPC server...", - c.name, - e - ); - continue; - + "Failed to receive block in chain {}, due to {} switching RPC server...", + c.name, + e + ); + continue; }, }; + // TODO: continue and reconnect if spec_version changed let events = events_at_block( &client, &block, diff --git a/src/chain/utils.rs b/src/chain/utils.rs index 6741e22..02c2696 100644 --- a/src/chain/utils.rs +++ b/src/chain/utils.rs @@ -6,10 +6,9 @@ use frame_metadata::{ v14::StorageHasher, v15::{RuntimeMetadataV15, StorageEntryMetadata, StorageEntryType}, }; -use parity_scale_codec::{Decode, Encode}; -use primitive_types::H256; +use parity_scale_codec::Encode; use scale_info::{form::PortableForm, TypeDef, TypeDefPrimitive}; -use serde_json::{Map, Number, Value}; +use serde_json::{Map, Value}; use sp_crypto_hashing::{blake2_128, blake2_256, twox_128, twox_256, twox_64}; use substrate_constructor::{ fill_prepare::{ @@ -23,14 +22,14 @@ use substrate_constructor::{ StorageSelector, StorageSelectorFunctional, }, }; -use substrate_crypto_light::common::{AccountId32, DeriveJunction, FullDerivation}; +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, - AsMetadata, ShortSpecs, + ShortSpecs, }; pub struct AssetTransferConstructor<'a> { diff --git a/src/error.rs b/src/error.rs index 6a30b69..9fbe063 100644 --- a/src/error.rs +++ b/src/error.rs @@ -24,6 +24,9 @@ pub enum Error { #[error("failed to parse the config parameter {0}")] ConfigParse(String), + #[error("chain {0} doesn't have any `endpoints` in the config")] + EmptyEndpoints(String), + #[error("RPC server error: {0:?}")] ErrorChain(ErrorChain), @@ -147,95 +150,50 @@ pub enum ErrorChain { #[error("Unexpected genesis hash format.")] GenesisHashFormat, - #[error("Unexpected genesis hash length.")] - GenesisHashLength, - #[error("...")] MetadataFormat, #[error("...")] MetadataNotDecodeable, - #[error("{0}")] - MetadataVersion(MetaVersionErrorPallets), - #[error("No base58 prefix is fetched as system properties or found in the metadata.")] NoBase58Prefix, #[error("No decimals value is fetched.")] NoDecimals, - #[error("No existing metadata and specs entry before metadata update for hash {}. Remove entry and start over.", hex::encode(.0))] - NoExistingEntryMetadataUpdate(H256), - #[error("Metadata v15 not available through rpc.")] NoMetadataV15, #[error("Metadata must start with `meta` prefix.")] NoMetaPrefix, - #[error("{0}")] - NotHex(NotHex), - - #[error("Fetched values were not sent through successfully.")] - NotSent, - #[error("No unit value is fetched.")] NoUnit, - #[error("Only Sr25519 encryption, 0x01, is supported. Received transaction has encoded encryption 0x{}", hex::encode([*.0]))] - OnlySr25519(u8), - - #[error("...")] - PoisonedLockSelector, - #[error("...")] PropertiesFormat, #[error("...")] RawMetadataNotDecodeable, - #[error("Can't read data through the interface. Receiver closed.")] - ReceiverClosed, - - #[error("Can't read data through the interface. Receiver guard is poisoned.")] - ReceiverGuardPoisoned, - - #[error("Received QR payload is too short.")] - TooShort, - - #[error("Received transaction could not be parsed. {0}.")] - TransactionNotParsable(SignableError<(), RuntimeMetadataV15>), - - #[error("Unexpected payload type, 0x{}", hex::encode([*.0]))] - UnknownPayloadType(u8), - #[error("Format of fetched unit {value} is not supported.")] UnitFormatNotSupported { value: String }, - #[error("Try updating metadata. Metadata version in transaction {as_decoded} does not match the version of the available metadata entry {in_metadata}.")] - UpdateMetadata { - as_decoded: String, - in_metadata: String, - }, - - #[error("Unexpected storage value format for key {0}")] - StorageValueFormat(String), - - #[error("Chain returned zero for block time")] - ZeroBlockTime, + #[error("Unexpected storage value format for key {0:?}")] + StorageValueFormat(Value), - #[error("chain doesn't have any `endpoints` in the config")] - EmptyEndpoints, + //#[error("Chain returned zero for block time")] + //ZeroBlockTime, - #[error("Runtime api call response should be String, but received {0:?}")] - StateCallResponse(Value), + //#[error("Runtime api call response should be String, but received {0:?}")] + //StateCallResponse(Value), - #[error("Could not fetch BABE expected block time")] - BabeExpectedBlockTime, + //#[error("Could not fetch BABE expected block time")] + //BabeExpectedBlockTime, - #[error("Aura slot duration could not be parsed as u64")] - AuraSlotDurationFormat, + //#[error("Aura slot duration could not be parsed as u64")] + //AuraSlotDurationFormat, #[error("Internal error: {0:?}")] // TODO this should be replaced by specific errors ErrorUtil(ErrorUtil), @@ -267,8 +225,8 @@ pub enum ErrorChain { #[error("Storage query could not be formed")] StorageQuery, - #[error("Storage format error")] - StorageFormatError, + //#[error("Storage format error")] + //StorageFormatError, #[error("Events could not be fetched")] EventsMissing, @@ -399,9 +357,6 @@ pub enum ErrorOrder { #[error("Order parameter invalid: {0}")] InvalidParameter(String), - #[error("Order already processed: {0:?}")] - AlreadyProcessed(Box), - #[error("Internal error")] InternalError, } @@ -465,9 +420,6 @@ pub enum NotHex { #[error("Block hash string is not a valid hexadecimal.")] BlockHash, - #[error("Genesis hash string is not a valid hexadecimal.")] - GenesisHash, - #[error("Encoded metadata string is not a valid hexadecimal.")] Metadata, diff --git a/src/main.rs b/src/main.rs index fe64300..1816fc2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,11 +7,9 @@ use std::{ fs, future::Future, net::{IpAddr, Ipv4Addr, SocketAddr}, - ops::Deref, panic, str, }; use substrate_crypto_light::common::{AccountId32, AsBase58}; -use substrate_crypto_light::{common::cut_path, sr25519::Pair}; use tokio::{ signal, sync::{mpsc, oneshot}, @@ -20,7 +18,6 @@ use tokio::{ use tokio_util::{sync::CancellationToken, task}; use tracing_subscriber::{fmt::time::UtcTime, EnvFilter}; -mod asset; mod callback; mod chain; mod database; @@ -127,7 +124,7 @@ async fn main() -> Result<(), Error> { let db = database::Database::init(database_path, task_tracker.clone())?; - let (cm_tx, mut cm_rx) = oneshot::channel(); + let (cm_tx, cm_rx) = oneshot::channel(); let state = State::initialise( currencies, diff --git a/src/server.rs b/src/server.rs index c6d6ef9..1bf9973 100644 --- a/src/server.rs +++ b/src/server.rs @@ -163,9 +163,6 @@ async fn order( }]), ) .into_response(), - ErrorOrder::AlreadyProcessed(order_status) => { - (StatusCode::CONFLICT, Json(order_status)).into_response() - } ErrorOrder::InternalError => StatusCode::INTERNAL_SERVER_ERROR.into_response(), }, } diff --git a/src/state.rs b/src/state.rs index bbcc050..f04dc6b 100644 --- a/src/state.rs +++ b/src/state.rs @@ -22,7 +22,7 @@ use tokio::sync::oneshot; /// it should go through here. #[derive(Clone, Debug)] pub struct State { - pub tx: tokio::sync::mpsc::Sender, + tx: tokio::sync::mpsc::Sender, } impl State { From 6a72dc5c81cae256eba74423552123bfe947713a Mon Sep 17 00:00:00 2001 From: Slesarew <33295157+Slesarew@users.noreply.github.com> Date: Thu, 16 May 2024 19:48:47 +0300 Subject: [PATCH 43/76] Update src/chain/rpc.rs Co-authored-by: Vova Lando --- src/chain/rpc.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/chain/rpc.rs b/src/chain/rpc.rs index 0bb6a62..cb6609e 100644 --- a/src/chain/rpc.rs +++ b/src/chain/rpc.rs @@ -158,7 +158,7 @@ pub async fn metadata( "state_call", rpc_params![ "Metadata_metadata_at_version", - "0f000000", + "0x0f000000", &block.to_string() ], ) From 110e6e5f194e046105cfbbe5f961636479909fff Mon Sep 17 00:00:00 2001 From: Slesarev Date: Thu, 16 May 2024 22:02:53 +0300 Subject: [PATCH 44/76] fix: swat panics in chain/utils --- src/callback.rs | 15 +- src/chain/definitions.rs | 6 +- src/chain/mod.rs | 18 +- src/chain/payout.rs | 50 ++- src/chain/rpc.rs | 15 +- src/chain/utils.rs | 917 ++++++++++++++++++++------------------- src/error.rs | 59 ++- src/state.rs | 6 +- 8 files changed, 581 insertions(+), 505 deletions(-) diff --git a/src/callback.rs b/src/callback.rs index 9749435..96bfd30 100644 --- a/src/callback.rs +++ b/src/callback.rs @@ -1,22 +1,13 @@ -use crate::definitions::{ - api_v2::{ - OrderStatus - }, -}; +use crate::definitions::api_v2::OrderStatus; use tokio::task; pub const MODULE: &str = module_path!(); -pub async fn callback( - path: String, - order_status: OrderStatus, -) { +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(); + let _d = req.send_json(order_status).unwrap(); }) .await .unwrap(); diff --git a/src/chain/definitions.rs b/src/chain/definitions.rs index 402fe1d..b8c868c 100644 --- a/src/chain/definitions.rs +++ b/src/chain/definitions.rs @@ -94,7 +94,7 @@ pub struct WatchAccount { pub address: AccountId32, pub currency: String, pub amount: Balance, - pub recipient: Option, + pub recipient: AccountId32, pub res: oneshot::Sender>, } @@ -102,7 +102,7 @@ impl WatchAccount { pub fn new( id: String, order: OrderInfo, - recipient: Option, + recipient: AccountId32, res: oneshot::Sender>, ) -> Result { Ok(WatchAccount { @@ -131,7 +131,7 @@ pub struct Invoice { pub address: AccountId32, pub currency: String, pub amount: Balance, - pub recipient: Option, + pub recipient: AccountId32, } impl Invoice { diff --git a/src/chain/mod.rs b/src/chain/mod.rs index 3bf3ca0..19919db 100644 --- a/src/chain/mod.rs +++ b/src/chain/mod.rs @@ -54,7 +54,9 @@ impl ChainManager { // start network monitors for c in chain { - if c.endpoints.is_empty() {return Err(Error::EmptyEndpoints(c.name))} + 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()); if let Some(ref a) = c.native_token { @@ -139,11 +141,16 @@ impl ChainManager { Ok(Self { tx }) } - pub async fn add_invoice(&self, id: String, order: OrderInfo) -> Result<(), ErrorChain> { + pub async fn add_invoice( + &self, + id: String, + order: OrderInfo, + recipient: AccountId32, + ) -> Result<(), ErrorChain> { let (res, rx) = oneshot::channel(); self.tx .send(ChainRequest::WatchAccount(WatchAccount::new( - id, order, None, res, + id, order, recipient, res, )?)) .await .map_err(|_| ErrorChain::MessageDropped)?; @@ -159,10 +166,7 @@ impl ChainManager { let (res, rx) = oneshot::channel(); self.tx .send(ChainRequest::Reap(WatchAccount::new( - id, - order, - Some(recipient), - res, + id, order, recipient, res, )?)) .await .map_err(|_| ErrorChain::MessageDropped)?; diff --git a/src/chain/payout.rs b/src/chain/payout.rs index f21cdc7..e5694ce 100644 --- a/src/chain/payout.rs +++ b/src/chain/payout.rs @@ -16,6 +16,7 @@ use crate::{ }, }, definitions::api_v2::TokenKind, + error::ErrorChain, signer::Signer, state::State, }; @@ -33,19 +34,20 @@ pub async fn payout( state: State, chain: ChainWatcher, signer: Signer, -) { +) -> Result<(), ErrorChain> { // 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.unwrap(); // TODO should retry instead - let block_number = current_block_number(&client, &chain.metadata, &block) - .await - .unwrap(); - let balance = order.balance(&client, &chain, &block).await.unwrap(); // TODO same + 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).unwrap(); + let currency = chain + .assets + .get(&order.currency) + .ok_or(ErrorChain::InvalidCurrency(order.currency))?; // Payout operation logic let transactions = match balance.0 - order.amount.0 { @@ -53,33 +55,33 @@ pub async fn payout( TokenKind::Balances => { let balance_transfer_constructor = BalanceTransferConstructor { amount: order.amount.0, - to_account: &order.recipient.unwrap(), + 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.unwrap(), + asset_id: currency.asset_id.ok_or(ErrorChain::AssetId)?, amount: order.amount.0, - to_account: &order.recipient.unwrap(), + 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; + return Ok(()); //TODO } _ => { tracing::error!("Balance is out of range: {balance:?}"); - return; + return Ok(()); //TODO } }; @@ -91,24 +93,26 @@ pub async fn payout( block, block_number, 0, - ); + )?; - let sign_this = batch_transaction.sign_this().unwrap(); + let sign_this = batch_transaction + .sign_this() + .ok_or(ErrorChain::TransactionNotSignable(format!( + "{batch_transaction:?}" + )))?; - let signature = signer.sign(order.id, sign_this).await.unwrap(); + 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) - .unwrap() - .unwrap(); + .send_this_signed::<(), RuntimeMetadataV15>(&chain.metadata)? + .ok_or(ErrorChain::NothingToSend)?; - send_stuff(&client, &format!("0x{}", hex::encode(extrinsic))) - .await - .unwrap(); + send_stuff(&client, &format!("0x{}", hex::encode(extrinsic))).await?; // TODO obvious } + Ok(()) } diff --git a/src/chain/rpc.rs b/src/chain/rpc.rs index 24fcafc..fd44c7b 100644 --- a/src/chain/rpc.rs +++ b/src/chain/rpc.rs @@ -4,9 +4,8 @@ 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, + asset_balance_query, block_number_query, events_entry_metadata, hashed_key_element, + system_balance_query, system_properties_to_short_specs, }, }, definitions::api_v2::CurrencyProperties, @@ -29,10 +28,7 @@ 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 std::{collections::HashMap, fmt::Debug}; use substrate_crypto_light::common::AccountId32; use substrate_parser::{ cards::{Event, ParsedData, Sequence}, @@ -670,10 +666,9 @@ pub async fn current_block_number( metadata: &RuntimeMetadataV15, block: &BlockHash, ) -> Result { - let block_number_query = block_number_query(metadata); + 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 - { + 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, diff --git a/src/chain/utils.rs b/src/chain/utils.rs index 02c2696..3ee0315 100644 --- a/src/chain/utils.rs +++ b/src/chain/utils.rs @@ -1,6 +1,5 @@ //! Utils to process chain data without accessing the chain -//TransactionToFill::init(&mut (), metadata, genesis_hash).unwrap(); use crate::{chain::definitions::BlockHash, definitions::api_v2::AssetId, error::ErrorChain}; use frame_metadata::{ v14::StorageHasher, @@ -29,7 +28,7 @@ use substrate_parser::{ decoding_sci::Ty, propagated::Propagated, special_indicators::SpecialtyUnsignedInteger, - ShortSpecs, + ResolveType, ShortSpecs, }; pub struct AssetTransferConstructor<'a> { @@ -41,14 +40,13 @@ pub struct AssetTransferConstructor<'a> { pub fn construct_single_asset_transfer_call( metadata: &RuntimeMetadataV15, asset_transfer_constructor: &AssetTransferConstructor, -) -> CallToFill { +) -> Result { let mut call = prepare_type::<(), RuntimeMetadataV15>( &Ty::Symbol(&metadata.extrinsic.call_ty), &mut (), &metadata.types, Propagated::new(), - ) - .unwrap(); + )?; if let TypeContentToFill::Variant(ref mut pallet_selector) = call.content { let mut index_assets_in_pallets = None; @@ -61,133 +59,142 @@ pub fn construct_single_asset_transfer_call( } } - let index_assets_in_pallets = index_assets_in_pallets.unwrap(); - - *pallet_selector = VariantSelector::new_at::<(), RuntimeMetadataV15>( - &pallet_selector.available_variants, - &mut (), - &metadata.types, - index_assets_in_pallets, - ) - .unwrap(); - - 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; + 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, + )?; - for (index_method, variant_method) in - method_selector.available_variants.iter().enumerate() + 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 { - if variant_method.name.as_str() == "transfer" { - index_transfer_in_methods = Some(index_method); - break; - } - } + let mut index_transfer_in_methods = None; - let index_transfer_in_methods = index_transfer_in_methods.unwrap(); - - *method_selector = VariantSelector::new_at::<(), RuntimeMetadataV15>( - &method_selector.available_variants, - &mut (), - &metadata.types, - index_transfer_in_methods, - ) - .unwrap(); - - 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; - } - } + 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; + } + } - let index_account_id_in_dest_selector = - index_account_id_in_dest_selector.unwrap(); - - *dest_selector = - VariantSelector::new_at::<(), RuntimeMetadataV15>( - &dest_selector.available_variants, - &mut (), - &metadata.types, - index_account_id_in_dest_selector, - ) - .unwrap(); - - 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 + 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 { - *account_to_fill = Some( - asset_transfer_constructor.to_account.to_owned(), - ) + 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, + "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); + } + _ => {} }, - )) => { - *value = Some(asset_transfer_constructor.amount); - } - TypeContentToFill::Primitive(PrimitiveToFill::Unsigned( - SpecialtyUnsignedToFill { - content: UnsignedToFill::U128(ref mut value), - specialty: SpecialtyUnsignedInteger::Balance, + "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); + } + _ => {} }, - )) => { - *value = Some(asset_transfer_constructor.amount); + _ => {} } - _ => {} - }, - _ => {} + } } } } } } } - CallToFill(call) + Ok(CallToFill(call)) } pub struct BalanceTransferConstructor<'a> { @@ -207,9 +214,8 @@ pub fn construct_batch_transaction( block: BlockHash, block_number: u32, nonce: u32, -) -> TransactionToFill { - let mut transaction_to_fill = - TransactionToFill::init(&mut (), metadata, genesis_hash.0).unwrap(); +) -> Result { + let mut transaction_to_fill = TransactionToFill::init(&mut (), metadata, genesis_hash.0)?; // deal with author match transaction_to_fill.author.content { @@ -235,23 +241,23 @@ pub fn construct_batch_transaction( } } - let index_account_id = index_account_id.unwrap(); - - *variant_selector = VariantSelector::new_at::<(), RuntimeMetadataV15>( - &variant_selector.available_variants, - &mut (), - &metadata.types, - index_account_id, - ) - .unwrap(); + 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] + 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); + { + *a = Some(author); + } } } } @@ -259,7 +265,7 @@ pub fn construct_batch_transaction( } // deal with call - transaction_to_fill.call = construct_batch_call(metadata, call_set).0; + transaction_to_fill.call = construct_batch_call(metadata, call_set)?.0; // set era to mortal for ext in transaction_to_fill.extensions.iter_mut() { @@ -297,17 +303,19 @@ pub fn construct_batch_transaction( } } - transaction_to_fill + Ok(transaction_to_fill) } -pub fn construct_batch_call(metadata: &RuntimeMetadataV15, call_set: &[CallToFill]) -> CallToFill { +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(), - ) - .unwrap(); + )?; if let TypeContentToFill::Variant(ref mut pallet_selector) = call.content { let mut index_utility_in_pallets = None; @@ -320,75 +328,72 @@ pub fn construct_batch_call(metadata: &RuntimeMetadataV15, call_set: &[CallToFil } } - let index_utility_in_pallets = index_utility_in_pallets.unwrap(); - - *pallet_selector = VariantSelector::new_at::<(), RuntimeMetadataV15>( - &pallet_selector.available_variants, - &mut (), - &metadata.types, - index_utility_in_pallets, - ) - .unwrap(); - - 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; + 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, + )?; - for (index_method, variant_method) in - method_selector.available_variants.iter().enumerate() + 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 { - if variant_method.name == "batch_all" { - index_batch_all_in_methods = Some(index_method); - break; - } - } - - let index_batch_all_in_methods = index_batch_all_in_methods.unwrap(); - - *method_selector = VariantSelector::new_at::<(), RuntimeMetadataV15>( - &method_selector.available_variants, - &mut (), - &metadata.types, - index_batch_all_in_methods, - ) - .unwrap(); + let mut index_batch_all_in_methods = None; - 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 + for (index_method, variant_method) in + method_selector.available_variants.iter().enumerate() { - calls_sequence.content = call_set - .iter() - .map(|call| call.0.content.to_owned()) - .collect(); + 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(); + } + } } } } } } - CallToFill(call) + Ok(CallToFill(call)) } pub fn construct_single_balance_transfer_call( metadata: &RuntimeMetadataV15, balance_transfer_constructor: &BalanceTransferConstructor, -) -> CallToFill { +) -> Result { let mut call = prepare_type::<(), RuntimeMetadataV15>( &Ty::Symbol(&metadata.extrinsic.call_ty), &mut (), &metadata.types, Propagated::new(), - ) - .unwrap(); + )?; if let TypeContentToFill::Variant(ref mut pallet_selector) = call.content { let mut index_balances_in_pallets = None; @@ -401,138 +406,149 @@ pub fn construct_single_balance_transfer_call( } } - let index_balances_in_pallets = index_balances_in_pallets.unwrap(); - - *pallet_selector = VariantSelector::new_at::<(), RuntimeMetadataV15>( - &pallet_selector.available_variants, - &mut (), - &metadata.types, - index_balances_in_pallets, - ) - .unwrap(); - - 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; + 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, + )?; - for (index_method, variant_method) in - method_selector.available_variants.iter().enumerate() + 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 { - match variant_method.name.as_str() { - "transfer_keep_alive" => { - if !balance_transfer_constructor.is_clearing { - index_transfer_in_methods = Some(index_method) + 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) + "transfer_all" => { + if balance_transfer_constructor.is_clearing { + index_transfer_in_methods = Some(index_method) + } } + _ => {} + } + if index_transfer_in_methods.is_some() { + break; } - _ => {} - } - if index_transfer_in_methods.is_some() { - break; } - } - - let index_transfer_in_methods = index_transfer_in_methods.unwrap(); - *method_selector = VariantSelector::new_at::<(), RuntimeMetadataV15>( - &method_selector.available_variants, - &mut (), - &metadata.types, - index_transfer_in_methods, - ) - .unwrap(); - - 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_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(), + ) + } + } + } } } - - let index_account_id_in_dest_selector = - index_account_id_in_dest_selector.unwrap(); - - *dest_selector = - VariantSelector::new_at::<(), RuntimeMetadataV15>( - &dest_selector.available_variants, - &mut (), - &metadata.types, - index_account_id_in_dest_selector, - ) - .unwrap(); - - 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 + "keep_alive" => { + if let TypeContentToFill::Primitive( + PrimitiveToFill::Regular(RegularPrimitiveToFill::Bool( + ref mut keep_alive_bool, + )), + ) = field.type_to_fill.content { - *account_to_fill = Some( - balance_transfer_constructor.to_account.to_owned(), - ) + *keep_alive_bool = Some(false); } } - } - } - "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" => 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); + } + _ => {} }, - )) => { - *value = Some(balance_transfer_constructor.amount); + _ => {} } - _ => {} - }, - _ => {} + } } } } } } } - CallToFill(call) + Ok(CallToFill(call)) } -pub fn block_number_query(metadata_v15: &RuntimeMetadataV15) -> FinalizedStorageQuery { - let storage_selector = StorageSelector::init(&mut (), metadata_v15).unwrap(); +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; @@ -548,50 +564,55 @@ pub fn block_number_query(metadata_v15: &RuntimeMetadataV15) -> FinalizedStorage } } - let index_system_in_pallet_selector = index_system_in_pallet_selector.unwrap(); - - // 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, - ) - .unwrap(); + 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 let EntrySelector::Functional(ref mut entry_selector_functional) = + storage_selector_functional.query.entry_selector { - if entry.name == "Number" { - entry_index = Some(index); - break; + 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(ErrorChain::StorageQuery)??) + } else { + Err(ErrorChain::NoBlockNumberDefinition) } + } else { + Err(ErrorChain::NoStorageInSystem) } - let entry_index = entry_index.unwrap(); - *entry_selector_functional = EntrySelectorFunctional::new_at::<(), RuntimeMetadataV15>( - &entry_selector_functional.available_entries, - &mut (), - &metadata_v15.types, - entry_index, - ) - .unwrap(); - - storage_selector_functional - .query - .finalize() - .unwrap() - .unwrap() } else { - panic!("no storage variants in system pallet") + Err(ErrorChain::NoSystem) } } else { - panic!("no pallets with storage") + Err(ErrorChain::NoStorage) } } @@ -648,7 +669,7 @@ pub fn asset_balance_query( account_id: &AccountId32, asset_id: AssetId, ) -> Result { - let storage_selector = StorageSelector::init(&mut (), metadata_v15).unwrap(); + 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; @@ -666,68 +687,71 @@ pub fn asset_balance_query( break; } } - let index_assets_in_pallet_selector = index_assets_in_pallet_selector.unwrap(); - - storage_selector_functional = StorageSelectorFunctional::new_at::<(), RuntimeMetadataV15>( - &storage_selector_functional.available_pallets, - &mut (), - &metadata_v15.types, - index_assets_in_pallet_selector, - ) - .unwrap(); - 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 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 { - if entry.name == "Account" { - entry_index = Some(index); - break; + 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; + } } - } - let entry_index = entry_index.unwrap(); - *entry_selector_functional = EntrySelectorFunctional::new_at::<(), RuntimeMetadataV15>( - &entry_selector_functional.available_entries, - &mut (), - &metadata_v15.types, - entry_index, - ) - .unwrap(); - 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); + 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); + } + } + _ => {} } } - _ => {} } } } @@ -747,7 +771,7 @@ pub fn system_balance_query( metadata_v15: &RuntimeMetadataV15, account_id: &AccountId32, ) -> Result { - let storage_selector = StorageSelector::init(&mut (), metadata_v15).unwrap(); + 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 { @@ -764,49 +788,50 @@ pub fn system_balance_query( break; } } - let index_system_in_pallet_selector = index_system_in_pallet_selector.unwrap(); - - storage_selector_functional = StorageSelectorFunctional::new_at::<(), RuntimeMetadataV15>( - &storage_selector_functional.available_pallets, - &mut (), - &metadata_v15.types, - index_system_in_pallet_selector, - ) - .unwrap(); + 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; - } - } - let entry_index = entry_index.unwrap(); - *entry_selector_functional = EntrySelectorFunctional::new_at::<(), RuntimeMetadataV15>( - &entry_selector_functional.available_entries, - &mut (), - &metadata_v15.types, - entry_index, - ) - .unwrap(); - if let StorageEntryTypeToFill::Map { - hashers: _, - ref mut key_to_fill, - value: _, - } = entry_selector_functional.selected_entry.type_to_fill + if let EntrySelector::Functional(ref mut entry_selector_functional) = + storage_selector_functional.query.entry_selector { - if let TypeContentToFill::SpecialType(SpecialTypeToFill::AccountId32( - ref mut account_to_fill, - )) = key_to_fill.content + let mut entry_index = None; + for (index, entry) in entry_selector_functional + .available_entries + .iter() + .enumerate() { - *account_to_fill = Some(*account_id) + 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) + } + } } } } @@ -836,7 +861,7 @@ pub fn whole_key_u32_value( storage_name: &str, metadata_v15: &RuntimeMetadataV15, entered_data: u32, -) -> String { +) -> Result { for pallet in metadata_v15.pallets.iter() { if let Some(storage) = &pallet.storage { if storage.prefix == prefix { @@ -844,7 +869,7 @@ pub fn whole_key_u32_value( if entry.name == storage_name { match &entry.ty { StorageEntryType::Plain(_) => { - panic!("expected map with single entry, got plain") + return Err(ErrorChain::StorageEntryNotMap) } StorageEntryType::Map { hashers, @@ -853,9 +878,13 @@ pub fn whole_key_u32_value( } => { if hashers.len() == 1 { let hasher = &hashers[0]; - match metadata_v15.types.resolve(key_ty.id).unwrap().type_def { + match metadata_v15 + .types + .resolve_ty(key_ty.id, &mut ())? + .type_def + { TypeDef::Primitive(TypeDefPrimitive::U32) => { - return format!( + return Ok(format!( "0x{}{}{}", hex::encode(twox_128(prefix.as_bytes())), hex::encode(twox_128(storage_name.as_bytes())), @@ -863,22 +892,22 @@ pub fn whole_key_u32_value( &entered_data.encode(), hasher )) - ) + )) } - _ => panic!("wrong data type"), + _ => return Err(ErrorChain::StorageKeyNotU32), } } else { - panic!("expected map with single entry, got multiple entries") + return Err(ErrorChain::StorageEntryMapMultiple); } } } } } - panic!("have not found entry with proper name"); + return Err(ErrorChain::StorageKeyNotFound(storage_name.to_string())); } } } - panic!("have not found pallet"); + Err(ErrorChain::NoPallet) } pub fn decimals(x: &Map) -> Result { diff --git a/src/error.rs b/src/error.rs index 9fbe063..ff019f0 100644 --- a/src/error.rs +++ b/src/error.rs @@ -96,6 +96,10 @@ impl From for Error { #[derive(Debug, thiserror::Error)] pub enum ErrorChain { + // TODO: this should be prevented by typesafety + #[error("Asset id is missing")] + AssetId, + #[error("Asset id is not u32")] AssetIdFormat, @@ -159,6 +163,9 @@ pub enum ErrorChain { #[error("No base58 prefix is fetched as system properties or found in the metadata.")] NoBase58Prefix, + #[error("Block number definition not found")] + NoBlockNumberDefinition, + #[error("No decimals value is fetched.")] NoDecimals, @@ -168,6 +175,18 @@ pub enum ErrorChain { #[error("Metadata must start with `meta` prefix.")] NoMetaPrefix, + #[error("Pallet not found")] + NoPallet, + + #[error("No pallets with storage found")] + NoStorage, + + #[error("Pallet System not found")] + NoSystem, + + #[error("No storage variants in system pallet")] + NoStorageInSystem, + #[error("No unit value is fetched.")] NoUnit, @@ -194,7 +213,6 @@ pub enum ErrorChain { //#[error("Aura slot duration could not be parsed as u64")] //AuraSlotDurationFormat, - #[error("Internal error: {0:?}")] // TODO this should be replaced by specific errors ErrorUtil(ErrorUtil), @@ -225,9 +243,6 @@ pub enum ErrorChain { #[error("Storage query could not be formed")] StorageQuery, - //#[error("Storage format error")] - //StorageFormatError, - #[error("Events could not be fetched")] EventsMissing, @@ -242,6 +257,30 @@ pub enum ErrorChain { #[error("Type registry error: {0}")] RegistryError(RegistryError<()>), + + #[error("Substrate constructor error: {0:?}")] + SubstrateConstructor(ErrorFixMe<(), RuntimeMetadataV15>), + + #[error("Transaction is not ready to be signed: {0}")] + TransactionNotSignable(String), + + #[error("Signing failed: {0}")] + Signer(ErrorSigner), + + #[error("Transaction could not be completed")] + NothingToSend, + + #[error("Storage entry is not a map")] + StorageEntryNotMap, + + #[error("Storage entry map has more than one records")] + StorageEntryMapMultiple, + + #[error("Storage key {0} not found")] + StorageKeyNotFound(String), + + #[error("Storage key is not u32")] + StorageKeyNotU32, } impl From for ErrorChain { @@ -250,6 +289,12 @@ impl From for ErrorChain { } } +impl From for ErrorChain { + fn from(e: ErrorSigner) -> Self { + ErrorChain::Signer(e) + } +} + impl From for ErrorChain { fn from(e: JoinError) -> Self { ErrorChain::Tokio(e) @@ -292,6 +337,12 @@ impl From> for ErrorChain { } } +impl From> for ErrorChain { + fn from(e: ErrorFixMe<(), RuntimeMetadataV15>) -> Self { + ErrorChain::SubstrateConstructor(e) + } +} + #[derive(Debug, thiserror::Error)] pub enum ErrorDb { #[error("Currency key is not found")] diff --git a/src/state.rs b/src/state.rs index f04dc6b..3cce24b 100644 --- a/src/state.rs +++ b/src/state.rs @@ -81,7 +81,9 @@ impl State { 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).await; + chain_manager_wakeup + .add_invoice(order, order_details, state.recipient) + .await; } Ok("All saved orders restored".into()) }); @@ -275,7 +277,7 @@ impl StateData { { OrderCreateResponse::New => { self.chain_manager - .add_invoice(order.clone(), order_info.clone()) + .add_invoice(order.clone(), order_info.clone(), self.recipient) .await?; Ok(OrderResponse::NewOrder(self.order_status( order, From c6fcb845c90a44ab1bfc473c89438e6dda4498b8 Mon Sep 17 00:00:00 2001 From: Vova Lando Date: Fri, 17 May 2024 10:37:47 +0300 Subject: [PATCH 45/76] basic test setup and fixes --- .gitignore | 6 +- Cargo.lock | 440 ++++++++++++++++++++++++++++++++++++- Cargo.toml | 1 + configs/chopsticks.toml | 18 ++ src/chain/rpc.rs | 144 +++++++----- src/main.rs | 2 +- start-ci.sh | 9 + start.sh | 1 - tests/integration_tests.rs | 56 +++++ 9 files changed, 604 insertions(+), 73 deletions(-) create mode 100644 configs/chopsticks.toml create mode 100755 start-ci.sh create mode 100644 tests/integration_tests.rs diff --git a/.gitignore b/.gitignore index 0e6ca42..f5f61bb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,6 @@ target -kalatori.db +*.db + +# IDE +.vscode +.idea \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 3f9c019..75561ad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -108,9 +108,9 @@ dependencies = [ "bytes", "futures-util", "http 1.1.0", - "http-body", + "http-body 1.0.0", "http-body-util", - "hyper", + "hyper 1.2.0", "hyper-util", "itoa", "matchit", @@ -140,7 +140,7 @@ dependencies = [ "bytes", "futures-util", "http 1.1.0", - "http-body", + "http-body 1.0.0", "http-body-util", "mime", "pin-project-lite", @@ -228,6 +228,12 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" + [[package]] name = "bitvec" version = "1.0.1" @@ -279,6 +285,12 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + [[package]] name = "byte-slice-cast" version = "1.2.2" @@ -552,18 +564,43 @@ dependencies = [ "zeroize", ] +[[package]] +name = "encoding_rs" +version = "0.8.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" +dependencies = [ + "cfg-if", +] + [[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.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + [[package]] 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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" + [[package]] name = "fdeflate" version = "0.3.4" @@ -626,6 +663,21 @@ 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 = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "form_urlencoded" version = "1.2.1" @@ -803,6 +855,25 @@ dependencies = [ "subtle", ] +[[package]] +name = "h2" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.12", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "hashbrown" version = "0.14.3" @@ -862,6 +933,17 @@ dependencies = [ "itoa", ] +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http 0.2.12", + "pin-project-lite", +] + [[package]] name = "http-body" version = "1.0.0" @@ -881,7 +963,7 @@ dependencies = [ "bytes", "futures-core", "http 1.1.0", - "http-body", + "http-body 1.0.0", "pin-project-lite", ] @@ -897,6 +979,30 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +[[package]] +name = "hyper" +version = "0.14.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http 0.2.12", + "http-body 0.4.6", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + [[package]] name = "hyper" version = "1.2.0" @@ -907,7 +1013,7 @@ dependencies = [ "futures-channel", "futures-util", "http 1.1.0", - "http-body", + "http-body 1.0.0", "httparse", "httpdate", "itoa", @@ -916,6 +1022,19 @@ dependencies = [ "tokio", ] +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper 0.14.28", + "native-tls", + "tokio", + "tokio-native-tls", +] + [[package]] name = "hyper-util" version = "0.1.3" @@ -925,8 +1044,8 @@ dependencies = [ "bytes", "futures-util", "http 1.1.0", - "http-body", - "hyper", + "http-body 1.0.0", + "hyper 1.2.0", "pin-project-lite", "socket2", "tokio", @@ -1003,12 +1122,27 @@ dependencies = [ "num-traits", ] +[[package]] +name = "ipnet" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" + [[package]] name = "itoa" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +[[package]] +name = "js-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +dependencies = [ + "wasm-bindgen", +] + [[package]] name = "jsonrpsee" version = "0.22.4" @@ -1114,6 +1248,7 @@ dependencies = [ "names", "parity-scale-codec", "primitive-types", + "reqwest", "scale-info", "serde", "serde_json", @@ -1156,6 +1291,12 @@ version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +[[package]] +name = "linux-raw-sys" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" + [[package]] name = "lock_api" version = "0.4.11" @@ -1250,6 +1391,24 @@ dependencies = [ "rand", ] +[[package]] +name = "native-tls" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "nodrop" version = "0.1.14" @@ -1332,12 +1491,50 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" +[[package]] +name = "openssl" +version = "0.10.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" +dependencies = [ + "bitflags 2.5.0", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + [[package]] 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.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "overload" version = "0.1.1" @@ -1498,6 +1695,12 @@ dependencies = [ "spki", ] +[[package]] +name = "pkg-config" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" + [[package]] name = "platforms" version = "3.4.0" @@ -1522,7 +1725,7 @@ version = "0.17.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06e4b0d3d1312775e782c86c91a111aa1f910cbb65e1337f9975b5f9a554b5e1" dependencies = [ - "bitflags", + "bitflags 1.3.2", "crc32fast", "fdeflate", "flate2", @@ -1631,7 +1834,7 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] @@ -1640,7 +1843,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] @@ -1687,6 +1890,46 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" +[[package]] +name = "reqwest" +version = "0.11.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" +dependencies = [ + "base64 0.21.7", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.28", + "hyper-tls", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls-pemfile 1.0.4", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper 0.1.2", + "system-configuration", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", +] + [[package]] name = "rfc6979" version = "0.4.0" @@ -1739,6 +1982,19 @@ dependencies = [ "semver", ] +[[package]] +name = "rustix" +version = "0.38.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +dependencies = [ + "bitflags 2.5.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + [[package]] name = "rustls" version = "0.22.3" @@ -1760,12 +2016,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f1fb85efa936c42c6d5fc28d2629bb51e4b2f4b8a5211e297d599cc5a093792" dependencies = [ "openssl-probe", - "rustls-pemfile", + "rustls-pemfile 2.1.2", "rustls-pki-types", "schannel", "security-framework", ] +[[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.2" @@ -1883,7 +2148,7 @@ version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "770452e37cad93e0a50d5abc3990d2bc351c36d0328f86cefec2f2fb206eaef6" dependencies = [ - "bitflags", + "bitflags 1.3.2", "core-foundation", "core-foundation-sys", "libc", @@ -2252,12 +2517,45 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "tap" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" +[[package]] +name = "tempfile" +version = "3.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +dependencies = [ + "cfg-if", + "fastrand", + "rustix", + "windows-sys 0.52.0", +] + [[package]] name = "thiserror" version = "1.0.58" @@ -2364,6 +2662,16 @@ dependencies = [ "syn 2.0.58", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + [[package]] name = "tokio-rustls" version = "0.25.0" @@ -2400,6 +2708,7 @@ dependencies = [ "hashbrown", "pin-project-lite", "tokio", + "tracing", ] [[package]] @@ -2543,6 +2852,12 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + [[package]] name = "twox-hash" version = "1.6.3" @@ -2625,18 +2940,109 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasm-bindgen" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.58", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", + "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 = "web-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "winapi" version = "0.3.9" @@ -2809,6 +3215,16 @@ dependencies = [ "memchr", ] +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + [[package]] name = "wyz" version = "0.5.1" diff --git a/Cargo.toml b/Cargo.toml index c623fe7..ef3c9ff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ names = { version = "0.14", default-features = false } tokio-util = { version = "0.7", features = ["rt"] } tokio = { version = "1", features = ["full"] } +reqwest = { version = "0.11", features = ["json"] } serde = { version = "1", features = ["derive"] } tracing = "0.1" diff --git a/configs/chopsticks.toml b/configs/chopsticks.toml new file mode 100644 index 0000000..d0fe099 --- /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 = "chop-assethub-polkadot" +endpoints = [ + "ws://localhost:8000" +] + +[[chain.asset]] +name = "USDC" +id = 1337 + +[[chain.asset]] +name = "USDT" +id = 1984 diff --git a/src/chain/rpc.rs b/src/chain/rpc.rs index 387a2db..f97a7b7 100644 --- a/src/chain/rpc.rs +++ b/src/chain/rpc.rs @@ -2,19 +2,23 @@ use crate::{ chain::{ - definitions::{BlockHash, EventFilter}, + definitions::{BlockHash, EventFilter, WatchAccount}, utils::{ - asset_balance_query, block_number_query, events_entry_metadata, hashed_key_element, - system_balance_query, system_properties_to_short_specs, + asset_balance_query, base58prefix, block_number_query, events_entry_metadata, + hashed_key_element, pallet_index, storage_key, system_balance_query, + system_properties_to_short_specs, unit, was_balance_received_at_account, }, }, - definitions::api_v2::CurrencyProperties, + definitions::api_v2::{CurrencyProperties, OrderInfo}, definitions::{ - api_v2::{AssetId, TokenKind}, - Balance, + api_v2::{AssetId, BlockNumber, CurrencyInfo, Decimals, TokenKind}, + AssetInfo, Balance, Chain, NativeToken, Nonce, PalletIndex, Timestamp, }, - error::{ErrorChain, NotHex}, + error::{Error, ErrorChain, NotHex}, + signer::Signer, + state::State, utils::unhex, + TaskTracker, }; use frame_metadata::{ v15::{RuntimeMetadataV15, StorageEntryMetadata, StorageEntryType}, @@ -22,20 +26,25 @@ use frame_metadata::{ }; use jsonrpsee::core::client::{ClientT, Subscription, SubscriptionClientT}; use jsonrpsee::rpc_params; -use jsonrpsee::ws_client::WsClient; -use parity_scale_codec::{DecodeAll, Encode}; +use jsonrpsee::ws_client::{WsClient, WsClientBuilder}; +use parity_scale_codec::{Decode, 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 serde::{Deserialize, Deserializer}; +use serde_json::{Map, Number, Value}; +use sp_crypto_hashing::{blake2_128, blake2_256, twox_128, twox_256, twox_64}; +use std::{ + borrow::Cow, + collections::{hash_map::Entry, HashMap}, + fmt::Debug, + num::NonZeroU64, +}; +use substrate_crypto_light::common::{AccountId32, AsBase58}; use substrate_parser::{ - cards::{Event, ParsedData, Sequence}, + cards::{Event, ExtendedData, FieldData, ParsedData, Sequence}, decode_all_as_type, decode_as_storage_entry, special_indicators::SpecialtyUnsignedInteger, storage_data::{KeyData, KeyPart}, - AsMetadata, ResolveType, ShortSpecs, + AsMetadata, ShortSpecs, }; const MAX_BLOCK_NUMBER_ERROR: &str = "block number type overflow is occurred"; @@ -86,19 +95,34 @@ pub async fn get_keys_from_storage( storage_name: &str, block: &BlockHash, ) -> Result { + let storage_key_prefix = format!( + "0x{}{}", + hex::encode(twox_128(prefix.as_bytes())), + hex::encode(twox_128(storage_name.as_bytes())) + ); + + let count = 100; // I expect it to always work, but will it actually? + let start_key: Option<&str> = None; // Start from the beginning + + let mut params = vec![ + serde_json::to_value(storage_key_prefix.clone()).unwrap(), + serde_json::to_value(count).unwrap(), + ]; + + if let Some(start_key) = start_key { + params.push(serde_json::to_value(start_key).unwrap()); + } + + params.push(serde_json::to_value(block.to_string()).unwrap()); + let keys: Value = client .request( - "state_getKeys", - rpc_params![ - format!( - "0x{}{}", - hex::encode(twox_128(prefix.as_bytes())), - hex::encode(twox_128(storage_name.as_bytes())) - ), - block.to_string() - ], + "state_getKeysPaged", + params ) - .await?; + .await + .map_err(ErrorChain::Client)?; + Ok(keys) } @@ -144,13 +168,14 @@ pub async fn metadata( client: &WsClient, block: &BlockHash, ) -> Result { + let block_str = format!("0x{}", block.to_string()); let metadata_request: Value = client .request( "state_call", rpc_params![ "Metadata_metadata_at_version", "0x0f000000", - &block.to_string() + &block_str ], ) .await @@ -283,8 +308,9 @@ pub async fn assets_set_at_block( 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 key_data = hex::decode(string_key.trim_start_matches("0x")).unwrap(); + let value_data = + hex::decode(string_value.trim_start_matches("0x")).unwrap(); let storage_entry = decode_as_storage_entry::<&[u8], (), RuntimeMetadataV15>( &key_data.as_ref(), &value_data.as_ref(), @@ -340,7 +366,8 @@ pub async fn assets_set_at_block( let hasher = &hashers[0]; match metadata_v15 .types - .resolve_ty(key_ty.id, &mut ())? + .resolve(key_ty.id) + .unwrap() .type_def { TypeDef::Primitive(TypeDefPrimitive::U32) => { @@ -445,7 +472,7 @@ pub async fn assets_set_at_block( } }, _ => {}, - }, + }, "decimals" => { if let ParsedData::PrimitiveU8{value, specialty: _} = field_data.data.data { decimals = Some(value); @@ -461,21 +488,20 @@ pub async fn assets_set_at_block( 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, - }, - ); - } + //let name = name.unwrap(); + let symbol = symbol.unwrap(); + let decimals = decimals.unwrap(); + 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( ErrorChain::AssetMetadataUnexpected, @@ -528,12 +554,12 @@ pub async fn asset_balance_at_account( return Ok(Balance(value)); } } - Err(ErrorChain::AssetBalanceNotFound) + panic!(); } else { - Err(ErrorChain::AssetBalanceFormat) + panic!() } } else { - Err(ErrorChain::StorageValueFormat(value_fetch)) + panic!() } } @@ -547,7 +573,7 @@ pub async fn system_balance_at_account( 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_data = hex::decode(string_value.trim_start_matches("0x")).unwrap(); let value = decode_all_as_type::<&[u8], (), RuntimeMetadataV15>( &query.value_ty, &value_data.as_ref(), @@ -615,7 +641,7 @@ pub async fn events_at_block( let value_bytes = if let Value::String(data_from_storage) = data_from_storage { unhex(&data_from_storage, NotHex::StorageValue)? } else { - return Err(ErrorChain::StorageValueFormat(data_from_storage)); + return Err(ErrorChain::StorageFormatError); }; let storage_data = decode_as_storage_entry::<&[u8], (), RuntimeMetadataV15>( &key_bytes.as_ref(), @@ -666,16 +692,18 @@ pub async fn current_block_number( 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 block_number_query = block_number_query(metadata); + if let Value::String(hex_data) = + get_value_from_storage(client, &block_number_query.key, block).await? + { + let value_data = hex::decode(hex_data.trim_start_matches("0x")).unwrap(); let value = decode_all_as_type::<&[u8], (), RuntimeMetadataV15>( &block_number_query.value_ty, &value_data.as_ref(), &mut (), &metadata.types, - )?; + ) + .unwrap(); if let ParsedData::PrimitiveU32 { value, specialty: _, @@ -686,7 +714,7 @@ pub async fn current_block_number( Err(ErrorChain::BlockNumberFormat) } } else { - Err(ErrorChain::StorageValueFormat(fetched_value)) + Err(ErrorChain::StorageFormatError) } } @@ -706,7 +734,7 @@ pub async fn send_stuff(client: &WsClient, data: &str) -> Result<(), ErrorChain> let mut subscription: Subscription = client .subscribe("author_submitAndWatchExtrinsic", rpc_params, "") .await?; - let _reply = subscription.next().await.unwrap(); + let reply = subscription.next().await.unwrap(); //println!("{reply:?}"); // TODO! Ok(()) } diff --git a/src/main.rs b/src/main.rs index 1816fc2..cdb33d5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -208,7 +208,7 @@ fn set_panic_hook(shutdown_notification: CancellationToken) { }; tracing::error!( - "A panic detected{at}{formatted_message}\nThis is a bug. Please report it at {}.", + "A panic detected{at}{formatted_message}\nThis is a bug. Please report it at {}/issues.", env!("CARGO_PKG_REPOSITORY") ); diff --git a/start-ci.sh b/start-ci.sh new file mode 100755 index 0000000..bf2f5fe --- /dev/null +++ b/start-ci.sh @@ -0,0 +1,9 @@ +KALATORI_CONFIG="configs/chopsticks.toml" \ +KALATORI_HOST="127.0.0.1:16726" \ +KALATORI_SEED="bottom drive obey lake curtain smoke basket hold race lonely fit walk" \ +KALATORI_DATABASE="database-ah-usdc.redb" \ +KALATORI_RPC="ws://localhost:8000" \ +KALATORI_RECIPIENT="5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY" \ +KALATORI_USD_ASSET="USDC" \ +KALATORI_REMARK="test" \ +cargo r diff --git a/start.sh b/start.sh index 26ee7e9..3f0b68e 100755 --- a/start.sh +++ b/start.sh @@ -6,4 +6,3 @@ KALATORI_RECIPIENT="5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY" \ KALATORI_USD_ASSET="USDC" \ KALATORI_REMARK="test" \ cargo r - diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs new file mode 100644 index 0000000..8b19ab7 --- /dev/null +++ b/tests/integration_tests.rs @@ -0,0 +1,56 @@ +use std::process::{Command, Child}; +use tokio::time::{sleep, Duration}; +use reqwest::Client; +use serde_json::json; +use std::env; +use std::io::Result; + +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", "wss://westend-rpc.polkadot.io"); + env::set_var("KALATORI_DECIMALS", "12"); + env::set_var("KALATORI_RECIPIENT", "5DfhGyQdFobKM8NsWvEeAKk5EQQgYe9AydgJ7rMB6E1EqRzV"); + env::set_var("KALATORI_REMARK", "KALATORI_REMARK"); + // env::set_var("RUST_BACKTRACE", "1"); +} + +async fn start_daemon() -> Result { + let daemon = Command::new("target/debug/kalatori") + .spawn()?; + // Give the daemon some time to start + sleep(Duration::from_secs(5)).await; + Ok(daemon) +} + +async fn stop_daemon(daemon: &mut Child) -> Result<()> { + daemon.kill()?; + daemon.wait()?; + Ok(()) +} + +#[tokio::test] +async fn test_daemon_health_check() { + // Load chain configuration + load_chain_config(); + + // Start the daemon + let mut daemon = start_daemon().await.expect("Failed to start kalatori daemon"); + + // Create an HTTP client + let client = Client::new(); + + // Send a health check request + 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()); + + // Shutdown the daemon + stop_daemon(&mut daemon).await.expect("Failed to kill kalatori daemon"); +} From 0f540b52887707b64eac9fe257ba3c8fe1149e29 Mon Sep 17 00:00:00 2001 From: Vova Lando Date: Fri, 17 May 2024 11:02:04 +0300 Subject: [PATCH 46/76] compiler errors and merge fixes --- src/chain/rpc.rs | 9 ++++----- src/error.rs | 3 +++ 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/chain/rpc.rs b/src/chain/rpc.rs index f97a7b7..67639c9 100644 --- a/src/chain/rpc.rs +++ b/src/chain/rpc.rs @@ -692,11 +692,10 @@ pub async fn current_block_number( metadata: &RuntimeMetadataV15, block: &BlockHash, ) -> Result { - let block_number_query = block_number_query(metadata); - if let Value::String(hex_data) = - get_value_from_storage(client, &block_number_query.key, block).await? - { - let value_data = hex::decode(hex_data.trim_start_matches("0x")).unwrap(); + 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(), diff --git a/src/error.rs b/src/error.rs index ff019f0..0fda36c 100644 --- a/src/error.rs +++ b/src/error.rs @@ -281,6 +281,9 @@ pub enum ErrorChain { #[error("Storage key is not u32")] StorageKeyNotU32, + + #[error("Storage value unsupported format error")] + StorageFormatError, } impl From for ErrorChain { From dbb87ff422c130114ebda8d2befc3a4d9c37f92b Mon Sep 17 00:00:00 2001 From: Vova Lando Date: Fri, 17 May 2024 14:35:14 +0300 Subject: [PATCH 47/76] moved chopsticks run to run with the test suite in preparation for the ci --- Cargo.lock | 1 + Cargo.toml | 1 + start-ci.sh | 9 ---- tests/integration_tests.rs | 99 ++++++++++++++++++++++++++++++++------ 4 files changed, 85 insertions(+), 25 deletions(-) delete mode 100755 start-ci.sh diff --git a/Cargo.lock b/Cargo.lock index 75561ad..8ccaee6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1244,6 +1244,7 @@ dependencies = [ "frame-metadata", "hex", "jsonrpsee", + "lazy_static", "mnemonic-external", "names", "parity-scale-codec", diff --git a/Cargo.toml b/Cargo.toml index ef3c9ff..50c0de4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,6 +40,7 @@ sp-crypto-hashing = "0.1.0" toml = "0.8.12" sled = "0.34.7" zeroize = "1.7.0" +lazy_static = "1.4.0" [profile.release] strip = true diff --git a/start-ci.sh b/start-ci.sh deleted file mode 100755 index bf2f5fe..0000000 --- a/start-ci.sh +++ /dev/null @@ -1,9 +0,0 @@ -KALATORI_CONFIG="configs/chopsticks.toml" \ -KALATORI_HOST="127.0.0.1:16726" \ -KALATORI_SEED="bottom drive obey lake curtain smoke basket hold race lonely fit walk" \ -KALATORI_DATABASE="database-ah-usdc.redb" \ -KALATORI_RPC="ws://localhost:8000" \ -KALATORI_RECIPIENT="5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY" \ -KALATORI_USD_ASSET="USDC" \ -KALATORI_REMARK="test" \ -cargo r diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 8b19ab7..958e92f 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -1,47 +1,95 @@ +// if running locally, ensure that you have no dangling processes (kalatori daemon, chopsticks) +// pkill -f kalatori; pkill -f chopsticks + use std::process::{Command, Child}; use tokio::time::{sleep, Duration}; use reqwest::Client; -use serde_json::json; use std::env; -use std::io::Result; +use std::sync::{Once, Mutex}; +use lazy_static::lazy_static; + +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(3)).await; // Give Chopsticks some time to start + Ok(chopsticks) +} + +async fn stop_chopsticks(chopsticks: &mut Child) -> std::io::Result<()> { + chopsticks.kill()?; + chopsticks.wait()?; + Ok(()) +} 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", "wss://westend-rpc.polkadot.io"); + 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"); // env::set_var("RUST_BACKTRACE", "1"); } -async fn start_daemon() -> Result { +async fn start_daemon() -> std::io::Result { let daemon = Command::new("target/debug/kalatori") .spawn()?; - // Give the daemon some time to start - sleep(Duration::from_secs(5)).await; + sleep(Duration::from_secs(3)).await; // Give the daemon some time to start Ok(daemon) } -async fn stop_daemon(daemon: &mut Child) -> Result<()> { +async fn stop_daemon(daemon: &mut Child) -> std::io::Result<()> { daemon.kill()?; daemon.wait()?; Ok(()) } -#[tokio::test] -async fn test_daemon_health_check() { - // Load chain configuration - load_chain_config(); +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; - // Start the daemon - let mut daemon = start_daemon().await.expect("Failed to start kalatori daemon"); + // Load chain config and 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; - // Create an HTTP client let client = Client::new(); - // Send a health check request let resp = client .get("http://127.0.0.1:16726/v2/status") .send() @@ -52,5 +100,24 @@ async fn test_daemon_health_check() { assert!(resp.status().is_success()); // Shutdown the daemon - stop_daemon(&mut daemon).await.expect("Failed to kill kalatori daemon"); + 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()); + + // Shutdown the daemon + context.drop_async().await; +} \ No newline at end of file From abb9688a124aa4832cfa6c2a071498bd96191693 Mon Sep 17 00:00:00 2001 From: Slesarev Date: Sat, 18 May 2024 01:05:14 +0300 Subject: [PATCH 48/76] fix: always prefix block with 0x --- src/chain/definitions.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/chain/definitions.rs b/src/chain/definitions.rs index b8c868c..c5831b7 100644 --- a/src/chain/definitions.rs +++ b/src/chain/definitions.rs @@ -22,7 +22,7 @@ pub struct BlockHash(pub primitive_types::H256); impl BlockHash { /// Convert block hash to RPC-friendly format pub fn to_string(&self) -> String { - hex::encode(&self.0) + format!("0x{}", hex::encode(&self.0)) } /// Convert string returned by RPC to typesafe block From 7a074b67c6b786ba99f1d7ec2f94d0fd9ec9fb42 Mon Sep 17 00:00:00 2001 From: Slesarev Date: Sat, 18 May 2024 09:21:35 +0300 Subject: [PATCH 49/76] feat: reconnect on new runtime version --- src/chain/rpc.rs | 15 ++++++++++++++- src/chain/tracker.rs | 13 +++++++++++-- src/chain/utils.rs | 1 + 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/chain/rpc.rs b/src/chain/rpc.rs index 387a2db..32162df 100644 --- a/src/chain/rpc.rs +++ b/src/chain/rpc.rs @@ -56,6 +56,20 @@ const TRANSFER: &str = "Transfer"; 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, ErrorChain> { Ok(client .subscribe( @@ -189,7 +203,6 @@ pub async fn specs( let specs_request: Value = client .request("system_properties", rpc_params![block.to_string()]) .await?; - //.map_err(ErrorChain::Client)?; match specs_request { Value::Object(properties) => system_properties_to_short_specs(&properties, &metadata), _ => return Err(ErrorChain::PropertiesFormat), diff --git a/src/chain/tracker.rs b/src/chain/tracker.rs index 4e62f58..da21034 100644 --- a/src/chain/tracker.rs +++ b/src/chain/tracker.rs @@ -4,6 +4,7 @@ use std::collections::HashMap; use frame_metadata::v15::RuntimeMetadataV15; use jsonrpsee::ws_client::{WsClient, WsClientBuilder}; +use serde_json::Value; use substrate_parser::ShortSpecs; use tokio::{ sync::mpsc, @@ -17,7 +18,7 @@ use crate::{ payout::payout, rpc::{ assets_set_at_block, block_hash, events_at_block, genesis_hash, metadata, next_block, - next_block_number, specs, subscribe_blocks, + next_block_number, runtime_version_identifier, specs, subscribe_blocks, }, utils::{events_entry_metadata, was_balance_received_at_account}, }, @@ -44,6 +45,7 @@ pub fn start_chain_watch( let watchdog = 30000; let mut watched_accounts = HashMap::new(); let mut shutdown = false; + // TODO: random pick instead for endpoint in c.endpoints.iter().cycle() { // not restarting chain if shutdown is in progress if shutdown || cancellation_token.is_cancelled() { @@ -81,10 +83,14 @@ pub fn start_chain_watch( c.name, e ); - continue; + break; }, }; // TODO: continue and reconnect if spec_version changed + if watcher.version != runtime_version_identifier(&client, &block).await? { + tracing::info!("Different runtime version reported! Restarting connection..."); + break; + } let events = events_at_block( &client, &block, @@ -152,6 +158,7 @@ pub struct ChainWatcher { pub metadata: RuntimeMetadataV15, pub specs: ShortSpecs, pub assets: HashMap, + version: Value, } impl ChainWatcher { @@ -166,6 +173,7 @@ impl ChainWatcher { 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 specs = specs(&client, &metadata, &block).await?; let assets = @@ -176,6 +184,7 @@ impl ChainWatcher { metadata, specs, assets, + version, }; // check monitored accounts diff --git a/src/chain/utils.rs b/src/chain/utils.rs index 3ee0315..2b29bac 100644 --- a/src/chain/utils.rs +++ b/src/chain/utils.rs @@ -1215,3 +1215,4 @@ pub fn unit(x: &Map) -> Result { None => Err(ErrorChain::NoUnit), } } + From 47e0e76ac10415a7d765847f92248447232f3d0c Mon Sep 17 00:00:00 2001 From: Vova Lando Date: Sat, 18 May 2024 15:41:09 +0300 Subject: [PATCH 50/76] merge fixes Co-authored-by: Slesarew <33295157+Slesarew@users.noreply.github.com> --- Cargo.toml | 6 ++- src/chain/rpc.rs | 97 +++++++++++++++++++++--------------------------- src/error.rs | 3 -- 3 files changed, 47 insertions(+), 59 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 50c0de4..1ed6e34 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,10 @@ readme = true keywords = ["substrate", "blockchain", "finance", "service", "middleware"] categories = ["finance"] +[dev-dependencies] +reqwest = { version = "0.12.4", features = ["json"] } +lazy_static = "1.4.0" + [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"] } @@ -19,7 +23,6 @@ names = { version = "0.14", default-features = false } tokio-util = { version = "0.7", features = ["rt"] } tokio = { version = "1", features = ["full"] } -reqwest = { version = "0.11", features = ["json"] } serde = { version = "1", features = ["derive"] } tracing = "0.1" @@ -40,7 +43,6 @@ sp-crypto-hashing = "0.1.0" toml = "0.8.12" sled = "0.34.7" zeroize = "1.7.0" -lazy_static = "1.4.0" [profile.release] strip = true diff --git a/src/chain/rpc.rs b/src/chain/rpc.rs index 67639c9..a2ce87f 100644 --- a/src/chain/rpc.rs +++ b/src/chain/rpc.rs @@ -2,23 +2,19 @@ use crate::{ chain::{ - definitions::{BlockHash, EventFilter, WatchAccount}, + definitions::{BlockHash, EventFilter}, utils::{ - asset_balance_query, base58prefix, block_number_query, events_entry_metadata, - hashed_key_element, pallet_index, storage_key, system_balance_query, - system_properties_to_short_specs, unit, was_balance_received_at_account, + asset_balance_query, block_number_query, events_entry_metadata, hashed_key_element, + system_balance_query, system_properties_to_short_specs, }, }, - definitions::api_v2::{CurrencyProperties, OrderInfo}, + definitions::api_v2::CurrencyProperties, definitions::{ - api_v2::{AssetId, BlockNumber, CurrencyInfo, Decimals, TokenKind}, - AssetInfo, Balance, Chain, NativeToken, Nonce, PalletIndex, Timestamp, + api_v2::{AssetId, TokenKind}, + Balance, }, - error::{Error, ErrorChain, NotHex}, - signer::Signer, - state::State, + error::{ErrorChain, NotHex}, utils::unhex, - TaskTracker, }; use frame_metadata::{ v15::{RuntimeMetadataV15, StorageEntryMetadata, StorageEntryType}, @@ -26,25 +22,20 @@ use frame_metadata::{ }; use jsonrpsee::core::client::{ClientT, Subscription, SubscriptionClientT}; use jsonrpsee::rpc_params; -use jsonrpsee::ws_client::{WsClient, WsClientBuilder}; -use parity_scale_codec::{Decode, DecodeAll, Encode}; +use jsonrpsee::ws_client::WsClient; +use parity_scale_codec::{DecodeAll, Encode}; use scale_info::{form::PortableForm, PortableRegistry, TypeDef, TypeDefPrimitive}; -use serde::{Deserialize, Deserializer}; -use serde_json::{Map, Number, Value}; -use sp_crypto_hashing::{blake2_128, blake2_256, twox_128, twox_256, twox_64}; -use std::{ - borrow::Cow, - collections::{hash_map::Entry, HashMap}, - fmt::Debug, - num::NonZeroU64, -}; -use substrate_crypto_light::common::{AccountId32, AsBase58}; +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, ExtendedData, FieldData, ParsedData, Sequence}, + cards::{Event, ParsedData, Sequence}, decode_all_as_type, decode_as_storage_entry, special_indicators::SpecialtyUnsignedInteger, storage_data::{KeyData, KeyPart}, - AsMetadata, ShortSpecs, + AsMetadata, ResolveType, ShortSpecs, }; const MAX_BLOCK_NUMBER_ERROR: &str = "block number type overflow is occurred"; @@ -101,7 +92,7 @@ pub async fn get_keys_from_storage( hex::encode(twox_128(storage_name.as_bytes())) ); - let count = 100; // I expect it to always work, but will it actually? + let count = 100; // TODO make full scan just in case let start_key: Option<&str> = None; // Start from the beginning let mut params = vec![ @@ -308,9 +299,8 @@ pub async fn assets_set_at_block( 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 = hex::decode(string_key.trim_start_matches("0x")).unwrap(); - let value_data = - hex::decode(string_value.trim_start_matches("0x")).unwrap(); + 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(), @@ -366,8 +356,7 @@ pub async fn assets_set_at_block( let hasher = &hashers[0]; match metadata_v15 .types - .resolve(key_ty.id) - .unwrap() + .resolve_ty(key_ty.id, &mut ())? .type_def { TypeDef::Primitive(TypeDefPrimitive::U32) => { @@ -488,20 +477,21 @@ pub async fn assets_set_at_block( break; } } - //let name = name.unwrap(); - let symbol = symbol.unwrap(); - let decimals = decimals.unwrap(); - 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, - }, - ); + 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( ErrorChain::AssetMetadataUnexpected, @@ -554,12 +544,12 @@ pub async fn asset_balance_at_account( return Ok(Balance(value)); } } - panic!(); + Err(ErrorChain::AssetBalanceNotFound) } else { - panic!() + Err(ErrorChain::AssetBalanceFormat) } } else { - panic!() + Err(ErrorChain::StorageValueFormat(value_fetch)) } } @@ -573,7 +563,7 @@ pub async fn system_balance_at_account( let value_fetch = get_value_from_storage(client, &query.key, block).await?; if let Value::String(ref string_value) = value_fetch { - let value_data = hex::decode(string_value.trim_start_matches("0x")).unwrap(); + let value_data = unhex(string_value, NotHex::StorageValue)?; let value = decode_all_as_type::<&[u8], (), RuntimeMetadataV15>( &query.value_ty, &value_data.as_ref(), @@ -641,7 +631,7 @@ pub async fn events_at_block( let value_bytes = if let Value::String(data_from_storage) = data_from_storage { unhex(&data_from_storage, NotHex::StorageValue)? } else { - return Err(ErrorChain::StorageFormatError); + return Err(ErrorChain::StorageValueFormat(data_from_storage)); }; let storage_data = decode_as_storage_entry::<&[u8], (), RuntimeMetadataV15>( &key_bytes.as_ref(), @@ -701,8 +691,7 @@ pub async fn current_block_number( &value_data.as_ref(), &mut (), &metadata.types, - ) - .unwrap(); + )?; if let ParsedData::PrimitiveU32 { value, specialty: _, @@ -713,7 +702,7 @@ pub async fn current_block_number( Err(ErrorChain::BlockNumberFormat) } } else { - Err(ErrorChain::StorageFormatError) + Err(ErrorChain::StorageValueFormat(fetched_value)) } } @@ -733,7 +722,7 @@ pub async fn send_stuff(client: &WsClient, data: &str) -> Result<(), ErrorChain> let mut subscription: Subscription = client .subscribe("author_submitAndWatchExtrinsic", rpc_params, "") .await?; - let reply = subscription.next().await.unwrap(); + let _reply = subscription.next().await.unwrap(); //println!("{reply:?}"); // TODO! Ok(()) } diff --git a/src/error.rs b/src/error.rs index 0fda36c..ff019f0 100644 --- a/src/error.rs +++ b/src/error.rs @@ -281,9 +281,6 @@ pub enum ErrorChain { #[error("Storage key is not u32")] StorageKeyNotU32, - - #[error("Storage value unsupported format error")] - StorageFormatError, } impl From for ErrorChain { From aef0d798aefa346642a3ff5f161e72a19e3c3e4a Mon Sep 17 00:00:00 2001 From: Vova Lando Date: Sat, 18 May 2024 17:50:25 +0300 Subject: [PATCH 51/76] status call tests progress save --- src/chain/rpc.rs | 3 +- src/definitions.rs | 12 +++---- src/lib.rs | 1 + src/state.rs | 2 +- tests/integration_tests.rs | 67 ++++++++++++++++++++++++++------------ 5 files changed, 55 insertions(+), 30 deletions(-) create mode 100644 src/lib.rs diff --git a/src/chain/rpc.rs b/src/chain/rpc.rs index a2ce87f..99f41bb 100644 --- a/src/chain/rpc.rs +++ b/src/chain/rpc.rs @@ -159,14 +159,13 @@ pub async fn metadata( client: &WsClient, block: &BlockHash, ) -> Result { - let block_str = format!("0x{}", block.to_string()); let metadata_request: Value = client .request( "state_call", rpc_params![ "Metadata_metadata_at_version", "0x0f000000", - &block_str + block.to_string() ], ) .await diff --git a/src/definitions.rs b/src/definitions.rs index 247b215..f9b7fde 100644 --- a/src/definitions.rs +++ b/src/definitions.rs @@ -80,7 +80,7 @@ pub mod api_v2 { use std::collections::HashMap; use parity_scale_codec::{Decode, Encode}; - use serde::{Serialize, Serializer}; + use serde::{Deserialize, Serialize, Serializer}; pub const AMOUNT: &str = "amount"; pub const CURRENCY: &str = "currency"; @@ -166,7 +166,7 @@ pub mod api_v2 { Completed, } - #[derive(Clone, Debug, Serialize)] + #[derive(Clone, Debug, Serialize, Deserialize)] pub struct ServerStatus { pub server_info: ServerInfo, pub supported_currencies: HashMap, @@ -220,7 +220,7 @@ pub mod api_v2 { } } - #[derive(Clone, Debug, Serialize)] + #[derive(Clone, Debug, Serialize, Deserialize)] pub struct CurrencyProperties { pub chain_name: String, pub kind: TokenKind, @@ -246,16 +246,16 @@ pub mod api_v2 { } } - #[derive(Clone, Copy, Debug, Serialize, Decode, Encode)] + #[derive(Clone, Copy, Debug, Serialize, Decode, Encode, Deserialize)] #[serde(rename_all = "lowercase")] pub enum TokenKind { Asset, Balances, } - #[derive(Clone, Debug, Serialize)] + #[derive(Clone, Debug, Serialize, Deserialize)] pub struct ServerInfo { - pub version: &'static str, + pub version: String, pub instance_id: String, pub debug: bool, pub kalatori_remark: String, diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..8edac6d --- /dev/null +++ b/src/lib.rs @@ -0,0 +1 @@ +pub mod definitions; \ No newline at end of file diff --git a/src/state.rs b/src/state.rs index 3cce24b..6fefa6e 100644 --- a/src/state.rs +++ b/src/state.rs @@ -57,7 +57,7 @@ impl State { let server_info = ServerInfo { // TODO - version: env!("CARGO_PKG_VERSION"), + version: env!("CARGO_PKG_VERSION").to_string(), instance_id: instance_id.clone(), debug, kalatori_remark: remark.clone(), diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 958e92f..54e5d22 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -1,6 +1,8 @@ // if running locally, ensure that you have no dangling processes (kalatori daemon, chopsticks) // pkill -f kalatori; pkill -f chopsticks + +use kalatori::definitions::api_v2::*; use std::process::{Command, Child}; use tokio::time::{sleep, Duration}; use reqwest::Client; @@ -27,6 +29,9 @@ async fn stop_chopsticks(chopsticks: &mut Child) -> std::io::Result<()> { 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"); @@ -34,7 +39,7 @@ fn load_chain_config() { 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"); + env::set_var("KALATORI_REMARK", KALATORI_REMARK.to_string()); // env::set_var("RUST_BACKTRACE", "1"); } @@ -69,7 +74,7 @@ impl TestContext { // Wait for Chopsticks to start sleep(Duration::from_secs(3)).await; - // Load chain config and start the daemon + // Then start the daemon load_chain_config(); let daemon = start_daemon().await.expect("Failed to start kalatori daemon"); @@ -99,25 +104,45 @@ async fn test_daemon_status_call() { // Assert that the response status is 200 OK assert!(resp.status().is_success()); - // Shutdown the daemon - 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() + let body = resp + .json::() .await - .expect("Failed to send request"); + .expect("Failed to parse response"); + + // Check that all required fields are present + assert_eq!(body.server_info.version, KALATORI_CARGO_PACKAGE_VERSION); + assert!(!body.server_info.instance_id.is_empty()); + assert_eq!(body.server_info.debug, true); + assert_eq!(body.server_info.kalatori_remark, KALATORI_REMARK); + + // Check that supported currencies are present + // assert!(!body.supported_currencies.is_empty()); + // for (currency, properties) in body.supported_currencies { + // assert!(!currency.is_empty()); + // assert!(!properties.chain_name.is_empty()); + // assert!(!properties.kind.is_empty()); + // assert!(properties.decimals > 0); + // assert!(!properties.rpc_url.is_empty()); + // // asset_id is optional, so no need to assert on it + // } - // Assert that the response status is 200 OK - assert!(resp.status().is_success()); - - // Shutdown the daemon context.drop_async().await; -} \ No newline at end of file +} + +// #[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; +// } \ No newline at end of file From 891dcffb8fc87d110b201cd246cfbc2889da1828 Mon Sep 17 00:00:00 2001 From: Vova Lando Date: Mon, 20 May 2024 00:14:12 +0300 Subject: [PATCH 52/76] test progress, currencies ugly build --- src/definitions.rs | 6 +++--- src/main.rs | 17 ++++++++++++++- tests/integration_tests.rs | 44 +++++++++++++++++++++++--------------- 3 files changed, 46 insertions(+), 21 deletions(-) diff --git a/src/definitions.rs b/src/definitions.rs index f9b7fde..644fcaa 100644 --- a/src/definitions.rs +++ b/src/definitions.rs @@ -203,7 +203,7 @@ pub mod api_v2 { pub rpc_url: String, #[serde(skip_serializing_if = "Option::is_none")] pub asset_id: Option, - #[serde(skip_serializing)] + // #[serde(skip_serializing)] pub ss58: u16, } @@ -228,7 +228,7 @@ pub mod api_v2 { pub rpc_url: String, #[serde(skip_serializing_if = "Option::is_none")] pub asset_id: Option, - #[serde(skip_serializing)] + // #[serde(skip_serializing)] pub ss58: u16, } @@ -246,7 +246,7 @@ pub mod api_v2 { } } - #[derive(Clone, Copy, Debug, Serialize, Decode, Encode, Deserialize)] + #[derive(Clone, Copy, Debug, Serialize, Decode, Encode, Deserialize, PartialEq)] #[serde(rename_all = "lowercase")] pub enum TokenKind { Asset, diff --git a/src/main.rs b/src/main.rs index cdb33d5..5b1918b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,6 +9,9 @@ use std::{ net::{IpAddr, Ipv4Addr, SocketAddr}, panic, str, }; +use frame_metadata::v15::RuntimeMetadataV15; +use jsonrpsee::ws_client::WsClientBuilder; +use substrate_constructor::fill_prepare::{SpecialTypeToFill, TypeContentToFill}; use substrate_crypto_light::common::{AccountId32, AsBase58}; use tokio::{ signal, @@ -29,11 +32,16 @@ mod state; mod utils; use crate::definitions::{Chain, Entropy, Timestamp, Version}; +use crate::error::ErrorChain; use chain::ChainManager; use database::ConfigWoChains; use error::Error; use signer::Signer; use state::State; +use crate::chain::rpc; +use crate::chain::rpc::{assets_set_at_block, block_hash, current_block_number, metadata, next_block, send_stuff, specs, subscribe_blocks}; +use crate::chain::utils::{AssetTransferConstructor, BalanceTransferConstructor, construct_batch_transaction, construct_single_asset_transfer_call, construct_single_balance_transfer_call}; +use crate::definitions::api_v2::TokenKind; const CONFIG: &str = "KALATORI_CONFIG"; const LOG: &str = "KALATORI_LOG"; @@ -112,9 +120,16 @@ async fn main() -> Result<(), Error> { let (task_tracker, error_rx) = TaskTracker::new(); //let (chains, currencies) = rpc::prepare(config.chain, config.account_lifetime, config.depth).await - let currencies = HashMap::new(); let rpc = env::var("KALATORI_RPC").unwrap(); + let mut currencies = HashMap::new(); + if let Ok(client) = WsClientBuilder::default().build(rpc.clone()).await { + let mut blocks = subscribe_blocks(&client).await?; + let block = next_block(&client, &mut blocks).await?; + let metadata = metadata(&client, &block).await?; + let specs = specs(&client, &metadata, &block).await?; + currencies.extend(assets_set_at_block(&client, &block, &metadata, &rpc, specs.clone()).await?); + } let recipient = AccountId32::from_base58_string(&recipient) .map_err(Error::RecipientAccount)? diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 54e5d22..0c89411 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -105,26 +105,36 @@ async fn test_daemon_status_call() { assert!(resp.status().is_success()); let body = resp - .json::() + .json::() .await .expect("Failed to parse response"); - // Check that all required fields are present - assert_eq!(body.server_info.version, KALATORI_CARGO_PACKAGE_VERSION); - assert!(!body.server_info.instance_id.is_empty()); - assert_eq!(body.server_info.debug, true); - assert_eq!(body.server_info.kalatori_remark, KALATORI_REMARK); - - // Check that supported currencies are present - // assert!(!body.supported_currencies.is_empty()); - // for (currency, properties) in body.supported_currencies { - // assert!(!currency.is_empty()); - // assert!(!properties.chain_name.is_empty()); - // assert!(!properties.kind.is_empty()); - // assert!(properties.decimals > 0); - // assert!(!properties.rpc_url.is_empty()); - // // asset_id is optional, so no need to assert on it - // } + 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, KALATORI_REMARK); + + 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 + assert!(properties.asset_id.is_none()); + assert_eq!(properties.ss58, 0); + } + } context.drop_async().await; } From 7252e9ca94c51519badddd033ec46558779cf873 Mon Sep 17 00:00:00 2001 From: Slesarev Date: Tue, 21 May 2024 11:42:40 +0300 Subject: [PATCH 53/76] feat: fill currencies info --- Cargo.lock | 111 +++++++++++++++++-------------------------- src/chain/mod.rs | 2 + src/chain/tracker.rs | 32 ++++++++----- src/database.rs | 12 ++--- src/definitions.rs | 8 ++-- src/error.rs | 4 +- src/main.rs | 18 ++++--- src/state.rs | 19 ++++++-- 8 files changed, 100 insertions(+), 106 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8ccaee6..b90d98c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -91,6 +91,12 @@ dependencies = [ "syn 2.0.58", ] +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "autocfg" version = "1.2.0" @@ -108,9 +114,9 @@ dependencies = [ "bytes", "futures-util", "http 1.1.0", - "http-body 1.0.0", + "http-body", "http-body-util", - "hyper 1.2.0", + "hyper", "hyper-util", "itoa", "matchit", @@ -140,7 +146,7 @@ dependencies = [ "bytes", "futures-util", "http 1.1.0", - "http-body 1.0.0", + "http-body", "http-body-util", "mime", "pin-project-lite", @@ -857,16 +863,16 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.26" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" +checksum = "fa82e28a107a8cc405f0839610bdc9b15f1e25ec7d696aa5cf173edbcb1486ab" dependencies = [ + "atomic-waker", "bytes", "fnv", "futures-core", "futures-sink", - "futures-util", - "http 0.2.12", + "http 1.1.0", "indexmap", "slab", "tokio", @@ -933,17 +939,6 @@ dependencies = [ "itoa", ] -[[package]] -name = "http-body" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" -dependencies = [ - "bytes", - "http 0.2.12", - "pin-project-lite", -] - [[package]] name = "http-body" version = "1.0.0" @@ -963,7 +958,7 @@ dependencies = [ "bytes", "futures-core", "http 1.1.0", - "http-body 1.0.0", + "http-body", "pin-project-lite", ] @@ -979,30 +974,6 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" -[[package]] -name = "hyper" -version = "0.14.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" -dependencies = [ - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "h2", - "http 0.2.12", - "http-body 0.4.6", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", - "want", -] - [[package]] name = "hyper" version = "1.2.0" @@ -1012,27 +983,32 @@ dependencies = [ "bytes", "futures-channel", "futures-util", + "h2", "http 1.1.0", - "http-body 1.0.0", + "http-body", "httparse", "httpdate", "itoa", "pin-project-lite", "smallvec", "tokio", + "want", ] [[package]] name = "hyper-tls" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", - "hyper 0.14.28", + "http-body-util", + "hyper", + "hyper-util", "native-tls", "tokio", "tokio-native-tls", + "tower-service", ] [[package]] @@ -1042,13 +1018,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca38ef113da30126bbff9cd1705f9273e15d45498615d138b0c20279ac7a76aa" dependencies = [ "bytes", + "futures-channel", "futures-util", "http 1.1.0", - "http-body 1.0.0", - "hyper 1.2.0", + "http-body", + "hyper", "pin-project-lite", "socket2", "tokio", + "tower", + "tower-service", + "tracing", ] [[package]] @@ -1893,20 +1873,22 @@ checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" [[package]] name = "reqwest" -version = "0.11.27" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" +checksum = "566cafdd92868e0939d3fb961bd0dc25fcfaaed179291093b3d43e6b3150ea10" dependencies = [ - "base64 0.21.7", + "base64 0.22.0", "bytes", "encoding_rs", "futures-core", "futures-util", "h2", - "http 0.2.12", - "http-body 0.4.6", - "hyper 0.14.28", + "http 1.1.0", + "http-body", + "http-body-util", + "hyper", "hyper-tls", + "hyper-util", "ipnet", "js-sys", "log", @@ -1915,7 +1897,7 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", - "rustls-pemfile 1.0.4", + "rustls-pemfile", "serde", "serde_json", "serde_urlencoded", @@ -2017,21 +1999,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f1fb85efa936c42c6d5fc28d2629bb51e4b2f4b8a5211e297d599cc5a093792" dependencies = [ "openssl-probe", - "rustls-pemfile 2.1.2", + "rustls-pemfile", "rustls-pki-types", "schannel", "security-framework", ] -[[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.2" @@ -2790,6 +2763,7 @@ dependencies = [ "tokio", "tower-layer", "tower-service", + "tracing", ] [[package]] @@ -2810,6 +2784,7 @@ 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", @@ -3218,9 +3193,9 @@ dependencies = [ [[package]] name = "winreg" -version = "0.50.0" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" dependencies = [ "cfg-if", "windows-sys 0.48.0", diff --git a/src/chain/mod.rs b/src/chain/mod.rs index 19919db..17a6663 100644 --- a/src/chain/mod.rs +++ b/src/chain/mod.rs @@ -84,6 +84,7 @@ impl ChainManager { task_tracker .clone() .spawn("Blockchain connections manager", async move { + // start requests engine while let Some(request) = rx.recv().await { match request { @@ -180,3 +181,4 @@ impl ChainManager { () } } + diff --git a/src/chain/tracker.rs b/src/chain/tracker.rs index da21034..8f7121d 100644 --- a/src/chain/tracker.rs +++ b/src/chain/tracker.rs @@ -30,7 +30,7 @@ use crate::{ }; pub fn start_chain_watch( - c: Chain, + chain: Chain, chain_tx: mpsc::Sender, mut chain_rx: mpsc::Receiver, state: State, @@ -38,29 +38,28 @@ pub fn start_chain_watch( task_tracker: TaskTracker, cancellation_token: CancellationToken, ) -> Result<(), ErrorChain> { - //let (block_source_tx, mut block_source_rx) = mpsc::channel(16); task_tracker .clone() - .spawn(format!("Chain {} watcher", c.name.clone()), async move { + .spawn(format!("Chain {} watcher", chain.name.clone()), async move { let watchdog = 30000; let mut watched_accounts = HashMap::new(); let mut shutdown = false; // TODO: random pick instead - for endpoint in c.endpoints.iter().cycle() { + 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, &mut watched_accounts, endpoint, chain_tx.clone(), state.interface(), task_tracker.clone()) + 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::info!( "Failed to connect to chain {}, due to {} switching RPC server...", - c.name, + chain.name, e ); continue; @@ -80,7 +79,7 @@ pub fn start_chain_watch( Err(e) => { tracing::info!( "Failed to receive block in chain {}, due to {} switching RPC server...", - c.name, + chain.name, e ); break; @@ -91,7 +90,7 @@ pub fn start_chain_watch( tracing::info!("Different runtime version reported! Restarting connection..."); break; } - let events = events_at_block( + if let Ok(events) = events_at_block( &client, &block, Some(EventFilter { @@ -101,8 +100,7 @@ pub fn start_chain_watch( events_entry_metadata(&watcher.metadata)?, &watcher.metadata.types, ) - .await - .unwrap(); + .await { let mut id_remove_list = Vec::new(); for (id, invoice) in watched_accounts.iter() { @@ -118,11 +116,12 @@ pub fn start_chain_watch( } } } - } - + } for id in id_remove_list { watched_accounts.remove(&id); } + } else {break;} + } ChainTrackerRequest::WatchAccount(request) => { watched_accounts.insert(request.id.clone(), Invoice::from_request(request)); @@ -147,7 +146,7 @@ pub fn start_chain_watch( } } } - Ok(format!("Chain {} monitor shut down", c.name).into()) + Ok(format!("Chain {} monitor shut down", chain.name).into()) }); Ok(()) } @@ -164,6 +163,7 @@ pub struct ChainWatcher { impl ChainWatcher { pub async fn prepare_chain( client: &WsClient, + chain: Chain, watched_accounts: &mut HashMap, rpc_url: &str, chain_tx: mpsc::Sender, @@ -179,6 +179,12 @@ impl ChainWatcher { let assets = assets_set_at_block(&client, &block, &metadata, rpc_url, specs.clone()).await?; + // TODO: fail on insufficient assets list + // TODO: remove assets that are not requested + // thus this MUST assert that assets match exactly + + state.connect_chain(assets.clone()).await; + let chain = ChainWatcher { genesis_hash, metadata, diff --git a/src/database.rs b/src/database.rs index cf091e8..aa5560b 100644 --- a/src/database.rs +++ b/src/database.rs @@ -10,14 +10,14 @@ use crate::{ AssetId, BlockNumber, CurrencyProperties, OrderCreateResponse, OrderInfo, OrderQuery, PaymentStatus, ServerInfo, ServerStatus, WithdrawalStatus, }, - Balance, Nonce, Timestamp, + Balance, Nonce, }, error::{Error, ErrorDb}, TaskTracker, }; use parity_scale_codec::{Compact, Decode, Encode}; use serde::Deserialize; -use std::{collections::HashMap, fs::File, io::ErrorKind}; +use std::{collections::HashMap, fs::File, io::ErrorKind, time::Duration}; use substrate_crypto_light::common::AccountId32; use tokio::sync::{mpsc, oneshot}; @@ -64,7 +64,7 @@ type PublicSlot = [u8; 32]; type BalanceSlot = u128; type Derivation = [u8; 32]; pub type Account = [u8; 32]; - +/* #[derive(Encode, Decode)] enum ChainKind { Id(Vec>), @@ -128,7 +128,7 @@ struct TransferTx { recipient: Account, exact_amount: Option>, } - +*/ /* impl Value for Invoice { type SelfType<'a> = Self; @@ -159,8 +159,8 @@ pub struct ConfigWoChains { pub recipient: AccountId32, pub debug: bool, pub remark: String, - pub depth: Option, - pub account_lifetime: BlockNumber, + //pub depth: Option, + pub account_lifetime: Duration, pub rpc: String, } diff --git a/src/definitions.rs b/src/definitions.rs index f9b7fde..848c91e 100644 --- a/src/definitions.rs +++ b/src/definitions.rs @@ -6,12 +6,12 @@ use serde::Deserialize; pub type Version = u64; pub type Nonce = u32; -pub type Timestamp = u64; + pub type PalletIndex = u8; pub type Entropy = Vec; // TODO: maybe enforce something here -#[derive(Deserialize)] +#[derive(Clone, Deserialize)] #[serde(rename_all = "kebab-case")] pub struct Chain { pub name: String, @@ -22,14 +22,14 @@ pub struct Chain { pub asset: Vec, } -#[derive(Deserialize)] +#[derive(Clone, Deserialize)] #[serde(rename_all = "kebab-case")] pub struct NativeToken { pub name: String, pub decimals: api_v2::Decimals, } -#[derive(Deserialize)] +#[derive(Clone, Deserialize)] pub struct AssetInfo { pub name: String, pub id: api_v2::AssetId, diff --git a/src/error.rs b/src/error.rs index ff019f0..b19ad2c 100644 --- a/src/error.rs +++ b/src/error.rs @@ -18,8 +18,8 @@ pub enum Error { #[error("failed to read a config file at {0:?}")] ConfigFileRead(String), - #[error("failed to parse the config at {0:?}")] - ConfigFileParse(String), + #[error("failed to parse the config at {0}")] + ConfigFileParse(toml::de::Error), #[error("failed to parse the config parameter {0}")] ConfigParse(String), diff --git a/src/main.rs b/src/main.rs index cdb33d5..3fc5f69 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,6 +8,7 @@ use std::{ future::Future, net::{IpAddr, Ipv4Addr, SocketAddr}, panic, str, + time::Duration, }; use substrate_crypto_light::common::{AccountId32, AsBase58}; use tokio::{ @@ -28,7 +29,7 @@ mod signer; mod state; mod utils; -use crate::definitions::{Chain, Entropy, Timestamp, Version}; +use crate::definitions::{Chain, Entropy, Version}; use chain::ChainManager; use database::ConfigWoChains; use error::Error; @@ -111,9 +112,6 @@ async fn main() -> Result<(), Error> { let (task_tracker, error_rx) = TaskTracker::new(); - //let (chains, currencies) = rpc::prepare(config.chain, config.account_lifetime, config.depth).await - let currencies = HashMap::new(); - let rpc = env::var("KALATORI_RPC").unwrap(); let recipient = AccountId32::from_base58_string(&recipient) @@ -127,14 +125,13 @@ async fn main() -> Result<(), Error> { let (cm_tx, cm_rx) = oneshot::channel(); let state = State::initialise( - currencies, signer.interface(), ConfigWoChains { recipient: recipient.clone(), debug: config.debug, remark, - depth: config.depth, - account_lifetime: config.account_lifetime, + //depth: config.depth, + account_lifetime: Duration::from_millis(config.account_lifetime), rpc: rpc.clone(), }, db, @@ -375,11 +372,12 @@ async fn shutdown_listener( Ok("The shutdown signal listener is shut down.".into()) } +/// User-supplied settings through config file #[derive(Deserialize)] #[serde(rename_all = "kebab-case")] struct Config { - account_lifetime: Timestamp, - depth: Option, + account_lifetime: u64, + depth: Option, host: Option, database: Option, debug: bool, @@ -402,6 +400,6 @@ impl Config { let unparsed_config = fs::read_to_string(&config_path) .map_err(|_| Error::ConfigFileRead(config_path.clone()))?; - toml::from_str(&unparsed_config).map_err(|_| Error::ConfigFileParse(config_path)) + toml::from_str(&unparsed_config).map_err(Error::ConfigFileParse) } } diff --git a/src/state.rs b/src/state.rs index 6fefa6e..dee86f1 100644 --- a/src/state.rs +++ b/src/state.rs @@ -27,13 +27,11 @@ pub struct State { impl State { pub fn initialise( - currencies: HashMap, signer: Signer, ConfigWoChains { recipient, debug, remark, - depth, account_lifetime, rpc, }: ConfigWoChains, @@ -68,7 +66,8 @@ impl State { let chain_manager = chain_manager.await.map_err(|_| Error::Fatal)?; let db_wakeup = db.clone(); let chain_manager_wakeup = chain_manager.clone(); - let state = StateData { + let currencies = HashMap::new(); + let mut state = StateData { currencies, recipient, server_info, @@ -90,6 +89,11 @@ impl State { while let Some(request) = rx.recv().await { 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 @@ -149,6 +153,10 @@ impl State { 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 @@ -217,6 +225,7 @@ impl State { } enum StateAccessRequest { + ConnectChain(HashMap), GetInvoiceStatus(GetInvoiceStatus), CreateInvoice(CreateInvoice), ServerStatus(oneshot::Sender), @@ -244,6 +253,10 @@ struct StateData { } 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 From a82c97dda0139b6af021fc3a4c0d8452aa6c316c Mon Sep 17 00:00:00 2001 From: Slesarev Date: Tue, 21 May 2024 12:00:15 +0300 Subject: [PATCH 54/76] fix: remove unwanted assets --- src/chain/tracker.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/chain/tracker.rs b/src/chain/tracker.rs index 8f7121d..bab40aa 100644 --- a/src/chain/tracker.rs +++ b/src/chain/tracker.rs @@ -176,11 +176,17 @@ impl ChainWatcher { let version = runtime_version_identifier(client, &block).await?; let metadata = metadata(&client, &block).await?; let specs = specs(&client, &metadata, &block).await?; - let assets = + let mut assets = assets_set_at_block(&client, &block, &metadata, rpc_url, specs.clone()).await?; + assets.retain(|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)) + } + }); // TODO: fail on insufficient assets list - // TODO: remove assets that are not requested // thus this MUST assert that assets match exactly state.connect_chain(assets.clone()).await; From 08b2ac02ce614b2114842cc6c0300f86cee674ed Mon Sep 17 00:00:00 2001 From: Slesarev Date: Tue, 21 May 2024 12:21:42 +0300 Subject: [PATCH 55/76] feat: populate currencies response info --- src/chain/mod.rs | 2 ++ src/chain/tracker.rs | 19 +++++++++++++++++-- src/error.rs | 3 +++ 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/chain/mod.rs b/src/chain/mod.rs index 17a6663..f90632b 100644 --- a/src/chain/mod.rs +++ b/src/chain/mod.rs @@ -59,6 +59,8 @@ impl ChainManager { } 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())); diff --git a/src/chain/tracker.rs b/src/chain/tracker.rs index bab40aa..9e8c845 100644 --- a/src/chain/tracker.rs +++ b/src/chain/tracker.rs @@ -178,6 +178,8 @@ impl ChainWatcher { let specs = specs(&client, &metadata, &block).await?; let mut assets = assets_set_at_block(&client, &block, &metadata, rpc_url, specs.clone()).await?; + + // Remove unwanted assets assets.retain(|name, properties| { if let Some(native_token) = &chain.native_token { (native_token.name == *name) && (native_token.decimals == specs.decimals) @@ -186,8 +188,21 @@ impl ChainWatcher { } }); - // TODO: fail on insufficient assets list - // thus this MUST assert that assets match exactly + // 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 + // + // This could be move verbose on reconnects + // + // 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(ErrorChain::AssetsInvalid(chain.name)); + } + // this MUST assert that assets match exactly before reporting it state.connect_chain(assets.clone()).await; diff --git a/src/error.rs b/src/error.rs index b19ad2c..b71a7ae 100644 --- a/src/error.rs +++ b/src/error.rs @@ -103,6 +103,9 @@ pub enum ErrorChain { #[error("Asset id is not u32")] AssetIdFormat, + #[error("Invalid assets for chain {0}")] + AssetsInvalid(String), + #[error("Asset key has no parceable part")] AssetKeyEmpty, From 274b1f3421120a2ebb5ecadb253bdc0a85e2ea9a Mon Sep 17 00:00:00 2001 From: Slesarev Date: Tue, 21 May 2024 12:24:37 +0300 Subject: [PATCH 56/76] chore: fmt --- src/chain/mod.rs | 1 - src/chain/rpc.rs | 15 ++++++--------- src/chain/tracker.rs | 21 ++++++++++++--------- src/chain/utils.rs | 1 - src/lib.rs | 2 +- tests/integration_tests.rs | 38 +++++++++++++++++++++++++------------- 6 files changed, 44 insertions(+), 34 deletions(-) diff --git a/src/chain/mod.rs b/src/chain/mod.rs index f90632b..409e25d 100644 --- a/src/chain/mod.rs +++ b/src/chain/mod.rs @@ -183,4 +183,3 @@ impl ChainManager { () } } - diff --git a/src/chain/rpc.rs b/src/chain/rpc.rs index 39e625d..a856d34 100644 --- a/src/chain/rpc.rs +++ b/src/chain/rpc.rs @@ -60,12 +60,12 @@ const AURA: &str = "AuraApi"; /// /// 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 { +pub async fn runtime_version_identifier( + client: &WsClient, + block: &BlockHash, +) -> Result { let value = client - .request( - "state_getRuntimeVersion", - rpc_params![block.to_string()], - ) + .request("state_getRuntimeVersion", rpc_params![block.to_string()]) .await?; Ok(value) } @@ -121,10 +121,7 @@ pub async fn get_keys_from_storage( params.push(serde_json::to_value(block.to_string()).unwrap()); let keys: Value = client - .request( - "state_getKeysPaged", - params - ) + .request("state_getKeysPaged", params) .await .map_err(ErrorChain::Client)?; diff --git a/src/chain/tracker.rs b/src/chain/tracker.rs index 9e8c845..ea485e5 100644 --- a/src/chain/tracker.rs +++ b/src/chain/tracker.rs @@ -86,7 +86,7 @@ pub fn start_chain_watch( }, }; // TODO: continue and reconnect if spec_version changed - if watcher.version != runtime_version_identifier(&client, &block).await? { + if watcher.version != runtime_version_identifier(&client, &block).await? { tracing::info!("Different runtime version reported! Restarting connection..."); break; } @@ -116,7 +116,7 @@ pub fn start_chain_watch( } } } - } + } for id in id_remove_list { watched_accounts.remove(&id); } @@ -181,12 +181,15 @@ impl ChainWatcher { // Remove unwanted assets assets.retain(|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)) - } - }); + 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 @@ -199,7 +202,7 @@ impl ChainWatcher { // // 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} { + if assets.len() != chain.asset.len() + if chain.native_token.is_some() { 1 } else { 0 } { return Err(ErrorChain::AssetsInvalid(chain.name)); } // this MUST assert that assets match exactly before reporting it diff --git a/src/chain/utils.rs b/src/chain/utils.rs index 2b29bac..3ee0315 100644 --- a/src/chain/utils.rs +++ b/src/chain/utils.rs @@ -1215,4 +1215,3 @@ pub fn unit(x: &Map) -> Result { None => Err(ErrorChain::NoUnit), } } - diff --git a/src/lib.rs b/src/lib.rs index 8edac6d..7f5d5d0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1 +1 @@ -pub mod definitions; \ No newline at end of file +pub mod definitions; diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 54e5d22..3513435 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -1,14 +1,13 @@ // if running locally, ensure that you have no dangling processes (kalatori daemon, chopsticks) // pkill -f kalatori; pkill -f chopsticks - use kalatori::definitions::api_v2::*; -use std::process::{Command, Child}; -use tokio::time::{sleep, Duration}; +use lazy_static::lazy_static; use reqwest::Client; use std::env; -use std::sync::{Once, Mutex}; -use lazy_static::lazy_static; +use std::process::{Child, Command}; +use std::sync::{Mutex, Once}; +use tokio::time::{sleep, Duration}; static INIT: Once = Once::new(); lazy_static! { @@ -17,7 +16,11 @@ lazy_static! { async fn start_chopsticks() -> std::io::Result { let mut command = Command::new("npx"); - command.args(&["@acala-network/chopsticks@latest", "-c", "chopsticks/pd-ah.yml"]); + command.args(&[ + "@acala-network/chopsticks@latest", + "-c", + "chopsticks/pd-ah.yml", + ]); let chopsticks = command.spawn()?; sleep(Duration::from_secs(3)).await; // Give Chopsticks some time to start Ok(chopsticks) @@ -35,17 +38,22 @@ 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_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_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()?; + let daemon = Command::new("target/debug/kalatori").spawn()?; sleep(Duration::from_secs(3)).await; // Give the daemon some time to start Ok(daemon) } @@ -65,7 +73,9 @@ impl TestContext { // Start Chopsticks if not already started INIT.call_once(|| { tokio::spawn(async { - let chopsticks = start_chopsticks().await.expect("Failed to start Chopsticks"); + let chopsticks = start_chopsticks() + .await + .expect("Failed to start Chopsticks"); let mut guard = CHOPSTICKS.lock().unwrap(); *guard = Some(chopsticks); }); @@ -76,7 +86,9 @@ impl TestContext { // Then start the daemon load_chain_config(); - let daemon = start_daemon().await.expect("Failed to start kalatori daemon"); + let daemon = start_daemon() + .await + .expect("Failed to start kalatori daemon"); TestContext { daemon: Some(daemon), @@ -145,4 +157,4 @@ async fn test_daemon_status_call() { // assert!(resp.status().is_success()); // // context.drop_async().await; -// } \ No newline at end of file +// } From c185e62c2f1cf439dda9f9facb1b9fbb46a19a99 Mon Sep 17 00:00:00 2001 From: Slesarev Date: Tue, 21 May 2024 12:37:10 +0300 Subject: [PATCH 57/76] fix: rename token --- configs/polkadot.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configs/polkadot.toml b/configs/polkadot.toml index bab6cd4..b892955 100644 --- a/configs/polkadot.toml +++ b/configs/polkadot.toml @@ -23,5 +23,5 @@ name = "USDC" id = 1337 [[chain.asset]] -name = "USDT" +name = "USDt" id = 1984 From fd718b070b90f6a52aadcaae84d99f0ae92c42f0 Mon Sep 17 00:00:00 2001 From: Slesarev Date: Tue, 21 May 2024 12:41:42 +0300 Subject: [PATCH 58/76] feat: a bit of verbosity on assets selection --- src/chain/tracker.rs | 5 +++-- src/definitions.rs | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/chain/tracker.rs b/src/chain/tracker.rs index ea485e5..c6da3c8 100644 --- a/src/chain/tracker.rs +++ b/src/chain/tracker.rs @@ -179,8 +179,11 @@ impl ChainWatcher { 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 { @@ -198,8 +201,6 @@ impl ChainWatcher { // This is probably an optimisation, but I don't have time to analyse perfirmance right // now, it's just simpler to implement // - // This could be move verbose on reconnects - // // 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 } { diff --git a/src/definitions.rs b/src/definitions.rs index 848c91e..71cbd78 100644 --- a/src/definitions.rs +++ b/src/definitions.rs @@ -22,14 +22,14 @@ pub struct Chain { pub asset: Vec, } -#[derive(Clone, Deserialize)] +#[derive(Clone, Debug, Deserialize)] #[serde(rename_all = "kebab-case")] pub struct NativeToken { pub name: String, pub decimals: api_v2::Decimals, } -#[derive(Clone, Deserialize)] +#[derive(Clone, Debug, Deserialize)] pub struct AssetInfo { pub name: String, pub id: api_v2::AssetId, From a23c644be5c787acc8487d3795c7faf1362bb473 Mon Sep 17 00:00:00 2001 From: Slesarev Date: Tue, 21 May 2024 15:17:40 +0300 Subject: [PATCH 59/76] feat: check spec name! --- src/chain/rpc.rs | 4 ---- src/chain/tracker.rs | 4 +++- src/error.rs | 3 +++ 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/chain/rpc.rs b/src/chain/rpc.rs index a856d34..0d5be50 100644 --- a/src/chain/rpc.rs +++ b/src/chain/rpc.rs @@ -246,10 +246,6 @@ pub struct BlockHead { //state_root: String, } -// TODO: add proper errors -// -// not urgent since it happens on boot once for now -// /// Get all sufficient assets from a chain pub async fn assets_set_at_block( client: &WsClient, diff --git a/src/chain/tracker.rs b/src/chain/tracker.rs index c6da3c8..9bf48c0 100644 --- a/src/chain/tracker.rs +++ b/src/chain/tracker.rs @@ -5,7 +5,7 @@ use std::collections::HashMap; use frame_metadata::v15::RuntimeMetadataV15; use jsonrpsee::ws_client::{WsClient, WsClientBuilder}; use serde_json::Value; -use substrate_parser::ShortSpecs; +use substrate_parser::{AsMetadata, ShortSpecs}; use tokio::{ sync::mpsc, time::{timeout, Duration}, @@ -175,6 +175,8 @@ impl ChainWatcher { 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(ErrorChain::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?; diff --git a/src/error.rs b/src/error.rs index b71a7ae..e69b814 100644 --- a/src/error.rs +++ b/src/error.rs @@ -284,6 +284,9 @@ pub enum ErrorChain { #[error("Storage key is not u32")] StorageKeyNotU32, + + #[error("RPC runs unexpected network: instead of {expected}, found {actual} at {rpc}")] + WrongNetwork{expected: String, actual: String, rpc: String}, } impl From for ErrorChain { From 612db05d0e6595e0c1fed706a9f3145e9627c88f Mon Sep 17 00:00:00 2001 From: Slesarev Date: Tue, 21 May 2024 15:18:32 +0300 Subject: [PATCH 60/76] fix: rename assethub to statemint in config --- configs/polkadot.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configs/polkadot.toml b/configs/polkadot.toml index b892955..518bbe1 100644 --- a/configs/polkadot.toml +++ b/configs/polkadot.toml @@ -12,7 +12,7 @@ endpoints = [ ] [[chain]] -name = "assethub-polkadot" +name = "statemint" endpoints = [ "wss://polkadot-asset-hub-rpc.polkadot.io", "wss://statemint-rpc.dwellir.com", From 86d2c5c05938304e5c34b1bcdf1f58f8b660976d Mon Sep 17 00:00:00 2001 From: Slesarev Date: Wed, 22 May 2024 12:11:12 +0300 Subject: [PATCH 61/76] fix: do not reconnect on empty events --- src/chain/rpc.rs | 87 ++++++++++++++++++++------------------- src/chain/tracker.rs | 96 +++++++++++++++++++++++++------------------- src/database.rs | 1 - src/main.rs | 3 -- src/state.rs | 1 - start.sh | 3 -- 6 files changed, 101 insertions(+), 90 deletions(-) diff --git a/src/chain/rpc.rs b/src/chain/rpc.rs index 0d5be50..9706a19 100644 --- a/src/chain/rpc.rs +++ b/src/chain/rpc.rs @@ -599,7 +599,7 @@ pub async fn system_balance_at_account( Err(ErrorChain::BalanceNotFound) } -async fn transfer_events( +pub async fn transfer_events( client: &WsClient, block: &BlockHash, metadata_v15: &RuntimeMetadataV15, @@ -619,7 +619,7 @@ async fn transfer_events( .await } -pub async fn events_at_block( +async fn events_at_block( client: &WsClient, block: &BlockHash, optional_filter: Option>, @@ -628,46 +628,48 @@ pub async fn events_at_block( ) -> Result, ErrorChain> { let keys_from_storage = get_keys_from_storage(client, "System", "Events", block).await?; let mut out = Vec::new(); - if let Value::Array(ref keys_array) = keys_from_storage { - 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(ErrorChain::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 + 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(ErrorChain::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 if event.pallet_name == filter.pallet { + } else { out.push(Event(event.to_owned())); } - } else { - out.push(Event(event.to_owned())); } } } @@ -675,11 +677,14 @@ pub async fn events_at_block( } } } - return Ok(out); - } - } + }; + return Ok(out); + }, + _ => { + tracing::warn!("{keys_from_storage}"); + return Err(ErrorChain::EventsMissing); + }, } - Err(ErrorChain::EventsMissing) } pub async fn current_block_number( diff --git a/src/chain/tracker.rs b/src/chain/tracker.rs index 9bf48c0..2d83e82 100644 --- a/src/chain/tracker.rs +++ b/src/chain/tracker.rs @@ -17,8 +17,9 @@ use crate::{ definitions::{BlockHash, ChainTrackerRequest, EventFilter, Invoice}, payout::payout, rpc::{ - assets_set_at_block, block_hash, events_at_block, genesis_hash, metadata, next_block, + 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}, }, @@ -41,7 +42,7 @@ pub fn start_chain_watch( task_tracker .clone() .spawn(format!("Chain {} watcher", chain.name.clone()), async move { - let watchdog = 30000; + let watchdog = 120000; let mut watched_accounts = HashMap::new(); let mut shutdown = false; // TODO: random pick instead @@ -57,7 +58,7 @@ pub fn start_chain_watch( { Ok(a) => a, Err(e) => { - tracing::info!( + tracing::warn!( "Failed to connect to chain {}, due to {} switching RPC server...", chain.name, e @@ -78,50 +79,53 @@ pub fn start_chain_watch( Ok(a) => a, Err(e) => { tracing::info!( - "Failed to receive block in chain {}, due to {} switching RPC server...", + "Failed to receive block in chain {}, due to {}; Switching RPC server...", chain.name, e ); break; }, }; - // TODO: continue and reconnect if spec_version changed + + 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; } - if let Ok(events) = events_at_block( + + match transfer_events( &client, &block, - Some(EventFilter { - pallet: "Balances", - optional_event_variant: Some("Transfer"), - }), - events_entry_metadata(&watcher.metadata)?, - &watcher.metadata.types, - ) + &watcher.metadata, + ) .await { - - let mut id_remove_list = Vec::new(); - for (id, invoice) in watched_accounts.iter() { - 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); + Ok(events) => { + let mut id_remove_list = Vec::new(); + for (id, invoice) in watched_accounts.iter() { + 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); + } + } } } + for id in id_remove_list { + watched_accounts.remove(&id); + } + }, + Err(e) => { + tracing::warn!("Events fetch error {e} at {}", chain.name); + break; + }, } - } - for id in id_remove_list { - watched_accounts.remove(&id); - } - } else {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)); @@ -238,19 +242,29 @@ impl ChainWatcher { watched_accounts.remove(&id); } - task_tracker.spawn("watching blocks at {rpc_url}", async move { - while let Ok(block) = next_block_number(&mut blocks).await { - if chain_tx - .send(ChainTrackerRequest::NewBlock(block)) - .await - .is_err() - { - break; + 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 meand that the latter is already down either way - Ok("Block watch at {rpc_url} stopped".into()) + // 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/database.rs b/src/database.rs index aa5560b..a091fe6 100644 --- a/src/database.rs +++ b/src/database.rs @@ -161,7 +161,6 @@ pub struct ConfigWoChains { pub remark: String, //pub depth: Option, pub account_lifetime: Duration, - pub rpc: String, } /// Database server handle diff --git a/src/main.rs b/src/main.rs index 3fc5f69..3162072 100644 --- a/src/main.rs +++ b/src/main.rs @@ -112,8 +112,6 @@ async fn main() -> Result<(), Error> { let (task_tracker, error_rx) = TaskTracker::new(); - let rpc = env::var("KALATORI_RPC").unwrap(); - let recipient = AccountId32::from_base58_string(&recipient) .map_err(Error::RecipientAccount)? .0; @@ -132,7 +130,6 @@ async fn main() -> Result<(), Error> { remark, //depth: config.depth, account_lifetime: Duration::from_millis(config.account_lifetime), - rpc: rpc.clone(), }, db, cm_rx, diff --git a/src/state.rs b/src/state.rs index dee86f1..90721d2 100644 --- a/src/state.rs +++ b/src/state.rs @@ -33,7 +33,6 @@ impl State { debug, remark, account_lifetime, - rpc, }: ConfigWoChains, db: Database, chain_manager: oneshot::Receiver, diff --git a/start.sh b/start.sh index 3f0b68e..bb35c7b 100755 --- a/start.sh +++ b/start.sh @@ -1,8 +1,5 @@ KALATORI_HOST="127.0.0.1:16726" \ KALATORI_SEED="bottom drive obey lake curtain smoke basket hold race lonely fit walk" \ -KALATORI_DATABASE="database-ah-usdc.redb" \ -KALATORI_RPC="wss://polkadot-asset-hub-rpc.polkadot.io" \ KALATORI_RECIPIENT="5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY" \ -KALATORI_USD_ASSET="USDC" \ KALATORI_REMARK="test" \ cargo r From a3304e8ad870ec3e598a656a7935f560556feb3d Mon Sep 17 00:00:00 2001 From: Vova Lando Date: Mon, 27 May 2024 23:08:39 +0300 Subject: [PATCH 62/76] test progress state --- chopsticks/pd-ah.yml | 2 +- configs/chopsticks.toml | 2 +- tests/integration_tests.rs | 13 ++++--------- 3 files changed, 6 insertions(+), 11 deletions(-) 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 index d0fe099..e832b2b 100644 --- a/configs/chopsticks.toml +++ b/configs/chopsticks.toml @@ -4,7 +4,7 @@ debug = true in-memory-db = true [[chain]] -name = "chop-assethub-polkadot" +name = "statemint" endpoints = [ "ws://localhost:8000" ] diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index e90cf71..3f725a3 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -1,3 +1,4 @@ +// 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 @@ -16,13 +17,9 @@ lazy_static! { async fn start_chopsticks() -> std::io::Result { let mut command = Command::new("npx"); - command.args(&[ - "@acala-network/chopsticks@latest", - "-c", - "chopsticks/pd-ah.yml", - ]); + command.args(&["@acala-network/chopsticks@latest", "-c", "chopsticks/pd-ah.yml"]); let chopsticks = command.spawn()?; - sleep(Duration::from_secs(3)).await; // Give Chopsticks some time to start + sleep(Duration::from_secs(10)).await; // Give Chopsticks some time to start Ok(chopsticks) } @@ -54,7 +51,7 @@ fn load_chain_config() { async fn start_daemon() -> std::io::Result { let daemon = Command::new("target/debug/kalatori").spawn()?; - sleep(Duration::from_secs(3)).await; // Give the daemon some time to start + sleep(Duration::from_secs(10)).await; // Give the daemon some time to start Ok(daemon) } @@ -143,8 +140,6 @@ async fn test_daemon_status_call() { assert_eq!(properties.decimals, 10); assert_eq!(properties.rpc_url, "ws://localhost:8000"); // those are wrong atm - assert!(properties.asset_id.is_none()); - assert_eq!(properties.ss58, 0); } } From bf15cf291ad1bf5198e0fd87b604bb52fdc547d1 Mon Sep 17 00:00:00 2001 From: Fluid <90795031+fluiderson@users.noreply.github.com> Date: Tue, 28 May 2024 12:26:58 +0300 Subject: [PATCH 63/76] Fix warns in `Cargo.toml`, make a major refinement of the error module, unify the style of error messages, completely migrate & adapt error types to `thiserror` macros, add the pretty error cause printer, just like in `anyhow` --- Cargo.lock | 426 ++++++++--------- Cargo.toml | 71 ++- src/chain/definitions.rs | 20 +- src/chain/mod.rs | 22 +- src/chain/payout.rs | 12 +- src/chain/rpc.rs | 94 ++-- src/chain/tracker.rs | 10 +- src/chain/utils.rs | 124 ++--- src/database.rs | 68 +-- src/error.rs | 499 +++++++++----------- src/main.rs | 21 +- src/server.rs | 50 +- src/signer.rs | 24 +- src/state.rs | 4 +- src/unused | 941 ------------------------------------- src/utils.rs | 8 +- tests/integration_tests.rs | 21 +- 17 files changed, 711 insertions(+), 1704 deletions(-) delete mode 100644 src/unused diff --git a/Cargo.lock b/Cargo.lock index b90d98c..5a39ce1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -40,15 +40,15 @@ dependencies = [ [[package]] name = "allocator-api2" -version = "0.2.16" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" [[package]] name = "anyhow" -version = "1.0.82" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" [[package]] name = "approx" @@ -82,13 +82,13 @@ checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" [[package]] name = "async-trait" -version = "0.1.79" +version = "0.1.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507401cad91ec6a857ed5513a2073c82a9b9048762b885bb98655b306964681" +checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.66", ] [[package]] @@ -99,9 +99,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" @@ -165,7 +165,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.66", ] [[package]] @@ -203,15 +203,9 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64" -version = "0.21.7" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" - -[[package]] -name = "base64" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9475866fec1451be56a3c2400fd081ff546538961565ccb5b7142cbd22bc7a51" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" @@ -305,9 +299,9 @@ checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" [[package]] name = "bytemuck" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d6d68c57235a3a081186990eca2867354726650f42f7516ca50c28d6281fd15" +checksum = "78834c15cb5d5efe3452d58b1e8ba890dd62d21907f867f383358198e56ebca5" [[package]] name = "byteorder" @@ -323,9 +317,9 @@ checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" [[package]] name = "cc" -version = "1.0.92" +version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2678b2e3449475e95b0aa6f9b506a28e61b3dc8996592b983695e8ebb58a8b41" +checksum = "41c270e7540d725e65ac7f1b212ac8ce349719624d7bcff99f8e2e488e8cf03f" [[package]] name = "cfg-if" @@ -384,9 +378,9 @@ dependencies = [ [[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", ] @@ -402,9 +396,9 @@ dependencies = [ [[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" @@ -459,7 +453,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.66", ] [[package]] @@ -628,9 +622,9 @@ dependencies = [ [[package]] name = "fiat-crypto" -version = "0.2.7" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c007b1ae3abe1cb6f85a16305acd418b7ca6343b953633fee2b76d8f108b830f" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" [[package]] name = "find-crate" @@ -655,9 +649,9 @@ dependencies = [ [[package]] name = "flate2" -version = "1.0.28" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" dependencies = [ "crc32fast", "miniz_oxide", @@ -765,7 +759,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.66", ] [[package]] @@ -826,9 +820,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", @@ -882,9 +876,9 @@ dependencies = [ [[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", "allocator-api2", @@ -976,9 +970,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" -version = "1.2.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "186548d73ac615b32a73aafe38fb4f56c0d340e110e5a200bcadbaf2e199263a" +checksum = "fe575dd17d0862a9a33781c8c4696a55c320909004a67a00fb286ba8b1bc496d" dependencies = [ "bytes", "futures-channel", @@ -1013,9 +1007,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca38ef113da30126bbff9cd1705f9273e15d45498615d138b0c20279ac7a76aa" +checksum = "3d8d52be92d09acc2e01dddb7fde3ad983fc6489c7db4837e605bc3fca4cb63e" dependencies = [ "bytes", "futures-channel", @@ -1086,9 +1080,9 @@ dependencies = [ [[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", ] @@ -1125,9 +1119,9 @@ dependencies = [ [[package]] name = "jsonrpsee" -version = "0.22.4" +version = "0.22.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4b0e68d9af1f066c06d6e2397583795b912d78537d7d907c561e82c13d69fa1" +checksum = "cfdb12a2381ea5b2e68c3469ec604a007b367778cdb14d09612c8069ebd616ad" dependencies = [ "jsonrpsee-core", "jsonrpsee-types", @@ -1136,9 +1130,9 @@ dependencies = [ [[package]] name = "jsonrpsee-client-transport" -version = "0.22.4" +version = "0.22.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92f254f56af1ae84815b9b1325094743dcf05b92abb5e94da2e81a35cff0cada" +checksum = "4978087a58c3ab02efc5b07c5e5e2803024536106fd5506f558db172c889b3aa" dependencies = [ "futures-util", "http 0.2.12", @@ -1157,9 +1151,9 @@ dependencies = [ [[package]] name = "jsonrpsee-core" -version = "0.22.4" +version = "0.22.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "274d68152c24aa78977243bb56f28d7946e6aa309945b37d33174a3f92d89a3a" +checksum = "b4b257e1ec385e07b0255dde0b933f948b5c8b8c28d42afda9587c3a967b896d" dependencies = [ "anyhow", "async-trait", @@ -1179,9 +1173,9 @@ dependencies = [ [[package]] name = "jsonrpsee-types" -version = "0.22.4" +version = "0.22.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dc828e537868d6b12bbb07ec20324909a22ced6efca0057c825c3e1126b2c6d" +checksum = "150d6168405890a7a3231a3c74843f58b8959471f6df76078db2619ddee1d07d" dependencies = [ "anyhow", "beef", @@ -1192,9 +1186,9 @@ dependencies = [ [[package]] name = "jsonrpsee-ws-client" -version = "0.22.4" +version = "0.22.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f00abe918bf34b785f87459b9205790e5361a3f7437adb50e928dc243f27eb" +checksum = "58b9db2dfd5bb1194b0ce921504df9ceae210a345bc2f6c5a61432089bbab070" dependencies = [ "http 0.2.12", "jsonrpsee-client-transport", @@ -1241,7 +1235,7 @@ dependencies = [ "thiserror", "tokio", "tokio-util", - "toml 0.8.12", + "toml 0.8.13", "tracing", "tracing-subscriber", "ureq", @@ -1268,21 +1262,21 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.153" +version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[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", @@ -1335,9 +1329,9 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "miniz_oxide" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" +checksum = "87dfd01fe195c66b572b37921ad8803d010623c0aca821bea2302239d155cdae" dependencies = [ "adler", "simd-adler32", @@ -1357,10 +1351,11 @@ dependencies = [ [[package]] name = "mnemonic-external" version = "0.1.0" -source = "git+https://github.com/Alzymologist/mnemonic-external#56fc336015c97e7ff01b702aeca02e5a21b4ee95" +source = "git+https://github.com/fluiderson/me?branch=thiserror#cf95774298cecc70d669fd8efc0c43358f7d1960" dependencies = [ "bitvec", "sha2", + "thiserror", ] [[package]] @@ -1374,11 +1369,10 @@ dependencies = [ [[package]] name = "native-tls" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" dependencies = [ - "lazy_static", "libc", "log", "openssl", @@ -1408,11 +1402,10 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +checksum = "c165a9ab64cf766f73521c0dd2cfdff64f488b8f0b3e621face3462d3db536d7" dependencies = [ - "autocfg", "num-integer", "num-traits", ] @@ -1434,9 +1427,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] @@ -1495,7 +1488,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.66", ] [[package]] @@ -1547,9 +1540,9 @@ dependencies = [ [[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", "bitvec", @@ -1561,11 +1554,11 @@ 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", @@ -1584,12 +1577,12 @@ dependencies = [ [[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 0.9.9", + "parking_lot_core 0.9.10", ] [[package]] @@ -1608,15 +1601,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.9" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.4.1", + "redox_syscall 0.5.1", "smallvec", - "windows-targets 0.48.5", + "windows-targets 0.52.5", ] [[package]] @@ -1651,7 +1644,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.66", ] [[package]] @@ -1738,28 +1731,18 @@ dependencies = [ [[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" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8366a6159044a37876a2b9817124296703c586a5c92e2c53751fa06d8d43e8" +checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" dependencies = [ - "toml_edit 0.20.7", + "toml_edit 0.21.1", ] [[package]] name = "proc-macro2" -version = "1.0.79" +version = "1.0.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" +checksum = "ec96c6a92621310b51366f1e28d05ef11489516e93be030060e5fc12024a49d6" dependencies = [ "unicode-ident", ] @@ -1820,11 +1803,11 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.4.1" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.5.0", ] [[package]] @@ -1877,7 +1860,7 @@ version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "566cafdd92868e0939d3fb961bd0dc25fcfaaed179291093b3d43e6b3150ea10" dependencies = [ - "base64 0.22.0", + "base64 0.22.1", "bytes", "encoding_rs", "futures-core", @@ -1940,15 +1923,15 @@ dependencies = [ [[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" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +checksum = "3450ed37fe9609abb6bc3b8891b6e078404e4c53c7332350e2e15126a95229bf" [[package]] name = "rustc-hex" @@ -1980,9 +1963,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.22.3" +version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99008d7ad0bbbea527ec27bddbc0e432c5b87d8175178cee68d2eec9c4a1813c" +checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" dependencies = [ "log", "ring", @@ -2011,21 +1994,21 @@ version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" dependencies = [ - "base64 0.22.0", + "base64 0.22.1", "rustls-pki-types", ] [[package]] name = "rustls-pki-types" -version = "1.4.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecd36cc4259e3e4514335c4a138c6b43171a8d61d8f5c9348f9fc7529416f247" +checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" [[package]] name = "rustls-webpki" -version = "0.102.2" +version = "0.102.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faaa0a62740bedb9b2ef5afa303da42764c012f743917351dc9a237ea1663610" +checksum = "ff448f7e92e913c4b7d4c6d8e4540a1724b319b4152b8aef6d4cf8339712b33e" dependencies = [ "ring", "rustls-pki-types", @@ -2034,21 +2017,21 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.15" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80af6f9131f277a45a3fba6ce8e2258037bb0477a67e610d3c1fe046ab31de47" +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 = "scale-info" -version = "2.11.2" +version = "2.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c453e59a955f81fb62ee5d596b450383d699f152d350e9d23a0db2adb78e4c0" +checksum = "eca070c12893629e2cc820a9761bedf6ce1dcddc9852984d1dc734b8bd9bd024" dependencies = [ "bitvec", "cfg-if", @@ -2060,11 +2043,11 @@ dependencies = [ [[package]] name = "scale-info-derive" -version = "2.11.2" +version = "2.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18cf6c6447f813ef19eb450e985bcce6705f9ce7660db221b59093d15c79c4b7" +checksum = "2d35494501194174bda522a32605929eefc9ecf7e0a326c26db1fdd85881eb62" dependencies = [ - "proc-macro-crate 1.3.1", + "proc-macro-crate", "proc-macro2", "quote", "syn 1.0.109", @@ -2118,11 +2101,11 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "770452e37cad93e0a50d5abc3990d2bc351c36d0328f86cefec2f2fb206eaef6" +checksum = "c627723fd09706bacdb5cf41499e95098555af3c3c29d014dc3c458ef6be11c0" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.5.0", "core-foundation", "core-foundation-sys", "libc", @@ -2131,9 +2114,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f3cc463c0ef97e11c3461a9d3787412d30e8e7eb907c79180c4a57bf7c04ef" +checksum = "317936bbbd05227752583946b9e66d7ce3b489f84e11a94a510b4437fef407d7" dependencies = [ "core-foundation-sys", "libc", @@ -2141,35 +2124,35 @@ 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.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.197" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.66", ] [[package]] name = "serde_json" -version = "1.0.116" +version = "1.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" +checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" dependencies = [ "itoa", "ryu", @@ -2188,9 +2171,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.5" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1" +checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0" dependencies = [ "serde", ] @@ -2252,9 +2235,9 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" dependencies = [ "libc", ] @@ -2308,9 +2291,9 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "socket2" -version = "0.5.6" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" dependencies = [ "libc", "windows-sys 0.52.0", @@ -2396,7 +2379,7 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "substrate-constructor" version = "0.1.0" -source = "git+https://github.com/Alzymologist/substrate-constructor#391e0760cde6eb88e787207f5c6b43f1f1c33caf" +source = "git+https://github.com/fluiderson/sc?branch=thiserror#7791cfce487f9675273f2e9540ebaf53f304d178" dependencies = [ "bitvec", "external-memory-tools", @@ -2410,12 +2393,13 @@ dependencies = [ "sp-crypto-hashing", "substrate-crypto-light", "substrate_parser", + "thiserror", ] [[package]] name = "substrate-crypto-light" version = "0.1.0" -source = "git+https://github.com/Alzymologist/substrate-crypto-light#7d933807f0ffaf150a069266083e8b2fc9cadf69" +source = "git+https://github.com/fluiderson/scl?branch=thiserror#1807aa5d008638ba06059024d07e83b7b59bc243" dependencies = [ "base58", "blake2b_simd", @@ -2429,6 +2413,7 @@ dependencies = [ "regex", "schnorrkel", "sha2", + "thiserror", "zeroize", ] @@ -2470,9 +2455,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.58" +version = "2.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687" +checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" dependencies = [ "proc-macro2", "quote", @@ -2532,22 +2517,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.58" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" +checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.58" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" +checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.66", ] [[package]] @@ -2562,9 +2547,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.35" +version = "0.3.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef89ece63debf11bc32d1ed8d078ac870cbeb44da02afb02a9ff135ae7ca0582" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ "deranged", "itoa", @@ -2617,7 +2602,7 @@ dependencies = [ "libc", "mio", "num_cpus", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "pin-project-lite", "signal-hook-registry", "socket2", @@ -2633,7 +2618,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.66", ] [[package]] @@ -2670,9 +2655,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.10" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" dependencies = [ "bytes", "futures-core", @@ -2682,7 +2667,6 @@ dependencies = [ "hashbrown", "pin-project-lite", "tokio", - "tracing", ] [[package]] @@ -2696,41 +2680,30 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.12" +version = "0.8.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9dd1545e8208b4a5af1aa9bbd0b4cf7e9ea08fabc5d0a5c67fcaafa17433aa3" +checksum = "a4e43f8cc456c9704c851ae29c67e17ef65d2c30017c17a9765b89c382dc8bba" dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.22.9", + "toml_edit 0.22.13", ] [[package]] name = "toml_datetime" -version = "0.6.5" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" +checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.19.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" -dependencies = [ - "indexmap", - "toml_datetime", - "winnow 0.5.40", -] - -[[package]] -name = "toml_edit" -version = "0.20.7" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81" +checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" dependencies = [ "indexmap", "toml_datetime", @@ -2739,15 +2712,15 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.9" +version = "0.22.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e40bb779c5187258fd7aad0eb68cb8706a0a81fa712fbea808ab43c4b8374c4" +checksum = "c127785850e8c20836d49732ae6abfa47616e60bf9d9f57c43c250361a9db96c" dependencies = [ "indexmap", "serde", "serde_spanned", "toml_datetime", - "winnow 0.6.5", + "winnow 0.6.8", ] [[package]] @@ -2763,7 +2736,6 @@ dependencies = [ "tokio", "tower-layer", "tower-service", - "tracing", ] [[package]] @@ -2784,7 +2756,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", @@ -2798,7 +2769,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.66", ] [[package]] @@ -2878,9 +2849,9 @@ checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-normalization" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" dependencies = [ "tinyvec", ] @@ -2893,11 +2864,11 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "ureq" -version = "2.9.6" +version = "2.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11f214ce18d8b2cbe84ed3aa6486ed3f5b285cf8d8fbdbce9f3f767a724adc35" +checksum = "d11a831e3c0b56e438a28308e7c810799e3c118417f342d30ecec080105395cd" dependencies = [ - "base64 0.21.7", + "base64 0.22.1", "log", "once_cell", "serde", @@ -2964,7 +2935,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.66", "wasm-bindgen-shared", ] @@ -2998,7 +2969,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.66", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -3056,7 +3027,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.4", + "windows-targets 0.52.5", ] [[package]] @@ -3076,17 +3047,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" 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 0.52.5", + "windows_aarch64_msvc 0.52.5", + "windows_i686_gnu 0.52.5", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.5", + "windows_x86_64_gnu 0.52.5", + "windows_x86_64_gnullvm 0.52.5", + "windows_x86_64_msvc 0.52.5", ] [[package]] @@ -3097,9 +3069,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" [[package]] name = "windows_aarch64_msvc" @@ -3109,9 +3081,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" [[package]] name = "windows_i686_gnu" @@ -3121,9 +3093,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" [[package]] name = "windows_i686_msvc" @@ -3133,9 +3111,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" [[package]] name = "windows_x86_64_gnu" @@ -3145,9 +3123,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" [[package]] name = "windows_x86_64_gnullvm" @@ -3157,9 +3135,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" [[package]] name = "windows_x86_64_msvc" @@ -3169,9 +3147,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" [[package]] name = "winnow" @@ -3184,9 +3162,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.6.5" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dffa400e67ed5a4dd237983829e66475f0a4a26938c4b04c21baede6262215b8" +checksum = "c3c52e9c97a68071b23e836c9380edae937f17b9c4667bd021973efc689f618d" dependencies = [ "memchr", ] @@ -3212,29 +3190,29 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.7.32" +version = "0.7.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.32" +version = "0.7.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.66", ] [[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", ] @@ -3247,5 +3225,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.66", ] diff --git a/Cargo.toml b/Cargo.toml index 1ed6e34..f5e1c72 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,39 +10,57 @@ readme = true keywords = ["substrate", "blockchain", "finance", "service", "middleware"] categories = ["finance"] -[dev-dependencies] -reqwest = { version = "0.12.4", features = ["json"] } -lazy_static = "1.4.0" - [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"] } +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"] } - serde = { version = "1", features = ["derive"] } tracing = "0.1" scale-info = "2" -axum-macros = "0.4.1" -primitive-types = { version = "0.12.2", features = ["codec"] } -substrate-crypto-light = { git = "https://github.com/Alzymologist/substrate-crypto-light" } -jsonrpsee = { version = "0.22.4", features = ["ws-client"] } -mnemonic-external = { git = "https://github.com/Alzymologist/mnemonic-external" } -thiserror = "1.0.58" +axum-macros = "0.4" +primitive-types = { version = "0.12", features = ["codec"] } +jsonrpsee = { version = "0.22", features = ["ws-client"] } +thiserror = "1" +frame-metadata = "16" +hex = "0.4" +parity-scale-codec = "3" +serde_json = "1" +sp-crypto-hashing = "0.1" +toml = "0.8" +sled = "0.34" +zeroize = "1.7" + substrate_parser = { git = "https://github.com/Alzymologist/substrate-parser" } substrate-constructor = { git = "https://github.com/Alzymologist/substrate-constructor" } -frame-metadata = "16.0.0" -hex = "0.4.3" -parity-scale-codec = "3.6.9" -serde_json = "1.0.116" -sp-crypto-hashing = "0.1.0" -toml = "0.8.12" -sled = "0.34.7" -zeroize = "1.7.0" +mnemonic-external = { git = "https://github.com/Alzymologist/mnemonic-external" } +substrate-crypto-light = { git = "https://github.com/Alzymologist/substrate-crypto-light" } + +[patch."https://github.com/Alzymologist/substrate-crypto-light"] +substrate-crypto-light = { git = "https://github.com/fluiderson/scl", branch = "thiserror" } + +[patch."https://github.com/Alzymologist/substrate-constructor"] +substrate-constructor = { git = "https://github.com/fluiderson/sc", branch = "thiserror" } + +[patch."https://github.com/Alzymologist/mnemonic-external"] +mnemonic-external = { git = "https://github.com/fluiderson/me", branch = "thiserror" } + +[dev-dependencies] +reqwest = { version = "0.12", features = ["json"] } +lazy_static = "1" [profile.release] strip = true @@ -52,8 +70,10 @@ codegen-units = 1 [lints.rust] future_incompatible = "warn" let_underscore = "warn" -#rust_2018_idioms = "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" @@ -61,4 +81,5 @@ shadow_same = "warn" shadow_unrelated = "warn" cargo_common_metadata = "warn" arithmetic_side_effects = "warn" -pedantic = "warn" +# TODO: https://github.com/rust-lang/cargo/issues/12918 +pedantic = { level = "warn", priority = -1 } diff --git a/src/chain/definitions.rs b/src/chain/definitions.rs index c5831b7..10a75f7 100644 --- a/src/chain/definitions.rs +++ b/src/chain/definitions.rs @@ -11,7 +11,7 @@ use crate::{ tracker::ChainWatcher, }, definitions::{api_v2::OrderInfo, Balance}, - error::{ErrorChain, NotHex}, + error::{ChainError, NotHex}, utils::unhex, }; @@ -28,12 +28,12 @@ impl BlockHash { /// Convert string returned by RPC to typesafe block /// /// TODO: integrate nicely with serde - pub fn from_str(s: &str) -> Result { + 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(|_| ErrorChain::BlockHashLength)?, + .map_err(|_| ChainError::BlockHashLength)?, ))) } } @@ -95,7 +95,7 @@ pub struct WatchAccount { pub currency: String, pub amount: Balance, pub recipient: AccountId32, - pub res: oneshot::Sender>, + pub res: oneshot::Sender>, } impl WatchAccount { @@ -103,12 +103,12 @@ impl WatchAccount { id: String, order: OrderInfo, recipient: AccountId32, - res: oneshot::Sender>, - ) -> Result { + res: oneshot::Sender>, + ) -> Result { Ok(WatchAccount { id, address: AccountId32::from_base58_string(&order.payment_account) - .map_err(ErrorChain::InvoiceAccount)? + .map_err(ChainError::InvoiceAccount)? .0, currency: order.currency.currency, amount: Balance::parse(order.amount, order.currency.decimals), @@ -151,11 +151,11 @@ impl Invoice { client: &WsClient, chain_watcher: &ChainWatcher, block: &BlockHash, - ) -> Result { + ) -> Result { let currency = chain_watcher .assets .get(&self.currency) - .ok_or(ErrorChain::InvalidCurrency(self.currency.clone()))?; + .ok_or(ChainError::InvalidCurrency(self.currency.clone()))?; if let Some(asset_id) = currency.asset_id { let balance = asset_balance_at_account( client, @@ -179,7 +179,7 @@ impl Invoice { client: &WsClient, chain_watcher: &ChainWatcher, block: &BlockHash, - ) -> Result { + ) -> Result { Ok(self.balance(client, chain_watcher, block).await? >= self.amount) } } diff --git a/src/chain/mod.rs b/src/chain/mod.rs index 409e25d..6f17c3e 100644 --- a/src/chain/mod.rs +++ b/src/chain/mod.rs @@ -11,7 +11,7 @@ use tokio_util::sync::CancellationToken; use crate::{ definitions::{api_v2::OrderInfo, Chain}, - error::{Error, ErrorChain}, + error::{Error, ChainError}, Signer, State, TaskTracker, }; @@ -99,12 +99,12 @@ impl ChainManager { } else { let _unused = request .res - .send(Err(ErrorChain::InvalidChain(chain.to_string()))); + .send(Err(ChainError::InvalidChain(chain.to_string()))); } } else { let _unused = request .res - .send(Err(ErrorChain::InvalidCurrency(request.currency))); + .send(Err(ChainError::InvalidCurrency(request.currency))); } } ChainRequest::Reap(request) => { @@ -115,12 +115,12 @@ impl ChainManager { } else { let _unused = request .res - .send(Err(ErrorChain::InvalidChain(chain.to_string()))); + .send(Err(ChainError::InvalidChain(chain.to_string()))); } } else { let _unused = request .res - .send(Err(ErrorChain::InvalidCurrency(request.currency))); + .send(Err(ChainError::InvalidCurrency(request.currency))); } } ChainRequest::Shutdown(res) => { @@ -149,15 +149,15 @@ impl ChainManager { id: String, order: OrderInfo, recipient: AccountId32, - ) -> Result<(), ErrorChain> { + ) -> Result<(), ChainError> { let (res, rx) = oneshot::channel(); self.tx .send(ChainRequest::WatchAccount(WatchAccount::new( id, order, recipient, res, )?)) .await - .map_err(|_| ErrorChain::MessageDropped)?; - rx.await.map_err(|_| ErrorChain::MessageDropped)? + .map_err(|_| ChainError::MessageDropped)?; + rx.await.map_err(|_| ChainError::MessageDropped)? } pub async fn reap( @@ -165,15 +165,15 @@ impl ChainManager { id: String, order: OrderInfo, recipient: AccountId32, - ) -> Result<(), ErrorChain> { + ) -> Result<(), ChainError> { let (res, rx) = oneshot::channel(); self.tx .send(ChainRequest::Reap(WatchAccount::new( id, order, recipient, res, )?)) .await - .map_err(|_| ErrorChain::MessageDropped)?; - rx.await.map_err(|_| ErrorChain::MessageDropped)? + .map_err(|_| ChainError::MessageDropped)?; + rx.await.map_err(|_| ChainError::MessageDropped)? } pub async fn shutdown(&self) -> () { diff --git a/src/chain/payout.rs b/src/chain/payout.rs index e5694ce..9a7627a 100644 --- a/src/chain/payout.rs +++ b/src/chain/payout.rs @@ -16,7 +16,7 @@ use crate::{ }, }, definitions::api_v2::TokenKind, - error::ErrorChain, + error::ChainError, signer::Signer, state::State, }; @@ -34,7 +34,7 @@ pub async fn payout( state: State, chain: ChainWatcher, signer: Signer, -) -> Result<(), ErrorChain> { +) -> Result<(), ChainError> { // TODO: make this retry and rotate RPCs maybe // // after some retries record a failure @@ -47,7 +47,7 @@ pub async fn payout( let currency = chain .assets .get(&order.currency) - .ok_or(ErrorChain::InvalidCurrency(order.currency))?; + .ok_or(ChainError::InvalidCurrency(order.currency))?; // Payout operation logic let transactions = match balance.0 - order.amount.0 { @@ -65,7 +65,7 @@ pub async fn payout( } TokenKind::Asset => { let asset_transfer_constructor = AssetTransferConstructor { - asset_id: currency.asset_id.ok_or(ErrorChain::AssetId)?, + asset_id: currency.asset_id.ok_or(ChainError::AssetId)?, amount: order.amount.0, to_account: &order.recipient, }; @@ -97,7 +97,7 @@ pub async fn payout( let sign_this = batch_transaction .sign_this() - .ok_or(ErrorChain::TransactionNotSignable(format!( + .ok_or(ChainError::TransactionNotSignable(format!( "{batch_transaction:?}" )))?; @@ -108,7 +108,7 @@ pub async fn payout( let extrinsic = batch_transaction .send_this_signed::<(), RuntimeMetadataV15>(&chain.metadata)? - .ok_or(ErrorChain::NothingToSend)?; + .ok_or(ChainError::NothingToSend)?; send_stuff(&client, &format!("0x{}", hex::encode(extrinsic))).await?; diff --git a/src/chain/rpc.rs b/src/chain/rpc.rs index 9706a19..5bde7d3 100644 --- a/src/chain/rpc.rs +++ b/src/chain/rpc.rs @@ -13,7 +13,7 @@ use crate::{ api_v2::{AssetId, TokenKind}, Balance, }, - error::{ErrorChain, NotHex}, + error::{ChainError, NotHex}, utils::unhex, }; use frame_metadata::{ @@ -63,14 +63,14 @@ const AURA: &str = "AuraApi"; pub async fn runtime_version_identifier( client: &WsClient, block: &BlockHash, -) -> Result { +) -> Result { let value = client .request("state_getRuntimeVersion", rpc_params![block.to_string()]) .await?; Ok(value) } -pub async fn subscribe_blocks(client: &WsClient) -> Result, ErrorChain> { +pub async fn subscribe_blocks(client: &WsClient) -> Result, ChainError> { Ok(client .subscribe( "chain_subscribeFinalizedHeads", @@ -84,7 +84,7 @@ pub async fn get_value_from_storage( client: &WsClient, whole_key: &str, block: &BlockHash, -) -> Result { +) -> Result { let value: Value = client .request( "state_getStorage", @@ -99,7 +99,7 @@ pub async fn get_keys_from_storage( prefix: &str, storage_name: &str, block: &BlockHash, -) -> Result { +) -> Result { let storage_key_prefix = format!( "0x{}{}", hex::encode(twox_128(prefix.as_bytes())), @@ -123,24 +123,24 @@ pub async fn get_keys_from_storage( let keys: Value = client .request("state_getKeysPaged", params) .await - .map_err(ErrorChain::Client)?; + .map_err(ChainError::Client)?; Ok(keys) } /// fetch genesis hash, must be a hexadecimal string transformable into /// H256 format -pub async fn genesis_hash(client: &WsClient) -> Result { +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(ErrorChain::Client)?; + .map_err(ChainError::Client)?; match genesis_hash_request { Value::String(x) => BlockHash::from_str(&x), - _ => return Err(ErrorChain::GenesisHashFormat), + _ => return Err(ChainError::GenesisHashFormat), } } @@ -149,7 +149,7 @@ pub async fn genesis_hash(client: &WsClient) -> Result { pub async fn block_hash( client: &WsClient, number: Option, -) -> Result { +) -> Result { let rpc_params = if let Some(a) = number { rpc_params![a] } else { @@ -158,10 +158,10 @@ pub async fn block_hash( let block_hash_request: Value = client .request("chain_getBlockHash", rpc_params) .await - .map_err(ErrorChain::Client)?; + .map_err(ChainError::Client)?; match block_hash_request { Value::String(x) => BlockHash::from_str(&x), - _ => return Err(ErrorChain::BlockHashFormat), + _ => return Err(ChainError::BlockHashFormat), } } @@ -169,7 +169,7 @@ pub async fn block_hash( pub async fn metadata( client: &WsClient, block: &BlockHash, -) -> Result { +) -> Result { let metadata_request: Value = client .request( "state_call", @@ -180,29 +180,29 @@ pub async fn metadata( ], ) .await - .map_err(ErrorChain::Client)?; + .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(|_| ErrorChain::RawMetadataNotDecodeable)?; + .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(ErrorChain::NoMetadataV15), - Err(_) => return Err(ErrorChain::MetadataNotDecodeable), + Ok(_) => return Err(ChainError::NoMetadataV15), + Err(_) => return Err(ChainError::MetadataNotDecodeable), } } else { - return Err(ErrorChain::NoMetaPrefix); + return Err(ChainError::NoMetaPrefix); } } else { - return Err(ErrorChain::NoMetadataV15); + return Err(ChainError::NoMetadataV15); } } - _ => return Err(ErrorChain::MetadataFormat), + _ => return Err(ChainError::MetadataFormat), }; } @@ -211,28 +211,28 @@ pub async fn specs( client: &WsClient, metadata: &RuntimeMetadataV15, block: &BlockHash, -) -> Result { +) -> 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(ErrorChain::PropertiesFormat), + _ => return Err(ChainError::PropertiesFormat), } } -pub async fn next_block_number(blocks: &mut Subscription) -> Result { +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(ErrorChain::BlockSubscriptionTerminated), + None => Err(ChainError::BlockSubscriptionTerminated), } } pub async fn next_block( client: &WsClient, blocks: &mut Subscription, -) -> Result { +) -> Result { block_hash(&client, Some(next_block_number(blocks).await?)).await } @@ -253,7 +253,7 @@ pub async fn assets_set_at_block( metadata_v15: &RuntimeMetadataV15, rpc_url: &str, specs: ShortSpecs, -) -> Result, ErrorChain> { +) -> Result, ChainError> { let mut assets_set = HashMap::new(); let chain_name = >::spec_name_version(metadata_v15)?.spec_name; @@ -323,13 +323,13 @@ pub async fn assets_set_at_block( { Ok(value) } else { - Err(ErrorChain::AssetIdFormat) + Err(ChainError::AssetIdFormat) } } else { - Err(ErrorChain::AssetKeyEmpty) + Err(ChainError::AssetKeyEmpty) } } else { - Err(ErrorChain::AssetKeyNotSingleHash) + Err(ChainError::AssetKeyNotSingleHash) } }?; let mut verified_sufficient = false; @@ -350,7 +350,7 @@ pub async fn assets_set_at_block( if verified_sufficient { match &assets_metadata_storage_metadata.ty { StorageEntryType::Plain(_) => { - return Err(ErrorChain::AssetMetadataPlain) + return Err(ChainError::AssetMetadataPlain) } StorageEntryType::Map { hashers, @@ -499,16 +499,16 @@ pub async fn assets_set_at_block( } } else { return Err( - ErrorChain::AssetMetadataUnexpected, + ChainError::AssetMetadataUnexpected, ); } } } - _ => return Err(ErrorChain::AssetMetadataType), + _ => return Err(ChainError::AssetMetadataType), } } else { - return Err(ErrorChain::AssetMetadataMapSize); + return Err(ChainError::AssetMetadataMapSize); } } } @@ -527,7 +527,7 @@ pub async fn asset_balance_at_account( metadata_v15: &RuntimeMetadataV15, account_id: &AccountId32, asset_id: AssetId, -) -> Result { +) -> Result { let query = asset_balance_query(metadata_v15, account_id, asset_id)?; let value_fetch = get_value_from_storage(client, &query.key, block).await?; @@ -549,12 +549,12 @@ pub async fn asset_balance_at_account( return Ok(Balance(value)); } } - Err(ErrorChain::AssetBalanceNotFound) + Err(ChainError::AssetBalanceNotFound) } else { - Err(ErrorChain::AssetBalanceFormat) + Err(ChainError::AssetBalanceFormat) } } else { - Err(ErrorChain::StorageValueFormat(value_fetch)) + Err(ChainError::StorageValueFormat(value_fetch)) } } @@ -563,7 +563,7 @@ pub async fn system_balance_at_account( block: &BlockHash, metadata_v15: &RuntimeMetadataV15, account_id: &AccountId32, -) -> Result { +) -> Result { let query = system_balance_query(metadata_v15, account_id)?; let value_fetch = get_value_from_storage(client, &query.key, block).await?; @@ -596,14 +596,14 @@ pub async fn system_balance_at_account( } } - Err(ErrorChain::BalanceNotFound) + Err(ChainError::BalanceNotFound) } pub async fn transfer_events( client: &WsClient, block: &BlockHash, metadata_v15: &RuntimeMetadataV15, -) -> Result, ErrorChain> { +) -> Result, ChainError> { let events_entry_metadata = events_entry_metadata(&metadata_v15)?; events_at_block( @@ -625,7 +625,7 @@ async fn events_at_block( optional_filter: Option>, events_entry_metadata: &StorageEntryMetadata, types: &PortableRegistry, -) -> Result, ErrorChain> { +) -> Result, ChainError> { let keys_from_storage = get_keys_from_storage(client, "System", "Events", block).await?; let mut out = Vec::new(); match keys_from_storage { @@ -637,7 +637,7 @@ async fn events_at_block( let value_bytes = if let Value::String(data_from_storage) = data_from_storage { unhex(&data_from_storage, NotHex::StorageValue)? } else { - return Err(ErrorChain::StorageValueFormat(data_from_storage)); + return Err(ChainError::StorageValueFormat(data_from_storage)); }; let storage_data = decode_as_storage_entry::<&[u8], (), RuntimeMetadataV15>( &key_bytes.as_ref(), @@ -682,7 +682,7 @@ async fn events_at_block( }, _ => { tracing::warn!("{keys_from_storage}"); - return Err(ErrorChain::EventsMissing); + return Err(ChainError::EventsMissing); }, } } @@ -691,7 +691,7 @@ pub async fn current_block_number( client: &WsClient, metadata: &RuntimeMetadataV15, block: &BlockHash, -) -> Result { +) -> 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 { @@ -709,10 +709,10 @@ pub async fn current_block_number( { Ok(value) } else { - Err(ErrorChain::BlockNumberFormat) + Err(ChainError::BlockNumberFormat) } } else { - Err(ErrorChain::StorageValueFormat(fetched_value)) + Err(ChainError::StorageValueFormat(fetched_value)) } } @@ -727,7 +727,7 @@ pub async fn get_nonce( Ok(()) } -pub async fn send_stuff(client: &WsClient, data: &str) -> Result<(), ErrorChain> { +pub async fn send_stuff(client: &WsClient, data: &str) -> Result<(), ChainError> { let rpc_params = rpc_params![data]; let mut subscription: Subscription = client .subscribe("author_submitAndWatchExtrinsic", rpc_params, "") diff --git a/src/chain/tracker.rs b/src/chain/tracker.rs index 2d83e82..68b7ede 100644 --- a/src/chain/tracker.rs +++ b/src/chain/tracker.rs @@ -24,7 +24,7 @@ use crate::{ utils::{events_entry_metadata, was_balance_received_at_account}, }, definitions::{api_v2::CurrencyProperties, Chain}, - error::ErrorChain, + error::ChainError, signer::Signer, state::State, TaskTracker, @@ -38,7 +38,7 @@ pub fn start_chain_watch( signer: Signer, task_tracker: TaskTracker, cancellation_token: CancellationToken, -) -> Result<(), ErrorChain> { +) -> Result<(), ChainError> { task_tracker .clone() .spawn(format!("Chain {} watcher", chain.name.clone()), async move { @@ -173,14 +173,14 @@ impl ChainWatcher { chain_tx: mpsc::Sender, state: State, task_tracker: TaskTracker, - ) -> Result { + ) -> 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(ErrorChain::WrongNetwork{expected: chain.name, actual: name, rpc: rpc_url.to_string()}) }; + 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?; @@ -210,7 +210,7 @@ impl ChainWatcher { // 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(ErrorChain::AssetsInvalid(chain.name)); + return Err(ChainError::AssetsInvalid(chain.name)); } // this MUST assert that assets match exactly before reporting it diff --git a/src/chain/utils.rs b/src/chain/utils.rs index 3ee0315..60f548f 100644 --- a/src/chain/utils.rs +++ b/src/chain/utils.rs @@ -1,6 +1,6 @@ //! Utils to process chain data without accessing the chain -use crate::{chain::definitions::BlockHash, definitions::api_v2::AssetId, error::ErrorChain}; +use crate::{chain::definitions::BlockHash, definitions::api_v2::AssetId, error::ChainError}; use frame_metadata::{ v14::StorageHasher, v15::{RuntimeMetadataV15, StorageEntryMetadata, StorageEntryType}, @@ -40,7 +40,7 @@ pub struct AssetTransferConstructor<'a> { pub fn construct_single_asset_transfer_call( metadata: &RuntimeMetadataV15, asset_transfer_constructor: &AssetTransferConstructor, -) -> Result { +) -> Result { let mut call = prepare_type::<(), RuntimeMetadataV15>( &Ty::Symbol(&metadata.extrinsic.call_ty), &mut (), @@ -214,7 +214,7 @@ pub fn construct_batch_transaction( block: BlockHash, block_number: u32, nonce: u32, -) -> Result { +) -> Result { let mut transaction_to_fill = TransactionToFill::init(&mut (), metadata, genesis_hash.0)?; // deal with author @@ -309,7 +309,7 @@ pub fn construct_batch_transaction( pub fn construct_batch_call( metadata: &RuntimeMetadataV15, call_set: &[CallToFill], -) -> Result { +) -> Result { let mut call = prepare_type::<(), RuntimeMetadataV15>( &Ty::Symbol(&metadata.extrinsic.call_ty), &mut (), @@ -387,7 +387,7 @@ pub fn construct_batch_call( pub fn construct_single_balance_transfer_call( metadata: &RuntimeMetadataV15, balance_transfer_constructor: &BalanceTransferConstructor, -) -> Result { +) -> Result { let mut call = prepare_type::<(), RuntimeMetadataV15>( &Ty::Symbol(&metadata.extrinsic.call_ty), &mut (), @@ -547,7 +547,7 @@ pub fn construct_single_balance_transfer_call( pub fn block_number_query( metadata_v15: &RuntimeMetadataV15, -) -> Result { +) -> Result { let storage_selector = StorageSelector::init(&mut (), metadata_v15)?; if let StorageSelector::Functional(mut storage_selector_functional) = storage_selector { @@ -601,24 +601,24 @@ pub fn block_number_query( .query .finalize() .transpose() - .ok_or(ErrorChain::StorageQuery)??) + .ok_or(ChainError::StorageQuery)??) } else { - Err(ErrorChain::NoBlockNumberDefinition) + Err(ChainError::NoBlockNumberDefinition) } } else { - Err(ErrorChain::NoStorageInSystem) + Err(ChainError::NoStorageInSystem) } } else { - Err(ErrorChain::NoSystem) + Err(ChainError::NoSystem) } } else { - Err(ErrorChain::NoStorage) + Err(ChainError::NoStorage) } } pub fn events_entry_metadata( metadata: &RuntimeMetadataV15, -) -> Result<&StorageEntryMetadata, ErrorChain> { +) -> Result<&StorageEntryMetadata, ChainError> { let mut found_events_entry_metadata = None; for pallet in &metadata.pallets { if let Some(storage) = &pallet.storage { @@ -634,7 +634,7 @@ pub fn events_entry_metadata( } match found_events_entry_metadata { Some(a) => Ok(a), - None => Err(ErrorChain::EventsNonexistant), + None => Err(ChainError::EventsNonexistant), } } @@ -668,7 +668,7 @@ pub fn asset_balance_query( metadata_v15: &RuntimeMetadataV15, account_id: &AccountId32, asset_id: AssetId, -) -> Result { +) -> Result { let storage_selector = StorageSelector::init(&mut (), metadata_v15)?; if let StorageSelector::Functional(mut storage_selector_functional) = storage_selector { @@ -761,16 +761,16 @@ pub fn asset_balance_query( storage_selector_functional .query .finalize()? - .ok_or(ErrorChain::StorageQuery) + .ok_or(ChainError::StorageQuery) } else { - Err(ErrorChain::StorageQuery) + Err(ChainError::StorageQuery) } } pub fn system_balance_query( metadata_v15: &RuntimeMetadataV15, account_id: &AccountId32, -) -> Result { +) -> Result { let storage_selector = StorageSelector::init(&mut (), metadata_v15)?; let mut index_system_in_pallet_selector = None; @@ -838,9 +838,9 @@ pub fn system_balance_query( storage_selector_functional .query .finalize()? - .ok_or(ErrorChain::StorageQuery) + .ok_or(ChainError::StorageQuery) } else { - Err(ErrorChain::StorageQuery) + Err(ChainError::StorageQuery) } } @@ -861,7 +861,7 @@ pub fn whole_key_u32_value( storage_name: &str, metadata_v15: &RuntimeMetadataV15, entered_data: u32, -) -> Result { +) -> Result { for pallet in metadata_v15.pallets.iter() { if let Some(storage) = &pallet.storage { if storage.prefix == prefix { @@ -869,7 +869,7 @@ pub fn whole_key_u32_value( if entry.name == storage_name { match &entry.ty { StorageEntryType::Plain(_) => { - return Err(ErrorChain::StorageEntryNotMap) + return Err(ChainError::StorageEntryNotMap) } StorageEntryType::Map { hashers, @@ -894,23 +894,23 @@ pub fn whole_key_u32_value( )) )) } - _ => return Err(ErrorChain::StorageKeyNotU32), + _ => return Err(ChainError::StorageKeyNotU32), } } else { - return Err(ErrorChain::StorageEntryMapMultiple); + return Err(ChainError::StorageEntryMapMultiple); } } } } } - return Err(ErrorChain::StorageKeyNotFound(storage_name.to_string())); + return Err(ChainError::StorageKeyNotFound(storage_name.to_string())); } } } - Err(ErrorChain::NoPallet) + Err(ChainError::NoPallet) } -pub fn decimals(x: &Map) -> Result { +pub fn decimals(x: &Map) -> Result { match x.get("tokenDecimals") { // decimals info is fetched in `system_properties` rpc call Some(a) => match a { @@ -923,15 +923,11 @@ pub fn decimals(x: &Map) -> Result { Ok(d) => Ok(d), // this `u64` does not fit into `u8`, this is an error - Err(_) => Err(ErrorChain::DecimalsFormatNotSupported { - value: a.to_string(), - }), + Err(_) => Err(ChainError::DecimalsFormatNotSupported(a.to_string())), }, // number could not be represented as `u64`, this is an error - None => Err(ErrorChain::DecimalsFormatNotSupported { - value: a.to_string(), - }), + None => Err(ChainError::DecimalsFormatNotSupported(a.to_string())), }, // fetched decimals is an array @@ -952,39 +948,31 @@ pub fn decimals(x: &Map) -> Result { // this `u64` does not fit into `u8`, this is an // error - Err(_) => Err(ErrorChain::DecimalsFormatNotSupported { - value: a.to_string(), - }), + Err(_) => { + Err(ChainError::DecimalsFormatNotSupported(a.to_string())) + } }, // number could not be represented as `u64`, this is // an error - None => Err(ErrorChain::DecimalsFormatNotSupported { - value: a.to_string(), - }), + None => Err(ChainError::DecimalsFormatNotSupported(a.to_string())), } } else { // element is not a number, this is an error - Err(ErrorChain::DecimalsFormatNotSupported { - value: a.to_string(), - }) + Err(ChainError::DecimalsFormatNotSupported(a.to_string())) } } else { // decimals are an array with more than one element - Err(ErrorChain::DecimalsFormatNotSupported { - value: a.to_string(), - }) + Err(ChainError::DecimalsFormatNotSupported(a.to_string())) } } // unexpected decimals format - _ => Err(ErrorChain::DecimalsFormatNotSupported { - value: a.to_string(), - }), + _ => Err(ChainError::DecimalsFormatNotSupported(a.to_string())), }, // decimals are missing - None => Err(ErrorChain::NoDecimals), + None => Err(ChainError::NoDecimals), } } @@ -1071,7 +1059,7 @@ pub fn fetch_constant( pub fn system_properties_to_short_specs( system_properties: &Map, metadata: &RuntimeMetadataV15, -) -> Result { +) -> 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)?; @@ -1103,7 +1091,7 @@ pub fn storage_key(prefix: &str, storage_name: &str) -> String { pub fn base58prefix( x: &Map, optional_prefix_from_meta: Option, -) -> Result { +) -> Result { let base58prefix: u16 = match x.get("ss58Format") { // base58 prefix is fetched in `system_properties` rpc call Some(a) => match a { @@ -1123,7 +1111,7 @@ pub fn base58prefix( if prefix_from_meta == d { d } else { - return Err(ErrorChain::Base58PrefixMismatch { + return Err(ChainError::Base58PrefixMismatch { specs: d, meta: prefix_from_meta, }); @@ -1137,27 +1125,17 @@ pub fn base58prefix( // `u64` value does not fit into `u16` base58 prefix format, // this is an error Err(_) => { - return Err(ErrorChain::Base58PrefixFormatNotSupported { - value: a.to_string(), - }) + return Err(ChainError::Base58PrefixFormatNotSupported(a.to_string())) } }, // base58 prefix value could not be presented as `u64` number, // this is an error - None => { - return Err(ErrorChain::Base58PrefixFormatNotSupported { - value: a.to_string(), - }) - } + None => return Err(ChainError::Base58PrefixFormatNotSupported(a.to_string())), }, // base58 prefix value is not a number, this is an error - _ => { - return Err(ErrorChain::Base58PrefixFormatNotSupported { - value: a.to_string(), - }) - } + _ => return Err(ChainError::Base58PrefixFormatNotSupported(a.to_string())), }, // no base58 prefix fetched in `system_properties` rpc call @@ -1167,13 +1145,13 @@ pub fn base58prefix( Some(prefix_from_meta) => prefix_from_meta, // no base58 prefix at all, this is an error - None => return Err(ErrorChain::NoBase58Prefix), + None => return Err(ChainError::NoBase58Prefix), }, }; Ok(base58prefix) } -pub fn unit(x: &Map) -> Result { +pub fn unit(x: &Map) -> Result { match x.get("tokenSymbol") { // unit info is fetched in `system_properties` rpc call Some(a) => match a { @@ -1193,25 +1171,19 @@ pub fn unit(x: &Map) -> Result { Ok(c.to_string()) } else { // element is not a `String`, this is an error - Err(ErrorChain::UnitFormatNotSupported { - value: a.to_string(), - }) + Err(ChainError::UnitFormatNotSupported(a.to_string())) } } else { // units are an array with more than one element - Err(ErrorChain::UnitFormatNotSupported { - value: a.to_string(), - }) + Err(ChainError::UnitFormatNotSupported(a.to_string())) } } // unexpected unit format - _ => Err(ErrorChain::UnitFormatNotSupported { - value: a.to_string(), - }), + _ => Err(ChainError::UnitFormatNotSupported(a.to_string())), }, // unit missing - None => Err(ErrorChain::NoUnit), + None => Err(ChainError::NoUnit), } } diff --git a/src/database.rs b/src/database.rs index a091fe6..9466fa9 100644 --- a/src/database.rs +++ b/src/database.rs @@ -12,7 +12,7 @@ use crate::{ }, Balance, Nonce, }, - error::{Error, ErrorDb}, + error::{Error, DbError}, TaskTracker, }; use parity_scale_codec::{Compact, Decode, Encode}; @@ -175,16 +175,16 @@ impl Database { let database = if let Some(path) = path_option { tracing::info!("Creating/Opening the database at {path:?}."); - sled::open(path).map_err(ErrorDb::DbStartError)? + sled::open(path).map_err(DbError::DbStartError)? } else { // TODO /* tracing::warn!( "The in-memory backend for the database is selected. All saved data will be deleted after the shutdown!" );*/ - sled::open("temp.db").map_err(ErrorDb::DbStartError)? + sled::open("temp.db").map_err(DbError::DbStartError)? }; - let orders = database.open_tree(ORDERS).map_err(ErrorDb::DbStartError)?; + let orders = database.open_tree(ORDERS).map_err(DbError::DbStartError)?; task_tracker.spawn("Database server", async move { // No process forking beyond this point! @@ -238,17 +238,17 @@ impl Database { Ok(Self { tx }) } - pub async fn order_list(&self) -> Result, ErrorDb> { + 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(|_| ErrorDb::DbEngineDown)? + rx.await.map_err(|_| DbError::DbEngineDown)? } pub async fn create_order( &self, order: String, order_info: OrderInfo, - ) -> Result { + ) -> Result { let (res, rx) = oneshot::channel(); let _unused = self .tx @@ -258,43 +258,43 @@ impl Database { res, })) .await; - rx.await.map_err(|_| ErrorDb::DbEngineDown)? + rx.await.map_err(|_| DbError::DbEngineDown)? } - pub async fn read_order(&self, order: String) -> Result, ErrorDb> { + 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(|_| ErrorDb::DbEngineDown)? + rx.await.map_err(|_| DbError::DbEngineDown)? } - pub async fn mark_paid(&self, order: String) -> Result { + 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(|_| ErrorDb::DbEngineDown)? + rx.await.map_err(|_| DbError::DbEngineDown)? } - pub async fn mark_withdrawn(&self, order: String) -> Result<(), ErrorDb> { + 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(|_| ErrorDb::DbEngineDown)? + rx.await.map_err(|_| DbError::DbEngineDown)? } - pub async fn mark_stuck(&self, order: String) -> Result<(), ErrorDb> { + 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(|_| ErrorDb::DbEngineDown)? + rx.await.map_err(|_| DbError::DbEngineDown)? } pub async fn shutdown(&self) { @@ -306,7 +306,7 @@ impl Database { enum DbRequest { CreateOrder(CreateOrder), - ActiveOrderList(oneshot::Sender, ErrorDb>>), + ActiveOrderList(oneshot::Sender, DbError>>), ReadOrder(ReadOrder), MarkPaid(MarkPaid), MarkWithdrawn(ModifyOrder), @@ -317,29 +317,29 @@ enum DbRequest { pub struct CreateOrder { pub order: String, pub order_info: OrderInfo, - pub res: oneshot::Sender>, + pub res: oneshot::Sender>, } pub struct ReadOrder { pub order: String, - pub res: oneshot::Sender, ErrorDb>>, + pub res: oneshot::Sender, DbError>>, } pub struct ModifyOrder { pub order: String, - pub res: oneshot::Sender>, + pub res: oneshot::Sender>, } pub struct MarkPaid { pub order: String, - pub res: oneshot::Sender>, + pub res: oneshot::Sender>, } fn create_order( order: String, order_info: OrderInfo, orders: &sled::Tree, -) -> Result { +) -> Result { Ok(match orders.get(&order)? { Some(record) => { let old_order_info = OrderInfo::decode(&mut &record[..])?; @@ -358,7 +358,7 @@ fn create_order( }) } -fn read_order(order: String, orders: &sled::Tree) -> Result, ErrorDb> { +fn read_order(order: String, orders: &sled::Tree) -> Result, DbError> { if let Some(order) = orders.get(order)? { Ok(Some(OrderInfo::decode(&mut &order[..])?)) } else { @@ -366,7 +366,7 @@ fn read_order(order: String, orders: &sled::Tree) -> Result, E } } -fn mark_paid(order: String, orders: &sled::Tree) -> Result { +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 { @@ -374,13 +374,13 @@ fn mark_paid(order: String, orders: &sled::Tree) -> Result { orders.insert(order.encode(), order_info.encode())?; Ok(order_info) } else { - Err(ErrorDb::AlreadyPaid(order)) + Err(DbError::AlreadyPaid(order)) } } else { - Err(ErrorDb::OrderNotFound(order)) + Err(DbError::OrderNotFound(order)) } } -fn mark_withdrawn(order: String, orders: &sled::Tree) -> Result<(), ErrorDb> { +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 { @@ -389,16 +389,16 @@ fn mark_withdrawn(order: String, orders: &sled::Tree) -> Result<(), ErrorDb> { orders.insert(order.encode(), order_info.encode())?; Ok(()) } else { - Err(ErrorDb::WithdrawalWasAttempted(order)) + Err(DbError::WithdrawalWasAttempted(order)) } } else { - Err(ErrorDb::NotPaid(order)) + Err(DbError::NotPaid(order)) } } else { - Err(ErrorDb::OrderNotFound(order)) + Err(DbError::OrderNotFound(order)) } } -fn mark_stuck(order: String, orders: &sled::Tree) -> Result<(), ErrorDb> { +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 { @@ -407,13 +407,13 @@ fn mark_stuck(order: String, orders: &sled::Tree) -> Result<(), ErrorDb> { orders.insert(order.encode(), order_info.encode())?; Ok(()) } else { - Err(ErrorDb::WithdrawalWasAttempted(order)) + Err(DbError::WithdrawalWasAttempted(order)) } } else { - Err(ErrorDb::NotPaid(order)) + Err(DbError::NotPaid(order)) } } else { - Err(ErrorDb::OrderNotFound(order)) + Err(DbError::OrderNotFound(order)) } } //impl StateInterface { diff --git a/src/error.rs b/src/error.rs index e69b814..e37e5b9 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,118 +1,91 @@ -use crate::{definitions::api_v2::OrderStatus, SocketAddr}; +use crate::definitions::api_v2::OrderStatus; use frame_metadata::v15::RuntimeMetadataV15; -use jsonrpsee::core::client::error::Error as ClientError; +use jsonrpsee::core::ClientError; use mnemonic_external::error::ErrorWordList; -use primitive_types::H256; +use parity_scale_codec::Error as ScaleError; use serde_json::Value; use sled::Error as DatabaseError; -use substrate_constructor::error::*; +use std::net::SocketAddr; +use std::{ + error::Error as StdError, + fmt::{Display, Formatter, Result}, + io::Error as IoError, +}; +use substrate_constructor::error::{ErrorFixMe, StorageRegistryError}; use substrate_crypto_light::error::Error as CryptoError; -use substrate_parser::error::*; +use substrate_parser::error::{MetaVersionErrorPallets, ParserError, RegistryError, StorageError}; +use thiserror::Error; use tokio::task::JoinError; +use toml::de::Error as TomlError; -#[derive(Debug, thiserror::Error)] +#[derive(Debug, Error)] pub enum Error { - #[error("failed to read `{0}`")] + #[error("failed to read the {0:?} environment variable")] Env(String), - #[error("failed to read a config file at {0:?}")] + #[error("failed to read the config file at {0:?}")] ConfigFileRead(String), - #[error("failed to parse the config at {0}")] - ConfigFileParse(toml::de::Error), + #[error("failed to parse the config")] + ConfigFileParse(#[from] TomlError), - #[error("failed to parse the config parameter {0}")] + #[error("failed to parse the config parameter {0:?}")] ConfigParse(String), - #[error("chain {0} doesn't have any `endpoints` in the config")] + #[error("chain {0:?} doesn't have any `endpoints` in the config")] EmptyEndpoints(String), - #[error("RPC server error: {0:?}")] - ErrorChain(ErrorChain), + #[error("RPC server error is occurred")] + Chain(#[from] ChainError), - #[error("Database error: {0:?}")] - ErrorDb(ErrorDb), + #[error("database error is occurred")] + Db(#[from] DbError), - #[error("Order error: {0:?}")] - ErrorOrder(ErrorOrder), + #[error("order error is occurred")] + Order(#[from] OrderError), - #[error("Daemon server error: {0:?}")] - ErrorServer(ErrorServer), + #[error("daemon server error is occurred")] + Server(#[from] ServerError), - #[error("Signer error: {0:?}")] - ErrorSigner(ErrorSigner), + #[error("signer error is occurred")] + Signer(#[from] SignerError), - #[error("Failed to listen to shutdown signal")] + #[error("failed to listen for the shutdown signal")] ShutdownSignal, - #[error("Receiver account could not be parsed: {0:?}")] - RecipientAccount(substrate_crypto_light::error::Error), + #[error("receiver account couldn't be parsed")] + RecipientAccount(#[from] CryptoError), - #[error("Fatal error. System is shutting down.")] + #[error("fatal error is occurred")] Fatal, - #[error("Operating system related I/O error {0:?}")] - IoError(std::io::Error), + #[error("operating system related I/O error is occurred")] + Io(#[from] IoError), - #[error("Duplicate config record for token {0}")] + #[error("found duplicate config record for the token {0:?}")] DuplicateCurrency(String), } -impl From for Error { - fn from(e: ErrorDb) -> Error { - Error::ErrorDb(e) - } -} - -impl From for Error { - fn from(e: ErrorChain) -> Error { - Error::ErrorChain(e) - } -} - -impl From for Error { - fn from(e: ErrorOrder) -> Error { - Error::ErrorOrder(e) - } -} - -impl From for Error { - fn from(e: ErrorServer) -> Error { - Error::ErrorServer(e) - } -} - -impl From for Error { - fn from(e: ErrorSigner) -> Error { - Error::ErrorSigner(e) - } -} - -impl From for Error { - fn from(e: std::io::Error) -> Self { - Error::IoError(e) - } -} - -#[derive(Debug, thiserror::Error)] -pub enum ErrorChain { +#[derive(Debug, Error)] +#[allow(clippy::module_name_repetitions)] +pub enum ChainError { // TODO: this should be prevented by typesafety - #[error("Asset id is missing")] + #[error("asset ID is missing")] AssetId, - #[error("Asset id is not u32")] + #[error("asset ID isn't `u32`")] AssetIdFormat, - #[error("Invalid assets for chain {0}")] + #[error("invalid assets for the chain {0:?}")] AssetsInvalid(String), - #[error("Asset key has no parceable part")] + #[error("asset key has no parceable part")] AssetKeyEmpty, - #[error("Asset key is not single hash")] + #[error("asset key isn't single hash")] AssetKeyNotSingleHash, - #[error("Asset metadata is not a map")] + #[error("asset metadata isn't a map")] AssetMetadataPlain, #[error("unexpected assets metadata value structure")] @@ -121,40 +94,40 @@ pub enum ErrorChain { #[error("wrong data type")] AssetMetadataType, - #[error("expected map with single entry, got multiple entries")] + #[error("expected a map with a single entry, got multiple entries")] AssetMetadataMapSize, - #[error("Asset balance format is unexpected")] + #[error("asset balance format is unexpected")] AssetBalanceFormat, - #[error("No balance field in asset record")] + #[error("no balance field in an asset record")] AssetBalanceNotFound, - #[error("Format of fetched base58 prefix {value} is not supported.")] - Base58PrefixFormatNotSupported { value: String }, + #[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.")] + #[error("Base58 prefixes in metadata ({meta:?}) and specs ({specs:?}) do not match.")] Base58PrefixMismatch { specs: u16, meta: u16 }, - #[error("Unexpected block number format.")] + #[error("unexpected block number format")] BlockNumberFormat, - #[error("Unexpected block hash format.")] + #[error("unexpected block hash format")] BlockHashFormat, - #[error("Unexpected block hash length.")] + #[error("unexpected block hash length")] BlockHashLength, - #[error("Ws client error. {0}")] - Client(ClientError), + #[error("WS client error is occurred")] + Client(#[from] ClientError), - #[error("Threading error. {0}")] - Tokio(JoinError), + #[error("threading error is occurred")] + Tokio(#[from] JoinError), - #[error("Format of fetched decimals {value} is not supported.")] - DecimalsFormatNotSupported { value: String }, + #[error("format of fetched decimals ({0}) isn't supported")] + DecimalsFormatNotSupported(String), - #[error("Unexpected genesis hash format.")] + #[error("unexpected genesis hash format")] GenesisHashFormat, #[error("...")] @@ -163,34 +136,34 @@ pub enum ErrorChain { #[error("...")] MetadataNotDecodeable, - #[error("No base58 prefix is fetched as system properties or found in the metadata.")] + #[error("no Base58 prefix is fetched as system properties or found in metadata")] NoBase58Prefix, - #[error("Block number definition not found")] + #[error("block number definition isn't found")] NoBlockNumberDefinition, - #[error("No decimals value is fetched.")] + #[error("no decimals value is fetched")] NoDecimals, - #[error("Metadata v15 not available through rpc.")] + #[error("metadata v15 isn't available through RPC")] NoMetadataV15, - #[error("Metadata must start with `meta` prefix.")] + #[error("metadata must start with the `meta` prefix")] NoMetaPrefix, - #[error("Pallet not found")] + #[error("pallet isn't found")] NoPallet, - #[error("No pallets with storage found")] + #[error("no pallets with a storage found")] NoStorage, - #[error("Pallet System not found")] + #[error("\"System\" pallet isn't found")] NoSystem, - #[error("No storage variants in system pallet")] + #[error("no storage variants in the \"System\" pallet")] NoStorageInSystem, - #[error("No unit value is fetched.")] + #[error("no unit value is fetched")] NoUnit, #[error("...")] @@ -199,10 +172,10 @@ pub enum ErrorChain { #[error("...")] RawMetadataNotDecodeable, - #[error("Format of fetched unit {value} is not supported.")] - UnitFormatNotSupported { value: String }, + #[error("format of the fetched unit ({0}) isn't supported")] + UnitFormatNotSupported(String), - #[error("Unexpected storage value format for key {0:?}")] + #[error("unexpected storage value format for the key \"{0:?}\"")] StorageValueFormat(Value), //#[error("Chain returned zero for block time")] @@ -216,273 +189,255 @@ pub enum ErrorChain { //#[error("Aura slot duration could not be parsed as u64")] //AuraSlotDurationFormat, - #[error("Internal error: {0:?}")] // TODO this should be replaced by specific errors - ErrorUtil(ErrorUtil), + #[error("internal error is occurred")] // TODO this should be replaced by specific errors + Util(#[from] UtilError), - #[error("Invoice account could not be parsed: {0:?}")] - InvoiceAccount(substrate_crypto_light::error::Error), + #[error("invoice account couldn't be parsed")] + InvoiceAccount(#[from] CryptoError), - #[error("Chain {0} not found")] + #[error("chain {0:?} isn't found")] InvalidChain(String), - #[error("Currency {0} not found")] + #[error("currency {0:?} isn't found")] InvalidCurrency(String), - #[error("Chain manager dropped a message, probably due to chain disconnect; maybe it should be sent again")] + #[error("chain manager dropped a message, probably due to a chain disconnect; maybe it should be sent again")] MessageDropped, - #[error("Block subscription terminated")] + #[error("block subscription is terminated")] BlockSubscriptionTerminated, - #[error("Metadata error: {0:?}")] - MetaVersionErrorPallets(MetaVersionErrorPallets), + #[error("metadata error is occurred")] + MetaVersionErrorPallets(#[from] MetaVersionErrorPallets), - #[error("Storage registry error: {0:?}")] - StorageRegistryError(StorageRegistryError), + #[error("storage registry error is occurred")] + StorageRegistryError(#[from] StorageRegistryError), - #[error("Balance was not found")] + #[error("balance wasn't found")] BalanceNotFound, - #[error("Storage query could not be formed")] + #[error("storage query couldn't be formed")] StorageQuery, - #[error("Events could not be fetched")] + #[error("events couldn't be fetched")] EventsMissing, - #[error("Events do not exist in this chain")] + #[error("no events in this chain")] EventsNonexistant, - #[error("Substrate parser error: {0}")] - ParserError(ParserError<()>), + #[error("substrate parser error is occurred")] + ParserError(#[from] ParserError<()>), - #[error("Storage entry decoding error: {0}")] - StorageDecodeError(StorageError<()>), + #[error("storage entry decoding error is occurred")] + StorageDecodeError(#[from] StorageError<()>), - #[error("Type registry error: {0}")] - RegistryError(RegistryError<()>), + #[error("type registry error is occurred")] + RegistryError(#[from] RegistryError<()>), - #[error("Substrate constructor error: {0:?}")] - SubstrateConstructor(ErrorFixMe<(), RuntimeMetadataV15>), + #[error("substrate constructor error is occurred")] + SubstrateConstructor(#[from] ErrorFixMe<(), RuntimeMetadataV15>), - #[error("Transaction is not ready to be signed: {0}")] + #[error("transaction isn't ready to be signed: {0:?}")] TransactionNotSignable(String), - #[error("Signing failed: {0}")] - Signer(ErrorSigner), + #[error("signing was failed")] + Signer(#[from] SignerError), - #[error("Transaction could not be completed")] + #[error("transaction couldn't be completed")] NothingToSend, - #[error("Storage entry is not a map")] + #[error("storage entry isn't a map")] StorageEntryNotMap, - #[error("Storage entry map has more than one records")] + #[error("storage entry map has more than one record")] StorageEntryMapMultiple, - #[error("Storage key {0} not found")] + #[error("storage key {0:?} isn't found")] StorageKeyNotFound(String), - #[error("Storage key is not u32")] + #[error("storage key isn't `u32`")] StorageKeyNotU32, - #[error("RPC runs unexpected network: instead of {expected}, found {actual} at {rpc}")] - WrongNetwork{expected: String, actual: String, rpc: String}, -} - -impl From for ErrorChain { - fn from(e: ClientError) -> Self { - ErrorChain::Client(e) - } -} - -impl From for ErrorChain { - fn from(e: ErrorSigner) -> Self { - ErrorChain::Signer(e) - } -} - -impl From for ErrorChain { - fn from(e: JoinError) -> Self { - ErrorChain::Tokio(e) - } -} - -impl From for ErrorChain { - fn from(e: ErrorUtil) -> Self { - ErrorChain::ErrorUtil(e) - } -} - -impl From for ErrorChain { - fn from(e: MetaVersionErrorPallets) -> Self { - ErrorChain::MetaVersionErrorPallets(e) - } -} - -impl From for ErrorChain { - fn from(e: StorageRegistryError) -> Self { - ErrorChain::StorageRegistryError(e) - } -} - -impl From> for ErrorChain { - fn from(e: ParserError<()>) -> Self { - ErrorChain::ParserError(e) - } -} - -impl From> for ErrorChain { - fn from(e: StorageError<()>) -> Self { - ErrorChain::StorageDecodeError(e) - } -} - -impl From> for ErrorChain { - fn from(e: RegistryError<()>) -> Self { - ErrorChain::RegistryError(e) - } -} - -impl From> for ErrorChain { - fn from(e: ErrorFixMe<(), RuntimeMetadataV15>) -> Self { - ErrorChain::SubstrateConstructor(e) - } + #[error( + "RPC runs on an unexpected network: instead of {expected:?}, found {actual:?} at {rpc:?}" + )] + WrongNetwork { + expected: String, + actual: String, + rpc: String, + }, } -#[derive(Debug, thiserror::Error)] -pub enum ErrorDb { - #[error("Currency key is not found")] +#[derive(Debug, Error)] +#[allow(clippy::module_name_repetitions)] +pub enum DbError { + #[error("currency key isn't found")] CurrencyKeyNotFound, - #[error("Database engine is not running")] + #[error("database engine isn't running")] DbEngineDown, - #[error("Database internal error: {0:?}")] - DbInternalError(DatabaseError), + #[error("database internal error is occurred")] + DbInternalError(#[from] DatabaseError), - #[error("Could not start database service: {0:?}")] + #[error("failed to start the database service")] DbStartError(DatabaseError), - #[error("Operating system related I/O error {0:?}")] - IoError(std::io::Error), + #[error("operating system related I/O error is occurred")] + IoError(#[from] IoError), - #[error("Database storage decoding error: {0:?}")] - CodecError(parity_scale_codec::Error), + #[error("database storage decoding error is occurred")] + CodecError(#[from] ScaleError), - #[error("Order {0} not found")] + #[error("order {0:?} isn't found")] OrderNotFound(String), - #[error("Order {0} was already paid")] + #[error("order {0:?} was already paid")] AlreadyPaid(String), - #[error("Order {0} is not paid yet")] + #[error("order {0:?} isn't paid yet")] NotPaid(String), - #[error("There was already an attempt to withdraw order {0}")] + #[error("there was already an attempt to withdraw order {0:?}")] WithdrawalWasAttempted(String), } -impl From for ErrorDb { - fn from(e: sled::Error) -> Self { - ErrorDb::DbInternalError(e) - } -} - -impl From for ErrorDb { - fn from(e: parity_scale_codec::Error) -> Self { - ErrorDb::CodecError(e) - } -} - -impl From for ErrorDb { - fn from(e: std::io::Error) -> Self { - ErrorDb::IoError(e) - } -} - -#[derive(Debug, thiserror::Error)] -pub enum ErrorOrder { - #[error("Invoice amount is less than existential deposit")] +#[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")] + #[error("unknown currency")] UnknownCurrency, - #[error("Order parameter missing: {0}")] + #[error("order parameter is missing: {0:?}")] MissingParameter(String), - #[error("Order parameter invalid: {0}")] + #[error("order parameter invalid: {0:?}")] InvalidParameter(String), - #[error("Internal error")] + #[error("internal error is occurred")] InternalError, } -#[derive(Debug, thiserror::Error)] -pub enum ErrorForceWithdrawal { - #[error("Order parameter missing: {0}")] +#[derive(Debug, Error)] +#[allow(clippy::module_name_repetitions)] +pub enum ForceWithdrawalError { + #[error("order parameter is missing: {0:?}")] MissingParameter(String), - #[error("Order parameter invalid: {0}")] + #[error("order parameter is invalid: {0:?}")] InvalidParameter(String), - #[error("Withdrawal failed: {0:?}")] - WithdrawalError(OrderStatus), + #[error("withdrawal was failed: \"{0:?}\"")] + WithdrawalError(Box), } #[derive(Debug, thiserror::Error)] -pub enum ErrorServer { - #[error("failed to bind the TCP listener to {0:?}")] +#[allow(clippy::module_name_repetitions)] +pub enum ServerError { + #[error("failed to bind the TCP listener to \"{0:?}\"")] TcpListenerBind(SocketAddr), - #[error("Internal threading error")] + #[error("internal threading error is occurred")] ThreadError, } -#[derive(Debug, thiserror::Error)] -pub enum ErrorUtil { - #[error("{0}")] +#[derive(Debug, Error)] +#[allow(clippy::module_name_repetitions)] +pub enum UtilError { + #[error("...")] NotHex(NotHex), } -#[derive(Debug, thiserror::Error)] -pub enum ErrorSigner { - #[error("failed to read `{0}`")] +#[derive(Debug, Error)] +#[allow(clippy::module_name_repetitions)] +pub enum SignerError { + #[error("failed to read {0:?}")] Env(String), - #[error("Signer is down")] + #[error("signer is down")] SignerDown, - #[error("Seed phrase invalid: {0:?}")] - InvalidSeed(ErrorWordList), + #[error("seed phrase is invalid")] + InvalidSeed(#[from] ErrorWordList), - #[error("Derivation failed: {0:?}")] - InvalidDerivation(CryptoError), -} - -impl From for ErrorSigner { - fn from(e: ErrorWordList) -> Self { - ErrorSigner::InvalidSeed(e) - } -} - -impl From for ErrorSigner { - fn from(e: CryptoError) -> Self { - ErrorSigner::InvalidDerivation(e) - } + #[error("derivation was failed")] + InvalidDerivation(#[from] CryptoError), } #[derive(Debug, Eq, PartialEq, thiserror::Error)] pub enum NotHex { - #[error("Block hash string is not a valid hexadecimal.")] + #[error("block hash string isn't a valid hexadecimal")] BlockHash, - #[error("Encoded metadata string is not a valid hexadecimal.")] + #[error("encoded metadata string isn't a valid hexadecimal")] Metadata, - #[error("Encoded storage key string is not a valid hexadecimal.")] + #[error("encoded storage key string isn't a valid hexadecimal")] StorageKey, - #[error("Encoded storage value string is not a valid hexadecimal.")] + #[error("encoded storage value string isn't a valid hexadecimal")] StorageValue, } + +pub struct PrettyCauseWrapper<'a, T>(&'a T); + +pub trait PrettyCause { + fn pretty_cause(&self) -> PrettyCauseWrapper<'_, T>; +} + +impl PrettyCause for T { + fn pretty_cause(&self) -> PrettyCauseWrapper<'_, T> { + PrettyCauseWrapper(self) + } +} + +impl Display for PrettyCauseWrapper<'_, 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("."); + }; + let mut number = 0u64; + + let mut print_cause = |cause_to_print, number_to_print| { + f.write_str("\n")?; + + write!(f, "{number_to_print:>5}: ")?; + + Display::fmt(cause_to_print, f)?; + + f.write_str(".") + }; + + // Otherwise, print a numbered list of error sources. + + print_cause(cause, number)?; + + loop { + number = number.saturating_add(1); + + print_cause(another_cause, number)?; + + if let Some(one_more_cause) = another_cause.source() { + another_cause = one_more_cause; + } else { + break Ok(()); + } + } + } +} diff --git a/src/main.rs b/src/main.rs index 3162072..b08dd8d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,6 @@ use serde::Deserialize; use std::{ borrow::Cow, - collections::HashMap, env::{self, VarError}, error::Error as _, fs, @@ -29,10 +28,10 @@ mod signer; mod state; mod utils; -use crate::definitions::{Chain, Entropy, Version}; use chain::ChainManager; use database::ConfigWoChains; -use error::Error; +use definitions::{Chain, Version}; +use error::{Error, PrettyCause}; use signer::Signer; use state::State; @@ -48,8 +47,17 @@ const DEFAULT_CONFIG: &str = "configs/polkadot.toml"; const DEFAULT_SOCKET: SocketAddr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 16726); const DEFAULT_DATABASE: &str = "kalatori.db"; +fn main() { + if let Err(error) = try_main() { + println!( + "Badbye! The daemon's got a fatal error during an initialization: {error}.{}", + error.pretty_cause() + ); + } +} + #[tokio::main] -async fn main() -> Result<(), Error> { +async fn try_main() -> Result<(), Error> { let shutdown_notification = CancellationToken::new(); set_panic_hook(shutdown_notification.clone()); @@ -318,7 +326,10 @@ impl TaskTracker { drop(self.error_tx); while let Some((from, error)) = error_rx.recv().await { - tracing::error!("Received a fatal error from {from}:\n{error:?}"); + tracing::error!( + "Received a fatal error from {from}:\n{error:?}.{}", + error.pretty_cause() + ); if !shutdown_notification.is_cancelled() { tracing::info!("Initialising the shutdown..."); diff --git a/src/server.rs b/src/server.rs index 1bf9973..dd7c0c0 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,6 +1,6 @@ use crate::{ definitions::api_v2::*, - error::{Error, ErrorForceWithdrawal, ErrorOrder, ErrorServer}, + error::{Error, ForceWithdrawalError, OrderError, ServerError}, state::State, }; use axum::{ @@ -22,7 +22,7 @@ pub async fn new( shutdown_notification: CancellationToken, host: SocketAddr, state: State, -) -> Result, Error>>, ErrorServer> { +) -> Result, Error>>, ServerError> { let v2: Router = Router::new() .route("/order/:order_id", routing::post(order)) .route( @@ -43,13 +43,13 @@ pub async fn new( let listener = TcpListener::bind(host) .await - .map_err(|_| ErrorServer::TcpListenerBind(host))?; + .map_err(|_| ServerError::TcpListenerBind(host))?; Ok(async { axum::serve(listener, app) .with_graceful_shutdown(shutdown_notification.cancelled_owned()) .await - .map_err(|_| ErrorServer::ThreadError)?; + .map_err(|_| ServerError::ThreadError)?; Ok("The server module is shut down.".into()) }) @@ -66,41 +66,41 @@ async fn process_order( matched_path: &MatchedPath, path_result: Result, query: &HashMap, -) -> Result { +) -> Result { const ORDER_ID: &str = "order_id"; let path_parameters = - path_result.map_err(|_| ErrorOrder::InvalidParameter(matched_path.as_str().to_owned()))?; + 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(|| ErrorOrder::MissingParameter(ORDER_ID.into()))? + .ok_or_else(|| OrderError::MissingParameter(ORDER_ID.into()))? .to_owned(); if query.is_empty() { state .order_status(&order) .await - .map_err(|_| ErrorOrder::InternalError) + .map_err(|_| OrderError::InternalError) } else { let get_parameter = |parameter: &str| { query .get(parameter) - .ok_or_else(|| ErrorOrder::MissingParameter(parameter.into())) + .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(|_| ErrorOrder::InvalidParameter(AMOUNT.into()))?; + .map_err(|_| OrderError::InvalidParameter(AMOUNT.into()))?; if currency != "USDC" { - return Err(ErrorOrder::UnknownCurrency); + return Err(OrderError::UnknownCurrency); } if amount < 0.07 { - return Err(ErrorOrder::LessThanExistentialDeposit(0.07)); + return Err(OrderError::LessThanExistentialDeposit(0.07)); } state @@ -111,7 +111,7 @@ async fn process_order( currency, }) .await - .map_err(|_| ErrorOrder::InternalError) + .map_err(|_| OrderError::InternalError) } } @@ -131,7 +131,7 @@ async fn order( OrderResponse::NotFound => (StatusCode::NOT_FOUND, "").into_response(), }, Err(error) => match error { - ErrorOrder::LessThanExistentialDeposit(existential_deposit) => ( + OrderError::LessThanExistentialDeposit(existential_deposit) => ( StatusCode::BAD_REQUEST, Json([InvalidParameter { parameter: AMOUNT.into(), @@ -139,7 +139,7 @@ async fn order( }]), ) .into_response(), - ErrorOrder::UnknownCurrency => ( + OrderError::UnknownCurrency => ( StatusCode::BAD_REQUEST, Json([InvalidParameter { parameter: CURRENCY.into(), @@ -147,7 +147,7 @@ async fn order( }]), ) .into_response(), - ErrorOrder::MissingParameter(parameter) => ( + OrderError::MissingParameter(parameter) => ( StatusCode::BAD_REQUEST, Json([InvalidParameter { parameter, @@ -155,7 +155,7 @@ async fn order( }]), ) .into_response(), - ErrorOrder::InvalidParameter(parameter) => ( + OrderError::InvalidParameter(parameter) => ( StatusCode::BAD_REQUEST, Json([InvalidParameter { parameter, @@ -163,7 +163,7 @@ async fn order( }]), ) .into_response(), - ErrorOrder::InternalError => StatusCode::INTERNAL_SERVER_ERROR.into_response(), + OrderError::InternalError => StatusCode::INTERNAL_SERVER_ERROR.into_response(), }, } } @@ -172,20 +172,20 @@ async fn process_force_withdrawal( state: State, matched_path: &MatchedPath, path_result: Result, -) -> Result { +) -> Result { const ORDER_ID: &str = "order_id"; let path_parameters = path_result - .map_err(|_| ErrorForceWithdrawal::InvalidParameter(matched_path.as_str().to_owned()))?; + .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(|| ErrorForceWithdrawal::MissingParameter(ORDER_ID.into()))? + .ok_or_else(|| ForceWithdrawalError::MissingParameter(ORDER_ID.into()))? .to_owned(); state .force_withdrawal(order) .await - .map_err(ErrorForceWithdrawal::WithdrawalError) + .map_err(|e| ForceWithdrawalError::WithdrawalError(e.into())) } #[debug_handler] @@ -196,10 +196,10 @@ async fn force_withdrawal( ) -> Response { match process_force_withdrawal(state, &matched_path, path_result).await { Ok(a) => (StatusCode::CREATED, Json(a)).into_response(), - Err(ErrorForceWithdrawal::WithdrawalError(a)) => { + Err(ForceWithdrawalError::WithdrawalError(a)) => { (StatusCode::BAD_REQUEST, Json(a)).into_response() } - Err(ErrorForceWithdrawal::MissingParameter(parameter)) => ( + Err(ForceWithdrawalError::MissingParameter(parameter)) => ( StatusCode::BAD_REQUEST, Json([InvalidParameter { parameter, @@ -207,7 +207,7 @@ async fn force_withdrawal( }]), ) .into_response(), - Err(ErrorForceWithdrawal::InvalidParameter(parameter)) => ( + Err(ForceWithdrawalError::InvalidParameter(parameter)) => ( StatusCode::BAD_REQUEST, Json([InvalidParameter { parameter, diff --git a/src/signer.rs b/src/signer.rs index 673aa44..4e9b574 100644 --- a/src/signer.rs +++ b/src/signer.rs @@ -11,7 +11,7 @@ use crate::{ definitions::Entropy, - error::{Error, ErrorSigner}, + error::{Error, SignerError}, TaskTracker, }; @@ -77,22 +77,22 @@ impl Signer { Ok(Self { tx }) } - pub async fn public(&self, id: String, ss58: u16) -> Result { + 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(|_| ErrorSigner::SignerDown)?; - rx.await.map_err(|_| ErrorSigner::SignerDown)? + .map_err(|_| SignerError::SignerDown)?; + rx.await.map_err(|_| SignerError::SignerDown)? } - pub async fn sign(&self, id: String, signable: Vec) -> Result { + 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(|_| ErrorSigner::SignerDown)?; - rx.await.map_err(|_| ErrorSigner::SignerDown)? + .map_err(|_| SignerError::SignerDown)?; + rx.await.map_err(|_| SignerError::SignerDown)? } pub async fn shutdown(&self) { @@ -125,25 +125,25 @@ enum SignerRequest { struct PublicKeyRequest { id: String, ss58: u16, - res: oneshot::Sender>, + res: oneshot::Sender>, } /// Bytes to sign, with callback struct Sign { id: String, signable: Vec, - res: oneshot::Sender>, + res: oneshot::Sender>, } /// Read seeds from env /// /// TODO: read also old seeds and do something about them -fn parse_seeds() -> Result { - entropy_from_phrase(&env::var(SEED).map_err(|_| ErrorSigner::Env(SEED.to_string()))?) +fn parse_seeds() -> Result { + entropy_from_phrase(&env::var(SEED).map_err(|_| SignerError::Env(SEED.to_string()))?) } /// Convert seed phrase to entropy -pub fn entropy_from_phrase(seed: &str) -> Result { +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)?; diff --git a/src/state.rs b/src/state.rs index 90721d2..0e8dcd5 100644 --- a/src/state.rs +++ b/src/state.rs @@ -8,7 +8,7 @@ use crate::{ }, Entropy, }, - error::{Error, ErrorOrder}, + error::{Error, OrderError}, signer::Signer, ConfigWoChains, TaskTracker, }; @@ -278,7 +278,7 @@ impl StateData { let currency = self .currencies .get(&order_query.currency) - .ok_or(ErrorOrder::UnknownCurrency)?; + .ok_or(OrderError::UnknownCurrency)?; let currency = currency.info(order_query.currency.clone()); let payment_account = self.signer.public(order.clone(), currency.ss58).await?; let order_info = OrderInfo::new(order_query, currency, payment_account); diff --git a/src/unused b/src/unused deleted file mode 100644 index 833e385..0000000 --- a/src/unused +++ /dev/null @@ -1,941 +0,0 @@ -/* -type ConnectedChainsChannel = ( - Sender>, - (String, ConnectedChain), -); - -type CurrenciesChannel = ( - Sender>, - (String, CurrencyProperties), -); - -struct AssetsInfoFetcher<'a> { - assets: (AssetInfo, Vec), - pallet_index: Option, -} -*/ -/* -async fn fetch_finalized_head_number_and_hash( - methods: &LegacyRpcMethods, -) -> Result<(BlockNumber, BlockHash)> { - let head_hash = methods - .chain_get_finalized_head() - .await - .context("failed to get the finalized head hash")?; - let head = 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)) -} -*/ -/* -impl AssetProperties { - async fn fetch(asset: AssetId) -> Result { - Ok(Self { - min_balance: check_sufficiency_and_fetch_min_balance(asset).await?, - decimals: fetch_asset_decimals(asset).await?, - }) - } -}*/ -/* -impl ChainProperties { - async fn fetch( - chain: &str, - currencies: Sender, - native_token_option: Option, - assets_fetcher: Option>, - account_lifetime: BlockNumber, - depth: Option, - ) -> Result { - //const ADDRESS_PREFIX: (&str, &str) = (SYSTEM, "SS58Prefix"); - const EXISTENTIAL_DEPOSIT: "ExistentialDeposit";//(&str, &str) = (BALANCES, "ExistentialDeposit"); - const BLOCK_HASH_COUNT: "BlockHashCount";//(&str, &str) = (SYSTEM, "BlockHashCount"); - - /* wtf is this? - let try_add_currency = |name, asset| async move { - let (tx, rx) = oneshot::channel(); - - currencies - .send((tx, (name, CurrencyProperties { - chain_name: chain.to_owned(), - kind: , - decimals: , - rpc_url: , - asset_if: , - }))) - .unwrap(); - - if let Some(( - name, - Currency { - chain: other_chain, .. - }, - )) = rx.await.unwrap() - { - Err(anyhow::anyhow!( - "chain {other_chain:?} already has the native token or an asset with the name {name:?}, all currency names must be unique" - )) - } else { - Ok(()) - } - };*/ - - let specs = specs(); - - let assets_pallet = if let Some(AssetsInfoFetcher { - assets: (last_asset_info, assets_info), - storage, - pallet_index, - }) = assets_fetcher - { - async fn try_add_asset( - assets: &mut HashMap, - id: AssetId, - chain: &str, - ) -> Result<()> { - match assets.entry(id) { - Entry::Occupied(_) => Err(anyhow::anyhow!( - "chain {chain} has 2 assets with the same ID {id}", - )), - Entry::Vacant(entry) => { - entry.insert(AssetProperties::fetch(storage, id).await?); - - Ok(()) - } - } - } - - let mut assets = HashMap::with_capacity(assets_info.len().saturating_add(1)); - - for asset_info in assets_info { - //try_add_currency.clone()(asset_info.name, Some(asset_info.id)).await?; - try_add_asset(&mut assets, asset_info.id, chain, storage).await?; - } - - //try_add_currency.clone()(last_asset_info.name, Some(last_asset_info.id)).await?; - try_add_asset(&mut assets, last_asset_info.id, chain, storage).await?; - - Some(AssetsPallet { - assets, - multi_location: pallet_index, - }) - } else { - None - }; - let block_hash_count = fetch_constant(constants, SYSTEM, BLOCK_HASH_COUNT)?; - - //Some(native_token.decimals), - - let existential_deposit = if let Some(_native_token) = native_token_option { - Some(fetch_constant(metadata, BALANCES, ).map(Balance)?) - } else { - None - }; - - let chain = Self { - specs, - existential_deposit, - assets_pallet, - block_hash_count, - account_lifetime, - depth, - }; - - Ok(chain) - } -}*/ -/* -async fn check_sufficiency_and_fetch_min_balance( - client: &WsClient, - asset: AssetId, - block: BlockHash, -) -> Result { - const ASSET: &str = "Asset"; - const MIN_BALANCE: &str = "min_balance"; - const IS_SUFFICIENT: &str = "is_sufficient"; - - let asset_info = storage_fetch(client, ASSETS, ASSET, block); - /* - .fetch(&dynamic::storage(ASSETS, ASSET, vec![asset.into()])) - .await - .with_context(|| format!("failed to fetch asset {asset} info from a chain"))? - .with_context(|| { - format!("received nothing after fetching asset info {asset} from a chain") - })? - .to_value() - .with_context(|| format!("failed to decode asset {asset} info"))?; - */ - - let encoded_is_sufficient = asset_info - .at(IS_SUFFICIENT) - .with_context(|| format!("{IS_SUFFICIENT} field wasn't found in asset {asset} info"))?; - - if !encoded_is_sufficient.as_bool().with_context(|| { - format!( - "expected `bool` as the type of {IS_SUFFICIENT:?} in asset {asset} info, got `{:?}`", - encoded_is_sufficient.value - ) - })? { - anyhow::bail!("only sufficient assets are supported, asset {asset} isn't sufficient"); - } - - let encoded_min_balance = asset_info - .at(MIN_BALANCE) - .with_context(|| format!("{MIN_BALANCE} field wasn't found in asset {asset} info"))?; - - encoded_min_balance.as_u128().map(Balance).with_context(|| { - format!( - "expected `u128` as the type of {MIN_BALANCE:?} in asset {asset} info, got `{:?}`", - encoded_min_balance.value - ) - }) -}*/ -/* -async fn fetch_asset_decimals( - client: &WsClient, - asset: AssetId, - block: &BlockHash, -) -> Result { - const METADATA: &str = "Metadata"; - const DECIMALS: &str = "decimals"; - - let asset_metadata = storage_fetch(client, ASSETS, METADATA, block); - /*storage - .fetch(&dynamic::storage(ASSETS, METADATA, vec![asset.into()])) - .await - .with_context(|| format!("failed to fetch asset {asset} metadata from a chain"))? - .with_context(|| { - format!("received nothing after fetching asset {asset} metadata from a chain") - })? - .to_value() - .with_context(|| format!("failed to decode asset {asset} metadata"))?;*/ - let encoded_decimals = asset_metadata - .at(DECIMALS) - .with_context(|| format!("{DECIMALS} field wasn't found in asset {asset} metadata"))?; - - let decimals = encoded_decimals.as_u128().with_context(|| { - format!( - "expected `u128` as the type of asset {asset} {DECIMALS:?}, got `{:?}`", - encoded_decimals.value - ) - })?; - - decimals.try_into().with_context(|| { - format!("asset {asset} {DECIMALS:?} must be less than `u8`, got {decimals}") - }) -}*/ -/* -pub async fn prepare( - chains: Vec, - account_lifetime: Timestamp, - depth: Option, -) -> Result<( - HashMap, - HashMap, -), ErrorChain> { - let mut connected_chains = HashMap::with_capacity(chains.len()); - let mut currencies = HashMap::with_capacity( - chains - .iter() - .map(|chain| { - chain - .asset - .as_ref() - .map(Vec::len) - .unwrap_or_default() - .saturating_add(chain.native_token.is_some().into()) - }) - .sum(), - ); - - let (connected_chains_tx, mut connected_chains_rx) = - mpsc::channel::(1024); - let (currencies_tx, mut currencies_rx) = mpsc::channel::(1024); - - let connected_chains_jh = tokio::spawn(async move { - while let Some((tx, (name, chain))) = connected_chains_rx.recv().await { - tx.send(match connected_chains.entry(name) { - Entry::Occupied(entry) => Some(entry.remove_entry()), - Entry::Vacant(entry) => { - tracing::info!("Prepared the {:?} chain:\n{:#?}", entry.key(), chain); - - entry.insert(chain); - - None - } - }) - .unwrap(); - } - - connected_chains - }); - - let currencies_jh = tokio::spawn(async move { - while let Some((tx, (name, currency))) = currencies_rx.recv().await { - tx.send(match currencies.entry(name) { - Entry::Occupied(entry) => Some(entry.remove_entry()), - Entry::Vacant(entry) => { - tracing::info!( - %currency.chain_name, ?currency.asset_id, - "Registered the currency {:?}.", - entry.key(), - ); - - entry.insert(currency); - - None - } - }) - .unwrap(); - } - - currencies - }); - - let (task_tracker, error_rx) = TaskTracker::new(); - - for chain in chains { - task_tracker.spawn( - format!("the {:?} chain preparator", chain.name), - prepare_chain( - chain, - connected_chains_tx.clone(), - currencies_tx.clone(), - account_lifetime, - depth, - ), - ); - } - - drop((connected_chains_tx, currencies_tx)); - - task_tracker.try_wait(error_rx).await?; - - Ok((connected_chains_jh.await?, currencies_jh.await?)) -} -*/ -/* -async fn state_call(client: &WsClient, params: RpcParams) -> Result { - let res = client - .request( - "state_call", - params, - ) - .await - .map_err(ErrorChain::Client)?; - if let Value::String(a) = res { Ok(a) } else { Err(ErrorChain::StateCallResponse(res)) } -} - - -#[tracing::instrument(skip_all, fields(chain = chain.name))] -async fn prepare_chain( - chain: Chain, - connected_chains: Sender, - currencies: Sender, - account_lifetime: Timestamp, - depth_option: Option, -) -> Result, ErrorChain> { - let chain_name = chain.name; - let endpoint = chain - .endpoints - .first().ok_or(ErrorChain::EmptyEndpoints)?; - let client = WsClientBuilder::default() - .build(&endpoint) - .await - .map_err(ErrorChain::Client)?; - - let genesis = genesis_hash(&client).await?; - - let finalized_hash = block_hash(&client).await?; - let block_hash_string = finalized_hash.to_string(); - let metadata = metadata(&client, &finalized_hash).await?; - let specs = specs(&client, &metadata, &finalized_hash).await?; - - //TODO: we don't have to require this mechanism at all - let block_time = if pallet_index(&metadata, BABE).is_some() { - match fetch_constant(&metadata, BABE, "ExpectedBlockTime").ok_or(ErrorChain::BabeExpectedBlockTime)?.data { - - } - } else { - //const SLOT_DURATION: &str = "slot_duration"; - - state_call( - &client, - rpc_params![ - "Aura_slot_duration", - &block_hash_string - ], - ) - .await? - .parse::() - .map_err(|_| ErrorChain::AuraSlotDurationFormat)? - - /* - ( - runtime_api - .at(finalized_hash) - .call(dynamic::runtime_api_call( - AURA, - SLOT_DURATION, - Vec::::new(), - )) - .await - .context("failed to fetch Aura's slot duration")? - .as_type() - .context("failed to decode Aura's slot duration")?, - Some(runtime_api), - )*/ - }; - - let block_time_non_zero = - NonZeroU64::new(block_time).ok_or(ErrorChain::ZeroBlockTime)?;//.context("block interval can't equal 0")?; - - let account_lifetime_in_blocks = account_lifetime / block_time_non_zero; -/* TODO - if account_lifetime_in_blocks == 0 { - anyhow::bail!("block interval is longer than the given `account-lifetime`"); - } -*/ -/* - let depth_in_blocks = if let Some(depth) = depth_option { - let depth_in_blocks = depth / block_time_non_zero; - - if depth_in_blocks > account_lifetime_in_blocks { - anyhow::bail!("`depth` can't be greater than `account-lifetime`"); - } - - Some( - NonZeroU64::new(depth_in_blocks) - .context("block interval is longer than the given `depth`")?, - ) - } else { - None - }; -*/ - let rpc = endpoint.into(); -/* - let assets_info_fetcher = if let Some(assets) = chain - .asset - .and_then(|mut assets| assets.pop().map(|latest| (latest, assets))) - { - const ASSET_ID: &str = "asset_id"; - const SOME: &str = "Some"; - - let extension = metadata - .extrinsic() - .signed_extensions() - .iter() - .find(|extension| extension.identifier() == CHARGE_ASSET_TX_PAYMENT) - .with_context(|| { - format!("failed to find the {CHARGE_ASSET_TX_PAYMENT:?} extension in metadata") - })? - .extra_ty(); - let types = metadata.types(); - - let TypeDef::Composite(ref extension_type) = types - .resolve(extension);/* - .with_context(|| { - format!("failed to resolve the type of the {CHARGE_ASSET_TX_PAYMENT:?} extension") - })? - .type_def - else { - anyhow::bail!("{CHARGE_ASSET_TX_PAYMENT:?} extension has an unexpected type"); - };*/ - - let asset_id_field = extension_type - .fields - .iter() - .find_map(|field| { - field - .name - .as_ref() - .and_then(|name| (name == ASSET_ID).then_some(field.ty.id)) - });/* - .with_context(|| { - format!( - "failed to find the field {ASSET_ID:?} in the {CHARGE_ASSET_TX_PAYMENT:?} extension" - ) - })?;*/ - - let TypeDef::Variant(ref option) = types.resolve(asset_id_field); - /*.with_context(|| { - format!( - "failed to resolve the type of the field {ASSET_ID:?} in the {CHARGE_ASSET_TX_PAYMENT:?} extension" - ) - })?.type_def else { - anyhow::bail!( - "field {ASSET_ID:?} in the {CHARGE_ASSET_TX_PAYMENT:?} extension has an unexpected type" - ); - };*/ - - let asset_id_some = option.variants.iter().find_map(|variant| { - if variant.name == SOME { - variant.fields.first().map(|field| { - if variant.fields.len() > 1 { - tracing::warn!( - ?variant.fields, - "The field {ASSET_ID:?} in the {CHARGE_ASSET_TX_PAYMENT:?} extension contains multiple inner fields instead of just 1." - ); - } - - field.ty.id - }) - } else { - None - } - })/*.with_context(|| format!( - "field {ASSET_ID:?} in the {CHARGE_ASSET_TX_PAYMENT:?} extension doesn't contain the {SOME:?} variant" - ))?*/; - - let asset_id = &types.resolve(asset_id_some);/*.with_context(|| { - format!( - "failed to resolve the type of the {SOME:?} variant of the field {ASSET_ID:?} in the {CHARGE_ASSET_TX_PAYMENT:?} extension" - ) - })?.type_def;*/ - - let pallet_index = if let TypeDef::Primitive(_) = asset_id { - None - } else { - pallet_index(metadata, ASSETS) - }; - Some(AssetsInfoFetcher { - assets, - pallet_index, - }) - } else { - None - }; - - let storage = if assets_info_fetcher.is_some() { - Some(storage_client) - } else { - None - }; -*/ - /* - let properties = ChainProperties::fetch( - &chain_name, - currencies, - chain.native_token, - assets_info_fetcher, - account_lifetime_in_blocks, - depth_in_blocks, - ) - .await?; - - let connected_chain = ConnectedChain { - //methods, - genesis, - rpc, - client, - properties, - }; - - let (tx, rx) = oneshot::channel(); - - connected_chains - .send((tx, (chain_name, connected_chain))) - .unwrap(); - - if let Some((name, _)) = rx.await.unwrap() { - anyhow::bail!( - "found `[chain]`s with the same name ({name:?}) in the config, all chain names must be unique", - ); - } -*/ - Ok("".into()) -} -*/ - - - - /* - .execute() - .await - .or_else(|error| { - error - .downcast() - .map(|Shutdown| "The RPC module is shut down.".into()) - })*/ - - /* - async fn execute(mut self) -> Result> { - let (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; - - next_unscanned_number = head_number.checked_add(1).context(MAX_BLOCK_NUMBER_ERROR)?; - subscription = self.finalized_heads().await?; - - loop { - self.process_finalized_heads(subscription, &mut next_unscanned_number) - .await?; - - tracing::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, BlockHash)> { - 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 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: BlockHash) -> Result<()> { - tracing::debug!("Processing the 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 invoices = &mut *self.state.invoices.write().await; - - // 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(); - - #[allow(clippy::single_match)] - match (metadata.pallet.name(), &*metadata.variant.name) { - // (SYSTEM, UPDATE) => update = true, - (ASSETS, TRANSFERRED) => { - let tr = Transferred::deserialize( - event - .field_values() - .context("failed to decode event's fields")?, - ) - .context("failed to deserialize a transfer event")?; - - tracing::info!("{tr:?}"); - /* TODO process using cache and db access - #[allow(clippy::unnecessary_find_map)] - if let Some(invoic) = invoices.iter().find_map(|invoic| { - tracing::info!("{tr:?} {invoic:?}"); - tracing::info!("{}", tr.to == invoic.1.paym_acc); - tracing::info!("{}", *invoic.1.amount >= tr.amount); - - if tr.to == invoic.1.paym_acc && *invoic.1.amount <= tr.amount { - Some(invoic) - } else { - None - } - }) { - tracing::info!("{invoic:?}"); - if !invoic.1.callback.is_empty() { - tracing::info!("{:?}", invoic.1.callback); - - crate::callback::callback( - invoic.1.callback.clone(), - invoic.0.to_string(), - self.state.recipient.clone(), - self.state.debug, - self.state.remark.clone(), - invoic.1.amount, - self.state.rpc.clone(), - invoic.1.paym_acc.clone(), - ) - .await; - } - - invoices.insert( - invoic.0.clone(), - Invoicee { - callback: invoic.1.callback.clone(), - amount: Balance(*invoic.1.amount), - paid: true, - paym_acc: invoic.1.paym_acc.clone(), - }, - ); - }*/ - } - _ => {} - } - } - - // 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")?; - // } - - Ok(()) - } - - async fn balance(&self, hash: BlockHash, account: &AccountId) -> Result { - const ACCOUNT: &str = "Account"; - const BALANCE: &str = "balance"; - /* TODO: this should fetch balance and also considet native-nonnative shit wtf is this? - if let Some(account_info) = self - .storage - .at(hash) - .fetch(&dynamic::storage( - ASSETS, - ACCOUNT, - vec![ - Value::from(1337u32), - 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().map(Balance).with_context(|| { - format!("expected `u128` as the type of a balance, got {encoded_balance}") - }) - } else { - Ok(Balance(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, Asset::Id(1337)); - */ - //TODO create tx - /* - self.client - .tx() - .create_signed(&call, signer, extensions.build()) - .await - .context("failed to create a transfer transaction") - */ - } - */ - - // async fn current_nonce(&self, account: &AccountId) -> Result { - // self.api - // .blocks - // .at(self.finalized_head_number_and_hash().await?.0) - // .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: BlockHash, - // invoice: AccountId, - // 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: &AccountId, amount: u128) -> Value { - const TRANSFER_KEEP_ALIVE: &str = "transfer"; - - dbg!(amount); - - dynamic::tx( - ASSETS, - TRANSFER_KEEP_ALIVE, - vec![ - 1337.into(), - scale_value::value!(Id(Value::from_bytes(to))), - amount.into(), - ], - ) - .into_value() -} -*/ -// impl Transferred { -// fn process( -// self, -// invoices_changes: &mut HashMap, -// invoices: &mut HashMap, -// ) -> Result<()> { -// let usd_asset = 1337u32; - -// tracing::debug!("Transferred event: {self:?}"); - -// if self.from == self.to || self.amount == 0 || self.asset_id != usd_asset { -// return Ok(()); -// } - -// match invoices_changes.entry(self.to) { -// Entry::Occupied(entry) => { -// entry -// .into_mut() -// .incoming -// .entry(self.from) -// .and_modify(|amount| *amount = Balance(amount.saturating_add(self.amount))) -// .or_insert(Balance(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/utils.rs b/src/utils.rs index 48ceef6..4fe6677 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,9 +1,9 @@ -use crate::error::{ErrorUtil, NotHex}; +use crate::error::{UtilError, NotHex}; -pub fn unhex(hex_data: &str, what_is_hex: NotHex) -> Result, ErrorUtil> { +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(|_| ErrorUtil::NotHex(what_is_hex)) + hex::decode(stripped).map_err(|_| UtilError::NotHex(what_is_hex)) } else { - hex::decode(hex_data).map_err(|_| ErrorUtil::NotHex(what_is_hex)) + hex::decode(hex_data).map_err(|_| UtilError::NotHex(what_is_hex)) } } diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 3f725a3..a8ffae8 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -2,7 +2,7 @@ // if running locally, ensure that you have no dangling processes (kalatori daemon, chopsticks) // pkill -f kalatori; pkill -f chopsticks -use kalatori::definitions::api_v2::*; +use kalatori::definitions::api_v2::{ServerStatus, TokenKind}; use lazy_static::lazy_static; use reqwest::Client; use std::env; @@ -17,7 +17,11 @@ lazy_static! { async fn start_chopsticks() -> std::io::Result { let mut command = Command::new("npx"); - command.args(&["@acala-network/chopsticks@latest", "-c", "chopsticks/pd-ah.yml"]); + 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) @@ -119,9 +123,13 @@ async fn test_daemon_status_call() { .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"); + 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_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, KALATORI_REMARK); @@ -130,7 +138,10 @@ async fn test_daemon_status_call() { 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!(matches!( + properties.kind, + TokenKind::Balances | TokenKind::Asset + )); assert!(properties.decimals > 0); assert!(!properties.rpc_url.is_empty()); From 18039bb97cd5695d23b6a9f9799418c2e30f631f Mon Sep 17 00:00:00 2001 From: Fluid <90795031+fluiderson@users.noreply.github.com> Date: Wed, 29 May 2024 21:44:47 +0300 Subject: [PATCH 64/76] Remove patches, update the lockfile --- Cargo.lock | 36 ++++++++++++++++++------------------ Cargo.toml | 9 --------- 2 files changed, 18 insertions(+), 27 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5a39ce1..62bd0dd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "addr2line" -version = "0.21.0" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" dependencies = [ "gimli", ] @@ -170,9 +170,9 @@ dependencies = [ [[package]] name = "backtrace" -version = "0.3.71" +version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" +checksum = "17c6a35df3749d2e8bb1b7b21a976d82b15548788d2735b9d82f329268f71a11" dependencies = [ "addr2line", "cc", @@ -840,9 +840,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.28.1" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" [[package]] name = "group" @@ -1007,9 +1007,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d8d52be92d09acc2e01dddb7fde3ad983fc6489c7db4837e605bc3fca4cb63e" +checksum = "7b875924a60b96e5d7b9ae7b066540b1dd1cbd90d1828f54c92e02a283351c56" dependencies = [ "bytes", "futures-channel", @@ -1351,7 +1351,7 @@ dependencies = [ [[package]] name = "mnemonic-external" version = "0.1.0" -source = "git+https://github.com/fluiderson/me?branch=thiserror#cf95774298cecc70d669fd8efc0c43358f7d1960" +source = "git+https://github.com/Alzymologist/mnemonic-external#ac8b4f027adec591ae16a4e284ca25c0ffda6c3d" dependencies = [ "bitvec", "sha2", @@ -1446,9 +1446,9 @@ dependencies = [ [[package]] name = "object" -version = "0.32.2" +version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +checksum = "b8ec7ab813848ba4522158d5517a6093db1ded27575b070f4177b8d12b41db5e" dependencies = [ "memchr", ] @@ -1929,9 +1929,9 @@ checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustc-hash" -version = "1.2.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3450ed37fe9609abb6bc3b8891b6e078404e4c53c7332350e2e15126a95229bf" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustc-hex" @@ -2379,7 +2379,7 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "substrate-constructor" version = "0.1.0" -source = "git+https://github.com/fluiderson/sc?branch=thiserror#7791cfce487f9675273f2e9540ebaf53f304d178" +source = "git+https://github.com/Alzymologist/substrate-constructor#b32ba7a2a85a890d8911356123f8b9d2ed941392" dependencies = [ "bitvec", "external-memory-tools", @@ -2399,7 +2399,7 @@ dependencies = [ [[package]] name = "substrate-crypto-light" version = "0.1.0" -source = "git+https://github.com/fluiderson/scl?branch=thiserror#1807aa5d008638ba06059024d07e83b7b59bc243" +source = "git+https://github.com/Alzymologist/substrate-crypto-light#406354cf2309f98699ef5efe66699fc098a3ee19" dependencies = [ "base58", "blake2b_simd", @@ -2720,7 +2720,7 @@ dependencies = [ "serde", "serde_spanned", "toml_datetime", - "winnow 0.6.8", + "winnow 0.6.9", ] [[package]] @@ -3162,9 +3162,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.6.8" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3c52e9c97a68071b23e836c9380edae937f17b9c4667bd021973efc689f618d" +checksum = "86c949fede1d13936a99f14fafd3e76fd642b556dd2ce96287fbe2e0151bfac6" dependencies = [ "memchr", ] diff --git a/Cargo.toml b/Cargo.toml index f5e1c72..a4108bb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,15 +49,6 @@ substrate-constructor = { git = "https://github.com/Alzymologist/substrate-const mnemonic-external = { git = "https://github.com/Alzymologist/mnemonic-external" } substrate-crypto-light = { git = "https://github.com/Alzymologist/substrate-crypto-light" } -[patch."https://github.com/Alzymologist/substrate-crypto-light"] -substrate-crypto-light = { git = "https://github.com/fluiderson/scl", branch = "thiserror" } - -[patch."https://github.com/Alzymologist/substrate-constructor"] -substrate-constructor = { git = "https://github.com/fluiderson/sc", branch = "thiserror" } - -[patch."https://github.com/Alzymologist/mnemonic-external"] -mnemonic-external = { git = "https://github.com/fluiderson/me", branch = "thiserror" } - [dev-dependencies] reqwest = { version = "0.12", features = ["json"] } lazy_static = "1" From fbc22dc80647d96f9904c3ace566d8fb3a116242 Mon Sep 17 00:00:00 2001 From: Fluid <90795031+fluiderson@users.noreply.github.com> Date: Thu, 30 May 2024 11:56:07 +0300 Subject: [PATCH 65/76] Add the lifetime parameter for orders --- src/chain/definitions.rs | 5 ++ src/chain/mod.rs | 10 ++-- src/chain/rpc.rs | 9 ++-- src/chain/tracker.rs | 67 ++++++++++++++++------- src/database.rs | 113 ++++++++++++++++++++++++++------------- src/definitions.rs | 2 +- src/main.rs | 4 +- src/state.rs | 19 +++++-- src/utils.rs | 2 +- 9 files changed, 158 insertions(+), 73 deletions(-) diff --git a/src/chain/definitions.rs b/src/chain/definitions.rs index 10a75f7..6d12d61 100644 --- a/src/chain/definitions.rs +++ b/src/chain/definitions.rs @@ -96,6 +96,7 @@ pub struct WatchAccount { pub amount: Balance, pub recipient: AccountId32, pub res: oneshot::Sender>, + pub death: u64, } impl WatchAccount { @@ -104,6 +105,7 @@ impl WatchAccount { order: OrderInfo, recipient: AccountId32, res: oneshot::Sender>, + death: u64, ) -> Result { Ok(WatchAccount { id, @@ -114,6 +116,7 @@ impl WatchAccount { amount: Balance::parse(order.amount, order.currency.decimals), recipient, res, + death, }) } } @@ -132,6 +135,7 @@ pub struct Invoice { pub currency: String, pub amount: Balance, pub recipient: AccountId32, + pub death: u64, } impl Invoice { @@ -143,6 +147,7 @@ impl Invoice { currency: watch_account.currency, amount: watch_account.amount, recipient: watch_account.recipient, + death: watch_account.death, } } diff --git a/src/chain/mod.rs b/src/chain/mod.rs index 6f17c3e..26a9b65 100644 --- a/src/chain/mod.rs +++ b/src/chain/mod.rs @@ -11,7 +11,7 @@ use tokio_util::sync::CancellationToken; use crate::{ definitions::{api_v2::OrderInfo, Chain}, - error::{Error, ChainError}, + error::{ChainError, Error}, Signer, State, TaskTracker, }; @@ -80,7 +80,7 @@ impl ChainManager { signer.interface(), task_tracker.clone(), cancellation_token.clone(), - )?; + ); } task_tracker @@ -148,12 +148,13 @@ impl ChainManager { &self, id: String, order: OrderInfo, + death: u64, recipient: AccountId32, ) -> Result<(), ChainError> { let (res, rx) = oneshot::channel(); self.tx .send(ChainRequest::WatchAccount(WatchAccount::new( - id, order, recipient, res, + id, order, recipient, res, death, )?)) .await .map_err(|_| ChainError::MessageDropped)?; @@ -164,12 +165,13 @@ impl ChainManager { &self, id: String, order: OrderInfo, + death: u64, recipient: AccountId32, ) -> Result<(), ChainError> { let (res, rx) = oneshot::channel(); self.tx .send(ChainRequest::Reap(WatchAccount::new( - id, order, recipient, res, + id, order, recipient, res, death, )?)) .await .map_err(|_| ChainError::MessageDropped)?; diff --git a/src/chain/rpc.rs b/src/chain/rpc.rs index 5bde7d3..dd3addb 100644 --- a/src/chain/rpc.rs +++ b/src/chain/rpc.rs @@ -651,7 +651,8 @@ async fn events_at_block( 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 event_record_element.field_name == Some("event".to_string()) + { if let ParsedData::Event(Event(ref event)) = event_record_element.data.data { @@ -677,13 +678,13 @@ async fn events_at_block( } } } - }; + } return Ok(out); - }, + } _ => { tracing::warn!("{keys_from_storage}"); return Err(ChainError::EventsMissing); - }, + } } } diff --git a/src/chain/tracker.rs b/src/chain/tracker.rs index 68b7ede..779a95a 100644 --- a/src/chain/tracker.rs +++ b/src/chain/tracker.rs @@ -1,6 +1,6 @@ //! A tracker that follows individual chain -use std::collections::HashMap; +use std::{collections::HashMap, time::SystemTime}; use frame_metadata::v15::RuntimeMetadataV15; use jsonrpsee::ws_client::{WsClient, WsClientBuilder}; @@ -17,9 +17,8 @@ use crate::{ 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, + 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}, }, @@ -30,6 +29,7 @@ use crate::{ TaskTracker, }; +#[allow(clippy::too_many_lines)] pub fn start_chain_watch( chain: Chain, chain_tx: mpsc::Sender, @@ -38,7 +38,7 @@ pub fn start_chain_watch( signer: Signer, task_tracker: TaskTracker, cancellation_token: CancellationToken, -) -> Result<(), ChainError> { +) { task_tracker .clone() .spawn(format!("Chain {} watcher", chain.name.clone()), async move { @@ -102,7 +102,9 @@ pub fn start_chain_watch( .await { Ok(events) => { let mut id_remove_list = Vec::new(); - for (id, invoice) in watched_accounts.iter() { + 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) => { @@ -115,6 +117,21 @@ pub fn start_chain_watch( } } } + + if invoice.death >= 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); @@ -138,7 +155,7 @@ pub fn start_chain_watch( 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 {} terminated", id).into()) + Ok(format!("Payout attempt for order {id} terminated").into()) }); } ChainTrackerRequest::Shutdown(res) => { @@ -152,7 +169,6 @@ pub fn start_chain_watch( } Ok(format!("Chain {} monitor shut down", chain.name).into()) }); - Ok(()) } #[derive(Debug, Clone)] @@ -180,16 +196,32 @@ impl ChainWatcher { 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()}) }; + 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); + 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); + 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 { @@ -248,16 +280,15 @@ impl ChainWatcher { 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}"); + 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}"}; + tracing::warn! {"Block watch error: {e} at {rpc}"}; break; } } diff --git a/src/database.rs b/src/database.rs index 9466fa9..0e88ca5 100644 --- a/src/database.rs +++ b/src/database.rs @@ -12,12 +12,17 @@ use crate::{ }, Balance, Nonce, }, - error::{Error, DbError}, + error::{DbError, Error}, TaskTracker, }; use parity_scale_codec::{Compact, Decode, Encode}; use serde::Deserialize; -use std::{collections::HashMap, fs::File, io::ErrorKind, time::Duration}; +use std::{ + collections::HashMap, + fs::File, + io::ErrorKind, + time::{Duration, Instant, SystemTime}, +}; use substrate_crypto_light::common::AccountId32; use tokio::sync::{mpsc, oneshot}; @@ -160,7 +165,6 @@ pub struct ConfigWoChains { pub debug: bool, pub remark: String, //pub depth: Option, - pub account_lifetime: Duration, } /// Database server handle @@ -170,7 +174,11 @@ pub struct Database { } impl Database { - pub fn init(path_option: Option, task_tracker: TaskTracker) -> Result { + pub fn init( + path_option: Option, + task_tracker: TaskTracker, + account_lifetime: u64, + ) -> 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:?}."); @@ -195,13 +203,12 @@ impl Database { .iter() .filter_map(|a| a.ok()) .filter_map(|(a, b)| { - match (String::decode(&mut &a[..]), OrderInfo::decode(&mut &b[..])) - { + match (String::decode(&mut &a[..]), OrderDb::decode(&mut &b[..])) { (Ok(a), Ok(b)) => Some((a, b)), _ => None, } }) - .filter(|(a, b)| b.payment_status == PaymentStatus::Pending) + .filter(|(a, b)| b.inner.payment_status == PaymentStatus::Pending) .collect())); } DbRequest::CreateOrder(request) => { @@ -209,6 +216,7 @@ impl Database { request.order, request.order_info, &orders, + account_lifetime, )); } DbRequest::ReadOrder(request) => { @@ -238,7 +246,7 @@ impl Database { Ok(Self { tx }) } - pub async fn order_list(&self) -> Result, DbError> { + 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)? @@ -270,7 +278,7 @@ impl Database { rx.await.map_err(|_| DbError::DbEngineDown)? } - pub async fn mark_paid(&self, order: String) -> Result { + pub async fn mark_paid(&self, order: String) -> Result { let (res, rx) = oneshot::channel(); let _unused = self .tx @@ -306,7 +314,7 @@ impl Database { enum DbRequest { CreateOrder(CreateOrder), - ActiveOrderList(oneshot::Sender, DbError>>), + ActiveOrderList(oneshot::Sender, DbError>>), ReadOrder(ReadOrder), MarkPaid(MarkPaid), MarkWithdrawn(ModifyOrder), @@ -332,45 +340,74 @@ pub struct ModifyOrder { pub struct MarkPaid { pub order: String, - pub res: oneshot::Sender>, + pub res: oneshot::Sender>, +} + +#[derive(Encode, Decode)] +pub struct OrderDb { + pub inner: OrderInfo, + pub start: u64, + pub death: u64, } fn create_order( order: String, - order_info: OrderInfo, + order_inner: OrderInfo, orders: &sled::Tree, + account_lifetime: u64, ) -> Result { - Ok(match orders.get(&order)? { - Some(record) => { - let old_order_info = OrderInfo::decode(&mut &record[..])?; - match order_info.payment_status { - PaymentStatus::Pending => { - drop(orders.insert(order.encode(), order_info.encode())?); - OrderCreateResponse::Modified - } - PaymentStatus::Paid => OrderCreateResponse::Collision(old_order_info), + Ok(if let Some(record) = orders.get(&order)? { + let old_order_info = OrderDb::decode(&mut &record[..])?; + match order_inner.payment_status { + PaymentStatus::Pending => { + drop( + orders.insert( + order.encode(), + OrderDb { + inner: order_inner, + start: old_order_info.start, + death: old_order_info.death, + } + .encode(), + )?, + ); + OrderCreateResponse::Modified } + PaymentStatus::Paid => OrderCreateResponse::Collision(old_order_info.inner), } - None => { - orders.insert(order.encode(), order_info.encode())?; - OrderCreateResponse::New - } + } else { + let start = SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap() + .as_millis() as u64; + let death = start + account_lifetime; + + orders.insert( + order.encode(), + OrderDb { + inner: order_inner, + start, + death, + } + .encode(), + )?; + OrderCreateResponse::New(death) }) } fn read_order(order: String, orders: &sled::Tree) -> Result, DbError> { if let Some(order) = orders.get(order)? { - Ok(Some(OrderInfo::decode(&mut &order[..])?)) + Ok(Some(OrderDb::decode(&mut &order[..])?.inner)) } else { Ok(None) } } -fn mark_paid(order: String, orders: &sled::Tree) -> Result { +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; + let mut order_info = OrderDb::decode(&mut &order_info[..])?; + if order_info.inner.payment_status == PaymentStatus::Pending { + order_info.inner.payment_status = PaymentStatus::Paid; orders.insert(order.encode(), order_info.encode())?; Ok(order_info) } else { @@ -382,10 +419,10 @@ fn mark_paid(order: String, orders: &sled::Tree) -> Result { } 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; + let mut order_info = OrderDb::decode(&mut &order_info[..])?; + if order_info.inner.payment_status == PaymentStatus::Paid { + if order_info.inner.withdrawal_status == WithdrawalStatus::Waiting { + order_info.inner.withdrawal_status = WithdrawalStatus::Completed; orders.insert(order.encode(), order_info.encode())?; Ok(()) } else { @@ -400,10 +437,10 @@ fn mark_withdrawn(order: String, orders: &sled::Tree) -> Result<(), DbError> { } 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; + let mut order_info = OrderDb::decode(&mut &order_info[..])?; + if order_info.inner.payment_status == PaymentStatus::Paid { + if order_info.inner.withdrawal_status == WithdrawalStatus::Waiting { + order_info.inner.withdrawal_status = WithdrawalStatus::Failed; orders.insert(order.encode(), order_info.encode())?; Ok(()) } else { diff --git a/src/definitions.rs b/src/definitions.rs index 9ef0350..bae8dca 100644 --- a/src/definitions.rs +++ b/src/definitions.rs @@ -146,7 +146,7 @@ pub mod api_v2 { } pub enum OrderCreateResponse { - New, + New(u64), Modified, Collision(OrderInfo), } diff --git a/src/main.rs b/src/main.rs index b08dd8d..7188b83 100644 --- a/src/main.rs +++ b/src/main.rs @@ -126,7 +126,8 @@ async fn try_main() -> Result<(), Error> { let signer = Signer::init(recipient.clone(), task_tracker.clone())?; - let db = database::Database::init(database_path, task_tracker.clone())?; + let db = + database::Database::init(database_path, task_tracker.clone(), config.account_lifetime)?; let (cm_tx, cm_rx) = oneshot::channel(); @@ -137,7 +138,6 @@ async fn try_main() -> Result<(), Error> { debug: config.debug, remark, //depth: config.depth, - account_lifetime: Duration::from_millis(config.account_lifetime), }, db, cm_rx, diff --git a/src/state.rs b/src/state.rs index 0e8dcd5..a6f1bec 100644 --- a/src/state.rs +++ b/src/state.rs @@ -32,7 +32,6 @@ impl State { recipient, debug, remark, - account_lifetime, }: ConfigWoChains, db: Database, chain_manager: oneshot::Receiver, @@ -80,7 +79,12 @@ impl State { 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) + .add_invoice( + order, + order_details.inner, + order_details.death, + state.recipient, + ) .await; } Ok("All saved orders restored".into()) @@ -117,7 +121,12 @@ impl State { match state.db.mark_paid(id.clone()).await { Ok(order) => { // TODO: callback here - drop(state.chain_manager.reap(id, order, state.recipient).await); + drop( + state + .chain_manager + .reap(id, order.inner, order.death, state.recipient) + .await, + ); } Err(e) => { tracing::error!( @@ -287,9 +296,9 @@ impl StateData { .create_order(order.clone(), order_info.clone()) .await? { - OrderCreateResponse::New => { + OrderCreateResponse::New(death) => { self.chain_manager - .add_invoice(order.clone(), order_info.clone(), self.recipient) + .add_invoice(order.clone(), order_info.clone(), death, self.recipient) .await?; Ok(OrderResponse::NewOrder(self.order_status( order, diff --git a/src/utils.rs b/src/utils.rs index 4fe6677..8b931f5 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,4 +1,4 @@ -use crate::error::{UtilError, NotHex}; +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") { From ef476c3f7277f72813cdedb8a6a4b273bb9fa856 Mon Sep 17 00:00:00 2001 From: Fluid <90795031+fluiderson@users.noreply.github.com> Date: Thu, 30 May 2024 12:59:21 +0300 Subject: [PATCH 66/76] Add the timestamp type, move it to the order info struct --- src/chain/definitions.rs | 12 +++-- src/chain/mod.rs | 11 ++-- src/chain/tracker.rs | 2 +- src/database.rs | 113 +++++++++++++++++++-------------------- src/definitions.rs | 42 +++++++++++++-- src/state.rs | 40 ++++++-------- 6 files changed, 122 insertions(+), 98 deletions(-) diff --git a/src/chain/definitions.rs b/src/chain/definitions.rs index 6d12d61..cc7f8ab 100644 --- a/src/chain/definitions.rs +++ b/src/chain/definitions.rs @@ -10,7 +10,10 @@ use crate::{ rpc::{asset_balance_at_account, system_balance_at_account}, tracker::ChainWatcher, }, - definitions::{api_v2::OrderInfo, Balance}, + definitions::{ + api_v2::{OrderInfo, Timestamp}, + Balance, + }, error::{ChainError, NotHex}, utils::unhex, }; @@ -96,7 +99,7 @@ pub struct WatchAccount { pub amount: Balance, pub recipient: AccountId32, pub res: oneshot::Sender>, - pub death: u64, + pub death: Timestamp, } impl WatchAccount { @@ -105,7 +108,6 @@ impl WatchAccount { order: OrderInfo, recipient: AccountId32, res: oneshot::Sender>, - death: u64, ) -> Result { Ok(WatchAccount { id, @@ -116,7 +118,7 @@ impl WatchAccount { amount: Balance::parse(order.amount, order.currency.decimals), recipient, res, - death, + death: order.death, }) } } @@ -135,7 +137,7 @@ pub struct Invoice { pub currency: String, pub amount: Balance, pub recipient: AccountId32, - pub death: u64, + pub death: Timestamp, } impl Invoice { diff --git a/src/chain/mod.rs b/src/chain/mod.rs index 26a9b65..41abf67 100644 --- a/src/chain/mod.rs +++ b/src/chain/mod.rs @@ -10,7 +10,10 @@ use tokio::{ use tokio_util::sync::CancellationToken; use crate::{ - definitions::{api_v2::OrderInfo, Chain}, + definitions::{ + api_v2::{OrderInfo, Timestamp}, + Chain, + }, error::{ChainError, Error}, Signer, State, TaskTracker, }; @@ -148,13 +151,12 @@ impl ChainManager { &self, id: String, order: OrderInfo, - death: u64, recipient: AccountId32, ) -> Result<(), ChainError> { let (res, rx) = oneshot::channel(); self.tx .send(ChainRequest::WatchAccount(WatchAccount::new( - id, order, recipient, res, death, + id, order, recipient, res, )?)) .await .map_err(|_| ChainError::MessageDropped)?; @@ -165,13 +167,12 @@ impl ChainManager { &self, id: String, order: OrderInfo, - death: u64, recipient: AccountId32, ) -> Result<(), ChainError> { let (res, rx) = oneshot::channel(); self.tx .send(ChainRequest::Reap(WatchAccount::new( - id, order, recipient, res, death, + id, order, recipient, res, )?)) .await .map_err(|_| ChainError::MessageDropped)?; diff --git a/src/chain/tracker.rs b/src/chain/tracker.rs index 779a95a..c90c494 100644 --- a/src/chain/tracker.rs +++ b/src/chain/tracker.rs @@ -118,7 +118,7 @@ pub fn start_chain_watch( } } - if invoice.death >= now { + if invoice.death.0 >= now { match invoice.check(&client, &watcher, &block).await { Ok(paid) => { if paid { diff --git a/src/database.rs b/src/database.rs index 0e88ca5..08a2455 100644 --- a/src/database.rs +++ b/src/database.rs @@ -7,8 +7,9 @@ use crate::{ definitions::{ api_v2::{ - AssetId, BlockNumber, CurrencyProperties, OrderCreateResponse, OrderInfo, OrderQuery, - PaymentStatus, ServerInfo, ServerStatus, WithdrawalStatus, + AssetId, BlockNumber, CurrencyProperties, OrderCreateResponse, OrderInfo, + OrderInfoWoDeath, OrderQuery, PaymentStatus, ServerInfo, ServerStatus, Timestamp, + WithdrawalStatus, }, Balance, Nonce, }, @@ -203,12 +204,13 @@ impl Database { .iter() .filter_map(|a| a.ok()) .filter_map(|(a, b)| { - match (String::decode(&mut &a[..]), OrderDb::decode(&mut &b[..])) { + match (String::decode(&mut &a[..]), OrderInfo::decode(&mut &b[..])) + { (Ok(a), Ok(b)) => Some((a, b)), _ => None, } }) - .filter(|(a, b)| b.inner.payment_status == PaymentStatus::Pending) + .filter(|(a, b)| b.payment_status == PaymentStatus::Pending) .collect())); } DbRequest::CreateOrder(request) => { @@ -246,7 +248,7 @@ impl Database { Ok(Self { tx }) } - pub async fn order_list(&self) -> Result, DbError> { + 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)? @@ -255,7 +257,7 @@ impl Database { pub async fn create_order( &self, order: String, - order_info: OrderInfo, + order_info: OrderInfoWoDeath, ) -> Result { let (res, rx) = oneshot::channel(); let _unused = self @@ -278,7 +280,7 @@ impl Database { rx.await.map_err(|_| DbError::DbEngineDown)? } - pub async fn mark_paid(&self, order: String) -> Result { + pub async fn mark_paid(&self, order: String) -> Result { let (res, rx) = oneshot::channel(); let _unused = self .tx @@ -314,7 +316,7 @@ impl Database { enum DbRequest { CreateOrder(CreateOrder), - ActiveOrderList(oneshot::Sender, DbError>>), + ActiveOrderList(oneshot::Sender, DbError>>), ReadOrder(ReadOrder), MarkPaid(MarkPaid), MarkWithdrawn(ModifyOrder), @@ -324,7 +326,7 @@ enum DbRequest { pub struct CreateOrder { pub order: String, - pub order_info: OrderInfo, + pub order_info: OrderInfoWoDeath, pub res: oneshot::Sender>, } @@ -340,74 +342,69 @@ pub struct ModifyOrder { pub struct MarkPaid { pub order: String, - pub res: oneshot::Sender>, -} - -#[derive(Encode, Decode)] -pub struct OrderDb { - pub inner: OrderInfo, - pub start: u64, - pub death: u64, + pub res: oneshot::Sender>, } fn create_order( order: String, - order_inner: OrderInfo, + order_info_wo_death: OrderInfoWoDeath, orders: &sled::Tree, account_lifetime: u64, ) -> Result { Ok(if let Some(record) = orders.get(&order)? { - let old_order_info = OrderDb::decode(&mut &record[..])?; - match order_inner.payment_status { + let old_order_info = OrderInfo::decode(&mut &record[..])?; + match old_order_info.payment_status { PaymentStatus::Pending => { - drop( - orders.insert( - order.encode(), - OrderDb { - inner: order_inner, - start: old_order_info.start, - death: old_order_info.death, - } - .encode(), - )?, - ); - OrderCreateResponse::Modified + let order_info_new = OrderInfo { + withdrawal_status: order_info_wo_death.withdrawal_status, + payment_status: order_info_wo_death.payment_status, + amount: order_info_wo_death.amount, + currency: order_info_wo_death.currency, + callback: order_info_wo_death.callback, + transactions: order_info_wo_death.transactions, + payment_account: order_info_wo_death.payment_account, + death: old_order_info.death, + }; + drop(orders.insert(order.encode(), order_info_new.encode())?); + OrderCreateResponse::Modified(order_info_new) } - PaymentStatus::Paid => OrderCreateResponse::Collision(old_order_info.inner), + PaymentStatus::Paid => OrderCreateResponse::Collision(old_order_info), } } else { let start = SystemTime::now() .duration_since(SystemTime::UNIX_EPOCH) .unwrap() .as_millis() as u64; - let death = start + account_lifetime; - - orders.insert( - order.encode(), - OrderDb { - inner: order_inner, - start, - death, - } - .encode(), - )?; - OrderCreateResponse::New(death) + let death = Timestamp(start + account_lifetime); + let order_info_new = OrderInfo { + withdrawal_status: order_info_wo_death.withdrawal_status, + payment_status: order_info_wo_death.payment_status, + amount: order_info_wo_death.amount, + currency: order_info_wo_death.currency, + callback: order_info_wo_death.callback, + transactions: order_info_wo_death.transactions, + payment_account: order_info_wo_death.payment_account, + death, + }; + + orders.insert(order.encode(), order_info_new.encode())?; + OrderCreateResponse::New(order_info_new) }) } fn read_order(order: String, orders: &sled::Tree) -> Result, DbError> { if let Some(order) = orders.get(order)? { - Ok(Some(OrderDb::decode(&mut &order[..])?.inner)) + Ok(Some(OrderInfo::decode(&mut &order[..])?)) } else { Ok(None) } } -fn mark_paid(order: String, orders: &sled::Tree) -> Result { +fn mark_paid(order: String, orders: &sled::Tree) -> Result { if let Some(order_info) = orders.get(order.clone())? { - let mut order_info = OrderDb::decode(&mut &order_info[..])?; - if order_info.inner.payment_status == PaymentStatus::Pending { - order_info.inner.payment_status = PaymentStatus::Paid; + 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 { @@ -419,10 +416,10 @@ fn mark_paid(order: String, orders: &sled::Tree) -> Result { } fn mark_withdrawn(order: String, orders: &sled::Tree) -> Result<(), DbError> { if let Some(order_info) = orders.get(order.clone())? { - let mut order_info = OrderDb::decode(&mut &order_info[..])?; - if order_info.inner.payment_status == PaymentStatus::Paid { - if order_info.inner.withdrawal_status == WithdrawalStatus::Waiting { - order_info.inner.withdrawal_status = WithdrawalStatus::Completed; + 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 { @@ -437,10 +434,10 @@ fn mark_withdrawn(order: String, orders: &sled::Tree) -> Result<(), DbError> { } fn mark_stuck(order: String, orders: &sled::Tree) -> Result<(), DbError> { if let Some(order_info) = orders.get(order.clone())? { - let mut order_info = OrderDb::decode(&mut &order_info[..])?; - if order_info.inner.payment_status == PaymentStatus::Paid { - if order_info.inner.withdrawal_status == WithdrawalStatus::Waiting { - order_info.inner.withdrawal_status = WithdrawalStatus::Failed; + 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 { diff --git a/src/definitions.rs b/src/definitions.rs index bae8dca..8375252 100644 --- a/src/definitions.rs +++ b/src/definitions.rs @@ -76,7 +76,6 @@ pub fn decimal_exponent_product(decimals: api_v2::Decimals) -> f64 { /// Self-sufficient schemas used by Api v2.0.0 pub mod api_v2 { - use std::collections::HashMap; use parity_scale_codec::{Decode, Encode}; @@ -91,6 +90,9 @@ pub mod api_v2 { pub type BlockNumber = u64; pub type ExtrinsicIndex = u32; + #[derive(Encode, Decode, Debug, Clone, Copy, Serialize)] + pub struct Timestamp(pub u64); + #[derive(Debug)] pub struct OrderQuery { pub order: String, @@ -120,6 +122,31 @@ pub mod api_v2 { pub redirect_url: String, } + #[derive(Clone, Debug, Serialize, Encode, Decode)] + pub struct OrderInfoWoDeath { + pub withdrawal_status: WithdrawalStatus, + pub payment_status: PaymentStatus, + pub amount: f64, + pub currency: CurrencyInfo, + pub callback: String, + pub transactions: Vec, + pub payment_account: String, + } + + impl OrderInfoWoDeath { + pub fn new(query: OrderQuery, currency: CurrencyInfo, payment_account: String) -> Self { + OrderInfoWoDeath { + withdrawal_status: WithdrawalStatus::Waiting, + payment_status: PaymentStatus::Pending, + amount: query.amount, + currency, + callback: query.callback, + transactions: Vec::new(), + payment_account, + } + } + } + #[derive(Clone, Debug, Serialize, Encode, Decode)] pub struct OrderInfo { pub withdrawal_status: WithdrawalStatus, @@ -129,10 +156,16 @@ pub mod api_v2 { 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) -> Self { + pub fn new( + query: OrderQuery, + currency: CurrencyInfo, + payment_account: String, + death: Timestamp, + ) -> Self { OrderInfo { withdrawal_status: WithdrawalStatus::Waiting, payment_status: PaymentStatus::Pending, @@ -141,13 +174,14 @@ pub mod api_v2 { callback: query.callback, transactions: Vec::new(), payment_account, + death, } } } pub enum OrderCreateResponse { - New(u64), - Modified, + New(OrderInfo), + Modified(OrderInfo), Collision(OrderInfo), } diff --git a/src/state.rs b/src/state.rs index a6f1bec..fa783d6 100644 --- a/src/state.rs +++ b/src/state.rs @@ -3,8 +3,8 @@ use crate::{ database::Database, definitions::{ api_v2::{ - CurrencyProperties, OrderCreateResponse, OrderInfo, OrderQuery, OrderResponse, - OrderStatus, ServerInfo, ServerStatus, + CurrencyProperties, OrderCreateResponse, OrderInfo, OrderInfoWoDeath, OrderQuery, + OrderResponse, OrderStatus, ServerInfo, ServerStatus, }, Entropy, }, @@ -79,12 +79,7 @@ impl State { task_tracker.spawn("Restore saved orders", async move { for (order, order_details) in order_list { chain_manager_wakeup - .add_invoice( - order, - order_details.inner, - order_details.death, - state.recipient, - ) + .add_invoice(order, order_details, state.recipient) .await; } Ok("All saved orders restored".into()) @@ -121,12 +116,7 @@ impl State { match state.db.mark_paid(id.clone()).await { Ok(order) => { // TODO: callback here - drop( - state - .chain_manager - .reap(id, order.inner, order.death, state.recipient) - .await, - ); + drop(state.chain_manager.reap(id, order, state.recipient).await); } Err(e) => { tracing::error!( @@ -290,31 +280,31 @@ impl StateData { .ok_or(OrderError::UnknownCurrency)?; let currency = currency.info(order_query.currency.clone()); let payment_account = self.signer.public(order.clone(), currency.ss58).await?; - let order_info = OrderInfo::new(order_query, currency, payment_account); match self .db - .create_order(order.clone(), order_info.clone()) + .create_order( + order.clone(), + OrderInfoWoDeath::new(order_query, currency, payment_account), + ) .await? { - OrderCreateResponse::New(death) => { + OrderCreateResponse::New(new_order_info) => { self.chain_manager - .add_invoice(order.clone(), order_info.clone(), death, self.recipient) + .add_invoice(order.clone(), new_order_info.clone(), self.recipient) .await?; Ok(OrderResponse::NewOrder(self.order_status( order, - order_info, + new_order_info, String::new(), ))) } - OrderCreateResponse::Modified => Ok(OrderResponse::ModifiedOrder(self.order_status( - order, - 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_info, + order_status, String::from("Order with this ID was already processed"), ))) } From f2cb3c9a5d86f5ef0ea13da37e55eb36b32e875d Mon Sep 17 00:00:00 2001 From: Fluid <90795031+fluiderson@users.noreply.github.com> Date: Fri, 31 May 2024 11:20:12 +0300 Subject: [PATCH 67/76] Remove a redurant struct, update the death timestamp on modify --- src/database.rs | 71 +++++++++++++++++++++++----------------------- src/definitions.rs | 25 ---------------- src/state.rs | 9 ++---- 3 files changed, 38 insertions(+), 67 deletions(-) diff --git a/src/database.rs b/src/database.rs index 08a2455..b1635eb 100644 --- a/src/database.rs +++ b/src/database.rs @@ -7,9 +7,8 @@ use crate::{ definitions::{ api_v2::{ - AssetId, BlockNumber, CurrencyProperties, OrderCreateResponse, OrderInfo, - OrderInfoWoDeath, OrderQuery, PaymentStatus, ServerInfo, ServerStatus, Timestamp, - WithdrawalStatus, + AssetId, BlockNumber, CurrencyInfo, CurrencyProperties, OrderCreateResponse, OrderInfo, + OrderQuery, PaymentStatus, ServerInfo, ServerStatus, Timestamp, WithdrawalStatus, }, Balance, Nonce, }, @@ -216,7 +215,9 @@ impl Database { DbRequest::CreateOrder(request) => { let _unused = request.res.send(create_order( request.order, - request.order_info, + request.query, + request.currency, + request.payment_account, &orders, account_lifetime, )); @@ -257,14 +258,18 @@ impl Database { pub async fn create_order( &self, order: String, - order_info: OrderInfoWoDeath, + query: OrderQuery, + currency: CurrencyInfo, + payment_account: String, ) -> Result { let (res, rx) = oneshot::channel(); let _unused = self .tx .send(DbRequest::CreateOrder(CreateOrder { order, - order_info, + query, + currency, + payment_account, res, })) .await; @@ -326,7 +331,9 @@ enum DbRequest { pub struct CreateOrder { pub order: String, - pub order_info: OrderInfoWoDeath, + pub query: OrderQuery, + pub currency: CurrencyInfo, + pub payment_account: String, pub res: oneshot::Sender>, } @@ -345,47 +352,39 @@ pub struct MarkPaid { pub res: oneshot::Sender>, } +fn calculate_death_ts(account_lifetime: u64) -> Timestamp { + let start = SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap() + .as_millis() as u64; + + Timestamp(start + account_lifetime) +} + fn create_order( order: String, - order_info_wo_death: OrderInfoWoDeath, + query: OrderQuery, + currency: CurrencyInfo, + payment_account: String, orders: &sled::Tree, account_lifetime: u64, ) -> Result { Ok(if let Some(record) = orders.get(&order)? { - let old_order_info = OrderInfo::decode(&mut &record[..])?; + let mut old_order_info = OrderInfo::decode(&mut &record[..])?; match old_order_info.payment_status { PaymentStatus::Pending => { - let order_info_new = OrderInfo { - withdrawal_status: order_info_wo_death.withdrawal_status, - payment_status: order_info_wo_death.payment_status, - amount: order_info_wo_death.amount, - currency: order_info_wo_death.currency, - callback: order_info_wo_death.callback, - transactions: order_info_wo_death.transactions, - payment_account: order_info_wo_death.payment_account, - death: old_order_info.death, - }; - drop(orders.insert(order.encode(), order_info_new.encode())?); - OrderCreateResponse::Modified(order_info_new) + 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 start = SystemTime::now() - .duration_since(SystemTime::UNIX_EPOCH) - .unwrap() - .as_millis() as u64; - let death = Timestamp(start + account_lifetime); - let order_info_new = OrderInfo { - withdrawal_status: order_info_wo_death.withdrawal_status, - payment_status: order_info_wo_death.payment_status, - amount: order_info_wo_death.amount, - currency: order_info_wo_death.currency, - callback: order_info_wo_death.callback, - transactions: order_info_wo_death.transactions, - payment_account: order_info_wo_death.payment_account, - death, - }; + let death = calculate_death_ts(account_lifetime); + let order_info_new = OrderInfo::new(query, currency, payment_account, death); orders.insert(order.encode(), order_info_new.encode())?; OrderCreateResponse::New(order_info_new) diff --git a/src/definitions.rs b/src/definitions.rs index 8375252..4bb1773 100644 --- a/src/definitions.rs +++ b/src/definitions.rs @@ -122,31 +122,6 @@ pub mod api_v2 { pub redirect_url: String, } - #[derive(Clone, Debug, Serialize, Encode, Decode)] - pub struct OrderInfoWoDeath { - pub withdrawal_status: WithdrawalStatus, - pub payment_status: PaymentStatus, - pub amount: f64, - pub currency: CurrencyInfo, - pub callback: String, - pub transactions: Vec, - pub payment_account: String, - } - - impl OrderInfoWoDeath { - pub fn new(query: OrderQuery, currency: CurrencyInfo, payment_account: String) -> Self { - OrderInfoWoDeath { - withdrawal_status: WithdrawalStatus::Waiting, - payment_status: PaymentStatus::Pending, - amount: query.amount, - currency, - callback: query.callback, - transactions: Vec::new(), - payment_account, - } - } - } - #[derive(Clone, Debug, Serialize, Encode, Decode)] pub struct OrderInfo { pub withdrawal_status: WithdrawalStatus, diff --git a/src/state.rs b/src/state.rs index fa783d6..5efea14 100644 --- a/src/state.rs +++ b/src/state.rs @@ -3,8 +3,8 @@ use crate::{ database::Database, definitions::{ api_v2::{ - CurrencyProperties, OrderCreateResponse, OrderInfo, OrderInfoWoDeath, OrderQuery, - OrderResponse, OrderStatus, ServerInfo, ServerStatus, + CurrencyProperties, OrderCreateResponse, OrderInfo, OrderQuery, OrderResponse, + OrderStatus, ServerInfo, ServerStatus, }, Entropy, }, @@ -282,10 +282,7 @@ impl StateData { let payment_account = self.signer.public(order.clone(), currency.ss58).await?; match self .db - .create_order( - order.clone(), - OrderInfoWoDeath::new(order_query, currency, payment_account), - ) + .create_order(order.clone(), order_query, currency, payment_account) .await? { OrderCreateResponse::New(new_order_info) => { From 6ca8299259a9a886402265ab20a0d4011c0fd2d7 Mon Sep 17 00:00:00 2001 From: Fluid <90795031+fluiderson@users.noreply.github.com> Date: Tue, 11 Jun 2024 20:39:01 +0300 Subject: [PATCH 68/76] lot of code --- Cargo.lock | 603 ++++++++++++++++++++++++---- Cargo.toml | 12 +- build.rs | 44 +++ src/database.rs | 16 +- src/definitions.rs | 8 +- src/error.rs | 254 +++++++++--- src/main.rs | 792 +++++++++++++++++++++++++------------ src/signer.rs | 17 +- src/state.rs | 92 +++-- tests/integration_tests.rs | 4 +- 10 files changed, 1401 insertions(+), 441 deletions(-) create mode 100644 build.rs diff --git a/Cargo.lock b/Cargo.lock index 62bd0dd..7679e0a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -24,6 +24,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", + "getrandom", "once_cell", "version_check", "zerocopy", @@ -44,6 +45,55 @@ version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" +[[package]] +name = "anstream" +version = "0.6.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" + +[[package]] +name = "anstyle-parse" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + [[package]] name = "anyhow" version = "1.0.86" @@ -162,7 +212,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00c055ee2d014ae5981ce1016374e8213682aa14d9bf40e48ab48b5f3ef20eaa" dependencies = [ - "heck", + "heck 0.4.1", "proc-macro2", "quote", "syn 2.0.66", @@ -317,9 +367,14 @@ checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" [[package]] name = "cc" -version = "1.0.98" +version = "1.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41c270e7540d725e65ac7f1b212ac8ce349719624d7bcff99f8e2e488e8cf03f" +checksum = "96c51067fd44124faa7f870b4b1c969379ad32b2ba805aa959430ceaa384f695" +dependencies = [ + "jobserver", + "libc", + "once_cell", +] [[package]] name = "cfg-if" @@ -327,18 +382,84 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "clap" +version = "4.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5db83dced34638ad474f39f250d7fea9598bdd239eaced1bdf45d597da0f433f" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7e204572485eb3fbf28f871612191521df159bc3e15a9f5064c66dba3a8c05f" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c780290ccf4fb26629baa7a1081e68ced113f1d3ec302fa5948f1c381ebf06c6" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.66", +] + +[[package]] +name = "clap_lex" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70" + [[package]] name = "color_quant" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" +[[package]] +name = "colorchoice" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" + [[package]] name = "const-oid" version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +[[package]] +name = "const_format" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a214c7af3d04997541b18d432afaff4c455e79e2029079647e72fc2bd27673" +dependencies = [ + "const_format_proc_macros", +] + +[[package]] +name = "const_format_proc_macros" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f6ff08fd20f4f299298a28e2dfa8a8ba1036e6cd2460ac1de7b425d76f2500" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + [[package]] name = "constant_time_eq" version = "0.1.5" @@ -507,6 +628,17 @@ dependencies = [ "subtle", ] +[[package]] +name = "displaydoc" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", +] + [[package]] name = "ecdsa" version = "0.16.9" @@ -632,7 +764,7 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59a98bbaacea1c0eb6a0876280051b892eb73594fd90cf3b20e9c817029c57d2" dependencies = [ - "toml 0.5.11", + "toml", ] [[package]] @@ -723,6 +855,7 @@ checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" dependencies = [ "futures-channel", "futures-core", + "futures-executor", "futures-io", "futures-sink", "futures-task", @@ -745,6 +878,17 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +[[package]] +name = "futures-executor" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + [[package]] name = "futures-io" version = "0.3.30" @@ -844,6 +988,19 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" +[[package]] +name = "git2" +version = "0.18.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "232e6a7bfe35766bf715e55a88b39a700596c0ccfd88cd3680b4cdb40d66ef70" +dependencies = [ + "bitflags 2.5.0", + "libc", + "libgit2-sys", + "log", + "url", +] + [[package]] name = "group" version = "0.13.0" @@ -890,6 +1047,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" @@ -945,12 +1108,12 @@ dependencies = [ [[package]] name = "http-body-util" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" dependencies = [ "bytes", - "futures-core", + "futures-util", "http 1.1.0", "http-body", "pin-project-lite", @@ -958,9 +1121,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.8.0" +version = "1.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" +checksum = "9f3935c160d00ac752e09787e6e6bfc26494c2183cc922f1bc678a60d4733bc2" [[package]] name = "httpdate" @@ -1025,14 +1188,134 @@ dependencies = [ "tracing", ] +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" + +[[package]] +name = "icu_properties" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f8ac670d7422d7f76b32e17a5db556510825b29ec9154f235977c9caba61036" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", +] + [[package]] name = "idna" -version = "0.5.0" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +checksum = "4716a3a0933a1d01c2f72450e89596eb51dd34ef3c211ccd875acdf1f8fe47ed" dependencies = [ - "unicode-bidi", - "unicode-normalization", + "icu_normalizer", + "icu_properties", + "smallvec", + "utf8_iter", ] [[package]] @@ -1078,6 +1361,12 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "indoc" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" + [[package]] name = "instant" version = "0.1.13" @@ -1102,12 +1391,33 @@ version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +[[package]] +name = "is_debug" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06d198e9919d9822d5f7083ba8530e04de87841eaf21ead9af8f2304efd57c89" + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" + [[package]] name = "itoa" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +[[package]] +name = "jobserver" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" +dependencies = [ + "libc", +] + [[package]] name = "js-sys" version = "0.3.69" @@ -1213,20 +1523,26 @@ dependencies = [ name = "kalatori" version = "0.2.0-rc3" dependencies = [ + "ahash", "axum", "axum-macros", + "clap", "frame-metadata", + "futures", "hex", + "indoc", "jsonrpsee", "lazy_static", "mnemonic-external", "names", "parity-scale-codec", + "parking_lot 0.12.3", "primitive-types", "reqwest", "scale-info", "serde", "serde_json", + "shadow-rs", "sled", "sp-crypto-hashing", "substrate-constructor", @@ -1235,7 +1551,7 @@ dependencies = [ "thiserror", "tokio", "tokio-util", - "toml 0.8.13", + "toml_edit 0.22.14", "tracing", "tracing-subscriber", "ureq", @@ -1266,12 +1582,42 @@ version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +[[package]] +name = "libgit2-sys" +version = "0.16.2+1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee4126d8b4ee5c9d9ea891dd875cfdc1e9d0950437179104b183d7d8a74d24e8" +dependencies = [ + "cc", + "libc", + "libz-sys", + "pkg-config", +] + +[[package]] +name = "libz-sys" +version = "1.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c15da26e5af7e25c90b37a2d75cdbf940cf4a55316de9d84c679c9b8bfabf82e" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "linux-raw-sys" version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +[[package]] +name = "litemap" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704" + [[package]] name = "lock_api" version = "0.4.12" @@ -1444,6 +1790,15 @@ dependencies = [ "libc", ] +[[package]] +name = "num_threads" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" +dependencies = [ + "libc", +] + [[package]] name = "object" version = "0.35.0" @@ -1740,9 +2095,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.84" +version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec96c6a92621310b51366f1e28d05ef11489516e93be030060e5fc12024a49d6" +checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23" dependencies = [ "unicode-ident", ] @@ -1812,14 +2167,14 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.4" +version = "1.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" +checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" 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]] @@ -1833,13 +2188,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]] @@ -1850,9 +2205,9 @@ 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 = "reqwest" @@ -2224,6 +2579,18 @@ dependencies = [ "keccak", ] +[[package]] +name = "shadow-rs" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d75516bdaee8f640543ad1f6e292448c23ce57143f812c3736ab4b0874383df" +dependencies = [ + "const_format", + "git2", + "is_debug", + "time", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -2370,12 +2737,24 @@ dependencies = [ "der", ] +[[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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "substrate-constructor" version = "0.1.0" @@ -2476,6 +2855,17 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", +] + [[package]] name = "system-configuration" version = "0.5.1" @@ -2553,7 +2943,9 @@ checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ "deranged", "itoa", + "libc", "num-conv", + "num_threads", "powerfmt", "serde", "time-core", @@ -2577,25 +2969,20 @@ dependencies = [ ] [[package]] -name = "tinyvec" -version = "1.6.0" +name = "tinystr" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" dependencies = [ - "tinyvec_macros", + "displaydoc", + "zerovec", ] -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - [[package]] name = "tokio" -version = "1.37.0" +version = "1.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" +checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a" dependencies = [ "backtrace", "bytes", @@ -2612,9 +2999,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" dependencies = [ "proc-macro2", "quote", @@ -2678,18 +3065,6 @@ dependencies = [ "serde", ] -[[package]] -name = "toml" -version = "0.8.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4e43f8cc456c9704c851ae29c67e17ef65d2c30017c17a9765b89c382dc8bba" -dependencies = [ - "serde", - "serde_spanned", - "toml_datetime", - "toml_edit 0.22.13", -] - [[package]] name = "toml_datetime" version = "0.6.6" @@ -2712,15 +3087,15 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.13" +version = "0.22.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c127785850e8c20836d49732ae6abfa47616e60bf9d9f57c43c250361a9db96c" +checksum = "f21c7aaf97f1bd9ca9d4f9e73b0a6c74bd5afef56f2bc931943a6e1c37e04e38" dependencies = [ "indexmap", "serde", "serde_spanned", "toml_datetime", - "winnow 0.6.9", + "winnow 0.6.13", ] [[package]] @@ -2835,12 +3210,6 @@ dependencies = [ "static_assertions", ] -[[package]] -name = "unicode-bidi" -version = "0.3.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" - [[package]] name = "unicode-ident" version = "1.0.12" @@ -2848,13 +3217,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] -name = "unicode-normalization" -version = "0.1.23" +name = "unicode-xid" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" -dependencies = [ - "tinyvec", -] +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" [[package]] name = "untrusted" @@ -2878,15 +3244,33 @@ dependencies = [ [[package]] name = "url" -version = "2.5.0" +version = "2.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +checksum = "f7c25da092f0a868cdf09e8674cd3b7ef3a7d92a24253e663a2fb85e2496de56" dependencies = [ "form_urlencoded", "idna", "percent-encoding", ] +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "vcpkg" version = "0.2.15" @@ -3162,9 +3546,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.6.9" +version = "0.6.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86c949fede1d13936a99f14fafd3e76fd642b556dd2ce96287fbe2e0151bfac6" +checksum = "59b5e5f6c299a3c7890b876a2a587f3115162487e704907d9b6cd29473052ba1" dependencies = [ "memchr", ] @@ -3179,6 +3563,18 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + [[package]] name = "wyz" version = "0.5.1" @@ -3188,6 +3584,30 @@ dependencies = [ "tap", ] +[[package]] +name = "yoke" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", + "synstructure", +] + [[package]] name = "zerocopy" version = "0.7.34" @@ -3208,6 +3628,27 @@ dependencies = [ "syn 2.0.66", ] +[[package]] +name = "zerofrom" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", + "synstructure", +] + [[package]] name = "zeroize" version = "1.8.1" @@ -3227,3 +3668,25 @@ dependencies = [ "quote", "syn 2.0.66", ] + +[[package]] +name = "zerovec" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb2cc8827d6c0994478a15c53f374f46fbd41bea663d809b14744bc42e6b109c" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97cf56601ee5052b4417d90c8755c6683473c926039908196cf35d99f893ebe7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", +] diff --git a/Cargo.toml b/Cargo.toml index a4108bb..a3dd9ef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,23 +40,33 @@ hex = "0.4" parity-scale-codec = "3" serde_json = "1" sp-crypto-hashing = "0.1" -toml = "0.8" +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" +parking_lot = "0.12" 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" 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/src/database.rs b/src/database.rs index b1635eb..8c232a3 100644 --- a/src/database.rs +++ b/src/database.rs @@ -10,7 +10,7 @@ use crate::{ AssetId, BlockNumber, CurrencyInfo, CurrencyProperties, OrderCreateResponse, OrderInfo, OrderQuery, PaymentStatus, ServerInfo, ServerStatus, Timestamp, WithdrawalStatus, }, - Balance, Nonce, + Balance, Nonce, Version, }, error::{DbError, Error}, TaskTracker, @@ -28,6 +28,8 @@ 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"); @@ -162,8 +164,8 @@ impl Value for Invoice { pub struct ConfigWoChains { pub recipient: AccountId32, - pub debug: bool, - pub remark: String, + pub debug: Option, + pub remark: Option, //pub depth: Option, } @@ -177,7 +179,7 @@ impl Database { pub fn init( path_option: Option, task_tracker: TaskTracker, - account_lifetime: u64, + account_lifetime: Timestamp, ) -> Result { let (tx, mut rx) = tokio::sync::mpsc::channel(1024); let database = if let Some(path) = path_option { @@ -352,13 +354,13 @@ pub struct MarkPaid { pub res: oneshot::Sender>, } -fn calculate_death_ts(account_lifetime: u64) -> Timestamp { +fn calculate_death_ts(account_lifetime: Timestamp) -> Timestamp { let start = SystemTime::now() .duration_since(SystemTime::UNIX_EPOCH) .unwrap() .as_millis() as u64; - Timestamp(start + account_lifetime) + Timestamp(start + account_lifetime.0) } fn create_order( @@ -367,7 +369,7 @@ fn create_order( currency: CurrencyInfo, payment_account: String, orders: &sled::Tree, - account_lifetime: u64, + account_lifetime: Timestamp, ) -> Result { Ok(if let Some(record) = orders.get(&order)? { let mut old_order_info = OrderInfo::decode(&mut &record[..])?; diff --git a/src/definitions.rs b/src/definitions.rs index 4bb1773..03b5c5f 100644 --- a/src/definitions.rs +++ b/src/definitions.rs @@ -90,7 +90,7 @@ pub mod api_v2 { pub type BlockNumber = u64; pub type ExtrinsicIndex = u32; - #[derive(Encode, Decode, Debug, Clone, Copy, Serialize)] + #[derive(Encode, Decode, Debug, Clone, Copy, Serialize, Deserialize)] pub struct Timestamp(pub u64); #[derive(Debug)] @@ -266,8 +266,10 @@ pub mod api_v2 { pub struct ServerInfo { pub version: String, pub instance_id: String, - pub debug: bool, - pub kalatori_remark: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub debug: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub kalatori_remark: Option, } #[derive(Clone, Debug, Serialize, Decode, Encode)] diff --git a/src/error.rs b/src/error.rs index e37e5b9..2a4a31f 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,36 +1,37 @@ -use crate::definitions::api_v2::OrderStatus; +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::Value; use sled::Error as DatabaseError; -use std::net::SocketAddr; -use std::{ - error::Error as StdError, - fmt::{Display, Formatter, Result}, - io::Error as IoError, -}; +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::de::Error as TomlError; +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 the {0:?} environment variable")] - Env(String), + #[error("failed to read a seed environment variable")] + SeedEnv(#[from] SeedEnvError), #[error("failed to read the config file at {0:?}")] - ConfigFileRead(String), + ConfigFileRead(String, #[source] IoError), #[error("failed to parse the config")] ConfigFileParse(#[from] TomlError), - #[error("failed to parse the config parameter {0:?}")] - ConfigParse(String), + #[error("failed to parse the config parameter `{0}`")] + ConfigParse(&'static str), #[error("chain {0:?} doesn't have any `endpoints` in the config")] EmptyEndpoints(String), @@ -51,7 +52,13 @@ pub enum Error { Signer(#[from] SignerError), #[error("failed to listen for the shutdown signal")] - ShutdownSignal, + 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), @@ -59,13 +66,20 @@ pub enum Error { #[error("fatal error is occurred")] Fatal, - #[error("operating system related I/O error is occurred")] - Io(#[from] IoError), - #[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 { @@ -382,61 +396,195 @@ pub enum NotHex { StorageValue, } -pub struct PrettyCauseWrapper<'a, T>(&'a T); +mod pretty_cause { + use std::{ + error::Error, + fmt::{Display, Formatter, Result}, + }; -pub trait PrettyCause { - fn pretty_cause(&self) -> PrettyCauseWrapper<'_, T>; -} + const OVERLOAD: u16 = 9999; + + pub struct Wrapper<'a, T>(&'a T); -impl PrettyCause for T { - fn pretty_cause(&self) -> PrettyCauseWrapper<'_, T> { - PrettyCauseWrapper(self) + pub trait PrettyCause { + fn pretty_cause(&self) -> Wrapper<'_, T>; } -} -impl Display for PrettyCauseWrapper<'_, 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(()); - }; + impl PrettyCause for T { + fn pretty_cause(&self) -> Wrapper<'_, T> { + Wrapper(self) + } + } - f.write_str("\n\nCaused by:")?; + 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(()); + }; - 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("\n\nCaused by:")?; - f.write_str(" ")?; + let Some(mut another_cause) = cause.source() else { + // If an error's source error has no source, print a cause in one line. - Display::fmt(&cause, f)?; + f.write_str(" ")?; - return f.write_str("."); - }; - let mut number = 0u64; + 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}"))?; - let mut print_cause = |cause_to_print, number_to_print| { - f.write_str("\n")?; + 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_to_print:>5}: ")?; + write!(f, "{number:>5}")?; - Display::fmt(cause_to_print, f)?; + f.write_str(": ")?; + + Display::fmt(cause, f)?; + + f.write_str(".") + } - f.write_str(".") + #[cfg(test)] + mod tests { + use super::{PrettyCause, OVERLOAD}; + use std::{ + error::Error, + fmt::{Debug, Display, Formatter, Result, Write}, }; - // Otherwise, print a numbered list of error sources. + #[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, + } + } - print_cause(cause, number)?; + fn nested(nest: u16) -> Self { + let mut e = Self::empty(); - loop { - number = number.saturating_add(1); + for _ in 0..nest { + e = Self { + number: e.number.saturating_add(1), + source: Some(e.into()), + }; + } - print_cause(another_cause, number)?; + e + } + } + + impl Display for TestError { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + f.debug_tuple(stringify!(TestError)) + .field(&self.number) + .finish() + } + } - if let Some(one_more_cause) = another_cause.source() { - another_cause = one_more_cause; - } else { - break Ok(()); + impl Error for TestError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + self.source.as_ref().map(|e| e as _) } } } diff --git a/src/main.rs b/src/main.rs index 7188b83..2b34c39 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,22 +1,16 @@ -use serde::Deserialize; -use std::{ - borrow::Cow, - env::{self, VarError}, - error::Error as _, - fs, - future::Future, - net::{IpAddr, Ipv4Addr, SocketAddr}, - panic, str, - time::Duration, -}; +use clap::Parser; +use std::{borrow::Cow, future::Future, process::ExitCode, str}; use substrate_crypto_light::common::{AccountId32, AsBase58}; use tokio::{ - signal, - sync::{mpsc, oneshot}, + runtime::Runtime, + sync::{ + mpsc::{self, UnboundedReceiver, UnboundedSender}, + oneshot, + }, task::JoinHandle, }; use tokio_util::{sync::CancellationToken, task}; -use tracing_subscriber::{fmt::time::UtcTime, EnvFilter}; +use tracing::Level; mod callback; mod chain; @@ -28,83 +22,94 @@ mod signer; mod state; mod utils; +use arguments::{CliArgs, Config, SeedEnvVars, DATABASE_DEFAULT}; use chain::ChainManager; use database::ConfigWoChains; -use definitions::{Chain, Version}; use error::{Error, PrettyCause}; +use shutdown::{ShutdownNotification, ShutdownReason}; use signer::Signer; use state::State; -const CONFIG: &str = "KALATORI_CONFIG"; -const LOG: &str = "KALATORI_LOG"; -const RECIPIENT: &str = "KALATORI_RECIPIENT"; -const REMARK: &str = "KALATORI_REMARK"; -const OLD_SEED: &str = "KALATORI_OLD_SEED_"; - -const DB_VERSION: Version = 0; +fn main() -> ExitCode { + let shutdown_notification = ShutdownNotification::new(); -const DEFAULT_CONFIG: &str = "configs/polkadot.toml"; -const DEFAULT_SOCKET: SocketAddr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 16726); -const DEFAULT_DATABASE: &str = "kalatori.db"; + // Sets the panic hook to print directly to the standart output because the logger isn't + // initialized yet. + shutdown::set_panic_hook(|panic| eprintln!("{panic}"), shutdown_notification.clone()); -fn main() { - if let Err(error) = try_main() { - println!( - "Badbye! The daemon's got a fatal error during an initialization: {error}.{}", - error.pretty_cause() - ); - } -} + match try_main(shutdown_notification) { + Ok(reason) => match reason { + ShutdownReason::UserRequested => { + tracing::info!("Goodbye!"); -#[tokio::main] -async fn try_main() -> Result<(), Error> { - let shutdown_notification = CancellationToken::new(); - - set_panic_hook(shutdown_notification.clone()); - initialize_logger()?; - - // Read env + ExitCode::SUCCESS + } + ShutdownReason::UnrecoverableError => { + tracing::error!("Badbye! The daemon's shut down with errors."); - let recipient = env::var(RECIPIENT).map_err(|_| Error::Env(RECIPIENT.to_string()))?; + ExitCode::FAILURE + } + }, + Err(error) => { + let print = |message| { + if tracing::event_enabled!(Level::ERROR) { + tracing::error!("{message}"); + } else { + eprintln!("{message}"); + } + }; - let remark = env::var(REMARK).map_err(|_| Error::Env(REMARK.to_string()))?; + print(format_args!( + "Badbye! The daemon's got a fatal error during an initialization:\n {error}.{}", + error.pretty_cause() + )); - let config = Config::load()?; + ExitCode::FAILURE + } + } +} - let host = if let Some(unparsed_host) = config.host { - unparsed_host - .parse() - .map_err(|_| Error::ConfigParse("host to define a socket address".to_string()))? - } else { - DEFAULT_SOCKET - }; +fn try_main(shutdown_notification: ShutdownNotification) -> Result { + let cli_args = CliArgs::parse(); - let debug = config.debug; + logger::initialize(cli_args.log)?; + shutdown::set_panic_hook( + |panic| tracing::error!("{panic}"), + shutdown_notification.clone(), + ); - let database_path = 'database: { - if debug { - if config.in_memory_db.unwrap_or_default() { - if config.database.is_some() { - tracing::warn!( - "`database` is set in the config but ignored because `in_memory_db` is \"true\"" - ); - } + 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, + )) + .map(ShutdownNotification::reason) +} - break 'database None; - } - } else if config.in_memory_db.is_some() { +async fn async_try_main( + shutdown_notification: ShutdownNotification, + recipient_string: String, + remark: Option, + config: Config, + seed_env_vars: SeedEnvVars, +) -> Result { + let database_path = if config.in_memory_db { + if config.database.is_some() { tracing::warn!( - "`in_memory_db` is set in the config but ignored because `debug` isn't set" + "`database` is set in the config but ignored because `in_memory_db` is \"true\"" ); } - Some(config.database.unwrap_or_else(|| { - tracing::debug!( - "`database` isn't present in the config, using the default value instead: {DEFAULT_DATABASE:?}." - ); - - DEFAULT_DATABASE.into() - })) + 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"); @@ -115,16 +120,16 @@ async fn try_main() -> Result<(), Error> { "Kalatori {} by {} is starting on {}...", env!("CARGO_PKG_VERSION"), env!("CARGO_PKG_AUTHORS"), - host, + config.host, ); let (task_tracker, error_rx) = TaskTracker::new(); - let recipient = AccountId32::from_base58_string(&recipient) + let recipient = AccountId32::from_base58_string(&recipient_string) .map_err(Error::RecipientAccount)? .0; - let signer = Signer::init(recipient.clone(), task_tracker.clone())?; + 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)?; @@ -134,7 +139,7 @@ async fn try_main() -> Result<(), Error> { let state = State::initialise( signer.interface(), ConfigWoChains { - recipient: recipient.clone(), + recipient, debug: config.debug, remark, //depth: config.depth, @@ -143,155 +148,64 @@ async fn try_main() -> Result<(), Error> { cm_rx, instance_id, task_tracker.clone(), + shutdown_notification.token().clone(), )?; - task_tracker.spawn( - "the shutdown listener", - shutdown_listener(shutdown_notification.clone(), state.interface()), - ); - - /* - task_tracker.spawn( - "proc", - Processor::ignite( - rpc, - recipient.into(), - state.clone(), - shutdown_notification.clone(), - ), - );*/ - cm_tx .send(ChainManager::ignite( config.chain, state.interface(), signer.interface(), task_tracker.clone(), - shutdown_notification.clone(), + shutdown_notification.token().clone(), )?) .map_err(|_| Error::Fatal)?; - let server = server::new(shutdown_notification.clone(), host, state.interface()).await?; + let server = server::new( + shutdown_notification.token().clone(), + config.host, + state.interface(), + ) + .await?; - // task_tracker.spawn(shutdown( - // processor.ignite(last_saved_block, task_tracker.clone(), error_tx.clone()), - // error_tx, - // )); task_tracker.spawn("the server module", server); - // Main loop - - task_tracker - .wait_with_notification(error_rx, shutdown_notification) - .await; + let shutdown_completed = CancellationToken::new(); + let mut shutdown_listener = tokio::spawn(shutdown::listener( + shutdown_notification.token().clone(), + shutdown_completed.clone(), + )); - // Shutdown - - tracing::info!("Goodbye!"); - - Ok(()) -} - -fn set_panic_hook(shutdown_notification: CancellationToken) { - panic::set_hook(Box::new(move |panic_info| { - let at = panic_info - .location() - .map(|location| format!(" at `{location}`")) - .unwrap_or_default(); - let payload = panic_info.payload(); - - let message = match payload.downcast_ref::<&str>() { - Some(string) => Some(*string), - None => payload.downcast_ref::().map(|string| &string[..]), - }; - let formatted_message = match message { - Some(string) => format!(":\n{string}\n"), - None => ".".into(), - }; - - tracing::error!( - "A panic detected{at}{formatted_message}\nThis is a bug. Please report it at {}/issues.", - env!("CARGO_PKG_REPOSITORY") - ); - - shutdown_notification.cancel(); - })); -} + // Main loop -fn initialize_logger() -> Result<(), Error> { - let filter = match EnvFilter::try_from_env(LOG) { - Err(error) => { - let Some(VarError::NotPresent) = error - .source() - .expect("should always be `Some`") - .downcast_ref() - else { - return Err(Error::Env(LOG.to_string())); - }; + let notification = tokio::select! { + biased; + notification = task_tracker.wait_with_notification(error_rx, shutdown_notification) => { + shutdown_completed.cancel(); - if cfg!(debug_assertions) { - EnvFilter::try_new("debug") - } else { - EnvFilter::try_new(default_filter()) - } - .unwrap() + notification + } + error = &mut shutdown_listener => { + return Err(error.expect("shutdown listener shouldn't panic").expect_err("shutdown listener should only complete on errors here")); } - Ok(filter) => filter, }; - tracing_subscriber::fmt() - .with_timer(UtcTime::rfc_3339()) - .with_env_filter(filter) - .init(); + shutdown_listener + .await + .expect("shutdown listener shouldn't panic") + .unwrap(); - Ok(()) -} - -fn default_filter() -> String { - 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"; - - let mut filter = String::with_capacity( - OFF.len().saturating_add( - TARGETS - .iter() - .map(|module| { - COMMA - .len() - .saturating_add(module.len()) - .saturating_add(INFO.len()) - }) - .sum(), - ), - ); - - filter.push_str(OFF); - - for target in TARGETS { - filter.push_str(COMMA); - filter.push_str(target); - filter.push_str(INFO); - } - - filter + Ok(notification) } #[derive(Clone)] struct TaskTracker { inner: task::TaskTracker, - error_tx: mpsc::UnboundedSender<(Cow<'static, str>, Error)>, + error_tx: UnboundedSender<(Cow<'static, str>, Error)>, } impl TaskTracker { - fn new() -> (Self, mpsc::UnboundedReceiver<(Cow<'static, str>, Error)>) { + fn new() -> (Self, UnboundedReceiver<(Cow<'static, str>, Error)>) { let (error_tx, error_rx) = mpsc::unbounded_channel(); let inner = task::TaskTracker::new(); @@ -320,94 +234,472 @@ impl TaskTracker { async fn wait_with_notification( self, - mut error_rx: mpsc::UnboundedReceiver<(Cow<'static, str>, Error)>, - shutdown_notification: CancellationToken, - ) { + mut error_rx: UnboundedReceiver<(Cow<'static, str>, Error)>, + shutdown_notification: ShutdownNotification, + ) -> ShutdownNotification { + // `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); while let Some((from, error)) = error_rx.recv().await { tracing::error!( - "Received a fatal error from {from}:\n{error:?}.{}", + "Received a fatal error from {from}:\n {error:?}.{}", error.pretty_cause() ); - if !shutdown_notification.is_cancelled() { + if !shutdown_notification.is_ignited() { tracing::info!("Initialising the shutdown..."); - shutdown_notification.cancel(); + shutdown_notification.ignite(); } } self.inner.wait().await; + + shutdown_notification } - async fn try_wait( - self, - mut error_rx: mpsc::UnboundedReceiver<(Cow<'static, str>, Error)>, - ) -> Result<(), Error> { - drop(self.error_tx); + // 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}; + + pub const SEED: &str = "SEED"; + pub const OLD_SEED: &str = "OLD_SEED_"; + + const SOCKET_DEFAULT: SocketAddr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 16726); + pub const DATABASE_DEFAULT: &str = "kalatori.db"; - if let Some((from, error)) = error_rx.recv().await { - return Err(error)?; + #[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" or "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, + value_name("PATH"), + default_value("configs/polkadot.toml") + )] + pub config: String, + + #[arg( + short, + long, + env, + value_name("DIRECTIVES"), + default_value(logger::default_filter()), + default_missing_value(""), + num_args(0..=1), + require_equals(true), + )] + pub log: String, + + #[arg(long, env, visible_alias("rmrk"), value_name("STRING"))] + pub remark: Option, + + #[arg(short, long, env, 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, + }) } + } - self.inner.wait().await; + /// 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, + } - Ok(()) + 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 } } -async fn shutdown_listener( - shutdown_notification: CancellationToken, - state: State, -) -> Result, Error> { - tokio::select! { - biased; - signal = signal::ctrl_c() => { - signal.map_err(|_| Error::ShutdownSignal)?; +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(), + ) + } - // Print shutdown log messages on the next line after the Control-C command. - println!(); + pub fn default_filter() -> String { + let mut filter = String::with_capacity(default_filter_capacity()); - tracing::info!("Received the shutdown signal. Initialising the shutdown..."); + filter.push_str(OFF); - shutdown_notification.cancel(); - state.shutdown().await; + for target in TARGETS { + filter.push_str(COMMA); + filter.push_str(target); + filter.push_str(INFO); } - () = shutdown_notification.cancelled() => {} + + filter } - Ok("The shutdown signal listener is shut down.".into()) -} + #[cfg(test)] + mod tests { + use tracing_subscriber::EnvFilter; + + #[test] + fn default_filter_capacity() { + assert_eq!( + super::default_filter().len(), + super::default_filter_capacity() + ); + } -/// User-supplied settings through config file -#[derive(Deserialize)] -#[serde(rename_all = "kebab-case")] -struct Config { - account_lifetime: u64, - depth: Option, - host: Option, - database: Option, - debug: bool, - in_memory_db: Option, - chain: Vec, + #[test] + fn default_filter_is_valid() { + assert!(EnvFilter::try_new(super::default_filter()).is_ok()); + } + } } -impl Config { - fn load() -> Result { - let config_path = env::var(CONFIG).or_else(|error| match error { - VarError::NotUnicode(_) => Err(Error::Env(CONFIG.to_string())), - VarError::NotPresent => { - tracing::debug!( - "`{CONFIG}` isn't present, using the default value instead: {DEFAULT_CONFIG:?}." - ); +mod shutdown { + use crate::Error; + use parking_lot::RwLock; + use std::{ + fmt::{Display, Formatter, Result as FmtResult}, + io::Result as IoResult, + panic::{self, PanicInfo}, + process, + sync::Arc, + time::Duration, + }; + use tokio::{signal, time}; + use tokio_util::sync::CancellationToken; + + #[derive(Clone)] + #[allow(clippy::module_name_repetitions)] + pub struct ShutdownNotification(CancellationToken, Arc>); + + impl ShutdownNotification { + pub fn new() -> Self { + Self( + CancellationToken::new(), + Arc::new(RwLock::new(ShutdownReason::UserRequested)), + ) + } + + pub fn is_ignited(&self) -> bool { + self.0.is_cancelled() + } + + pub fn ignite(&self) { + self.0.cancel(); + } + + pub fn reason(self) -> ShutdownReason { + *self.1.read() + } + + pub fn token(&self) -> &CancellationToken { + &self.0 + } + } - Ok(DEFAULT_CONFIG.into()) + 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() } - })?; - let unparsed_config = fs::read_to_string(&config_path) - .map_err(|_| Error::ConfigFileRead(config_path.clone()))?; + } - toml::from_str(&unparsed_config).map_err(Error::ConfigFileParse) + 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 mut current_reason = shutdown_notification.1.upgradable_read(); + + let first = match &*current_reason { + ShutdownReason::UserRequested => current_reason.with_upgraded(|reason| { + *reason = ShutdownReason::UnrecoverableError; + + true + }), + ShutdownReason::UnrecoverableError => false, + }; + + print(PrettyPanic { panic_info, first }); + + shutdown_notification.0.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/signer.rs b/src/signer.rs index 4e9b574..a5473af 100644 --- a/src/signer.rs +++ b/src/signer.rs @@ -25,8 +25,6 @@ use substrate_crypto_light::{ use tokio::sync::{mpsc, oneshot}; use zeroize::Zeroize; -const SEED: &str = "KALATORI_SEED"; - /// Signer handle pub struct Signer { tx: mpsc::Sender, @@ -34,10 +32,10 @@ pub struct Signer { impl Signer { /// Run once to initialize; this should do **all** secret management - pub fn init(recipient: AccountId32, task_tracker: TaskTracker) -> Result { + 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 = parse_seeds()?; // TODO: shutdown on failure + let mut seed_entropy = entropy_from_phrase(&seed)?; // TODO: shutdown on failure while let Some(request) = rx.recv().await { match request { SignerRequest::PublicKey(request) => { @@ -98,7 +96,7 @@ impl Signer { pub async fn shutdown(&self) { let (tx, rx) = oneshot::channel(); let _unused = self.tx.send(SignerRequest::Shutdown(tx)).await; - let _ = rx.await; + // let _ = rx.await; } /// Clone wrapper in case we need to make it more complex later @@ -135,14 +133,9 @@ struct Sign { res: oneshot::Sender>, } -/// Read seeds from env -/// -/// TODO: read also old seeds and do something about them -fn parse_seeds() -> Result { - entropy_from_phrase(&env::var(SEED).map_err(|_| SignerError::Env(SEED.to_string()))?) -} - /// 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(' ') { diff --git a/src/state.rs b/src/state.rs index 5efea14..3cfbe37 100644 --- a/src/state.rs +++ b/src/state.rs @@ -17,6 +17,7 @@ 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. @@ -37,6 +38,7 @@ impl State { chain_manager: oneshot::Receiver, instance_id: String, task_tracker: TaskTracker, + shutdown_notification: CancellationToken, ) -> Result { /* currencies: HashMap, @@ -85,48 +87,57 @@ impl State { Ok("All saved orders restored".into()) }); - while let Some(request) = rx.recv().await { - 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(), + loop { + tokio::select! { + biased; + request_option = rx.recv() => { + let Some(request) = request_option else { + break; }; - 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); + + 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)?; } - Err(e) => { - tracing::error!( - "Order was paid but this could not be recorded! {e:?}" - ) + 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 - StateAccessRequest::Shutdown => { + () = 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 @@ -142,7 +153,7 @@ impl State { // And shut down finally break; } - }; + } } Ok("State handler is shutting down".into()) @@ -216,10 +227,6 @@ impl State { tx: self.tx.clone(), } } - - pub async fn shutdown(&self) { - self.tx.send(StateAccessRequest::Shutdown).await.unwrap(); - } } enum StateAccessRequest { @@ -228,7 +235,6 @@ enum StateAccessRequest { CreateInvoice(CreateInvoice), ServerStatus(oneshot::Sender), OrderPaid(String), - Shutdown, } struct GetInvoiceStatus { diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index a8ffae8..6fd0a85 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -131,8 +131,8 @@ async fn test_daemon_status_call() { 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, KALATORI_REMARK); + assert_eq!(server_status.server_info.debug, Some(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 { From 9444fd504b7d6c98af5d65cccabc1c2d6bebda66 Mon Sep 17 00:00:00 2001 From: Fluid <90795031+fluiderson@users.noreply.github.com> Date: Tue, 18 Jun 2024 21:44:02 +0300 Subject: [PATCH 69/76] Fix `ShutdownLsitener`'s lock on `reason` --- Cargo.lock | 1 - Cargo.toml | 1 - docs/structure.md | 4 +- src/main.rs | 140 +++++++++++++++++++++++----------------------- src/signer.rs | 2 +- 5 files changed, 73 insertions(+), 75 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7679e0a..efb8bd5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1536,7 +1536,6 @@ dependencies = [ "mnemonic-external", "names", "parity-scale-codec", - "parking_lot 0.12.3", "primitive-types", "reqwest", "scale-info", diff --git a/Cargo.toml b/Cargo.toml index a3dd9ef..90229b9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,7 +47,6 @@ clap = { version = "4", features = ["derive", "cargo", "string", "env"] } shadow-rs = { version = "0.28", default-features = false } futures = "0.3" ahash = "0.8" -parking_lot = "0.12" substrate_parser = { git = "https://github.com/Alzymologist/substrate-parser" } substrate-constructor = { git = "https://github.com/Alzymologist/substrate-constructor" } diff --git a/docs/structure.md b/docs/structure.md index d862c35..89ef24a 100644 --- a/docs/structure.md +++ b/docs/structure.md @@ -54,7 +54,7 @@ The most complex module of the daemon where all the crucial blockchain-related l ### Assumptions -The current Substrate RPC API & pallets used by the daemon, unfortunately, don't have any strict standart, 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 standarts. +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`] @@ -64,7 +64,7 @@ One of difficult parts in the chain preparation is to determine & dynamically pr 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 standart, so we've stuck with the [`transfer_all`] call for transfers to both the beneficiary & overpayers accounts. +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. diff --git a/src/main.rs b/src/main.rs index 2b34c39..f11661f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,11 +1,11 @@ use clap::Parser; -use std::{borrow::Cow, future::Future, process::ExitCode, str}; +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, + oneshot, RwLock, }, task::JoinHandle, }; @@ -33,12 +33,12 @@ use state::State; fn main() -> ExitCode { let shutdown_notification = ShutdownNotification::new(); - // Sets the panic hook to print directly to the standart output because the logger isn't + // 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(reason) => match reason { + Ok(failed) => match *failed.blocking_read() { ShutdownReason::UserRequested => { tracing::info!("Goodbye!"); @@ -60,7 +60,7 @@ fn main() -> ExitCode { }; print(format_args!( - "Badbye! The daemon's got a fatal error during an initialization:\n {error}.{}", + "Badbye! The daemon's got a fatal error:\n {error}.{}", error.pretty_cause() )); @@ -69,7 +69,9 @@ fn main() -> ExitCode { } } -fn try_main(shutdown_notification: ShutdownNotification) -> Result { +fn try_main( + shutdown_notification: ShutdownNotification, +) -> Result>, Error> { let cli_args = CliArgs::parse(); logger::initialize(cli_args.log)?; @@ -90,7 +92,6 @@ fn try_main(shutdown_notification: ShutdownNotification) -> Result, config: Config, seed_env_vars: SeedEnvVars, -) -> Result { +) -> Result>, Error> { let database_path = if config.in_memory_db { if config.database.is_some() { tracing::warn!( @@ -148,7 +149,7 @@ async fn async_try_main( cm_rx, instance_id, task_tracker.clone(), - shutdown_notification.token().clone(), + shutdown_notification.token.clone(), )?; cm_tx @@ -157,12 +158,12 @@ async fn async_try_main( state.interface(), signer.interface(), task_tracker.clone(), - shutdown_notification.token().clone(), + shutdown_notification.token.clone(), )?) .map_err(|_| Error::Fatal)?; let server = server::new( - shutdown_notification.token().clone(), + shutdown_notification.token.clone(), config.host, state.interface(), ) @@ -172,30 +173,28 @@ async fn async_try_main( let shutdown_completed = CancellationToken::new(); let mut shutdown_listener = tokio::spawn(shutdown::listener( - shutdown_notification.token().clone(), + shutdown_notification.token.clone(), shutdown_completed.clone(), )); // Main loop - let notification = tokio::select! { + Ok(tokio::select! { biased; - notification = task_tracker.wait_with_notification(error_rx, shutdown_notification) => { + reason = task_tracker.wait_with_notification(error_rx, shutdown_notification) => { shutdown_completed.cancel(); + shutdown_listener.await.expect("shutdown listener shouldn't panic")?; - notification + reason } error = &mut shutdown_listener => { - return Err(error.expect("shutdown listener shouldn't panic").expect_err("shutdown listener should only complete on errors here")); + return Err( + error + .expect("shutdown listener shouldn't panic") + .expect_err("shutdown listener should only complete on errors here") + ); } - }; - - shutdown_listener - .await - .expect("shutdown listener shouldn't panic") - .unwrap(); - - Ok(notification) + }) } #[derive(Clone)] @@ -223,11 +222,12 @@ impl TaskTracker { self.inner.spawn(async move { match task.await { - Ok(shutdown_message) if !shutdown_message.is_empty() => { - tracing::info!("{shutdown_message}"); + Ok(shutdown_message) => { + if !shutdown_message.is_empty() { + tracing::info!("{shutdown_message}"); + } } Err(error) => error_tx.send((name.into(), error)).unwrap(), - _ => {} } }) } @@ -236,45 +236,49 @@ impl TaskTracker { self, mut error_rx: UnboundedReceiver<(Cow<'static, str>, Error)>, shutdown_notification: ShutdownNotification, - ) -> 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 !shutdown_notification.is_ignited() { + if failed || !shutdown_notification.is_ignited() { tracing::info!("Initialising the shutdown..."); - shutdown_notification.ignite(); + failed = true; + + shutdown_notification.ignite().await; } } self.inner.wait().await; - shutdown_notification + 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); + /* 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)?; - // } + if let Some((from, error)) = error_rx.recv().await { + return Err(error)?; + } - // self.inner.wait().await; + self.inner.wait().await; - // Ok(()) - // } + Ok(()) + } */ } mod arguments { @@ -332,7 +336,7 @@ mod arguments { 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" or "Z" UTC offset. + // 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. @@ -528,7 +532,6 @@ mod logger { mod shutdown { use crate::Error; - use parking_lot::RwLock; use std::{ fmt::{Display, Formatter, Result as FmtResult}, io::Result as IoResult, @@ -537,35 +540,31 @@ mod shutdown { sync::Arc, time::Duration, }; - use tokio::{signal, time}; + use tokio::{signal, sync::RwLock, time}; use tokio_util::sync::CancellationToken; #[derive(Clone)] #[allow(clippy::module_name_repetitions)] - pub struct ShutdownNotification(CancellationToken, Arc>); + pub struct ShutdownNotification { + pub token: CancellationToken, + pub reason: Arc>, + } impl ShutdownNotification { pub fn new() -> Self { - Self( - CancellationToken::new(), - Arc::new(RwLock::new(ShutdownReason::UserRequested)), - ) + Self { + token: CancellationToken::new(), + reason: Arc::new(RwLock::new(ShutdownReason::UserRequested)), + } } pub fn is_ignited(&self) -> bool { - self.0.is_cancelled() - } - - pub fn ignite(&self) { - self.0.cancel(); + self.token.is_cancelled() } - pub fn reason(self) -> ShutdownReason { - *self.1.read() - } - - pub fn token(&self) -> &CancellationToken { - &self.0 + pub async fn ignite(&self) { + *self.reason.write().await = ShutdownReason::UnrecoverableError; + self.token.cancel(); } } @@ -642,20 +641,21 @@ mod shutdown { shutdown_notification: ShutdownNotification, ) { panic::set_hook(Box::new(move |panic_info| { - let mut current_reason = shutdown_notification.1.upgradable_read(); + let reason = *shutdown_notification.reason.blocking_read(); - let first = match &*current_reason { - ShutdownReason::UserRequested => current_reason.with_upgraded(|reason| { - *reason = ShutdownReason::UnrecoverableError; + let first = match reason { + ShutdownReason::UserRequested => false, + ShutdownReason::UnrecoverableError => { + *shutdown_notification.reason.blocking_write() = + ShutdownReason::UnrecoverableError; true - }), - ShutdownReason::UnrecoverableError => false, + } }; print(PrettyPanic { panic_info, first }); - shutdown_notification.0.cancel(); + shutdown_notification.token.cancel(); })); } diff --git a/src/signer.rs b/src/signer.rs index a5473af..47b758a 100644 --- a/src/signer.rs +++ b/src/signer.rs @@ -144,7 +144,7 @@ pub fn entropy_from_phrase(seed: &str) -> Result { Ok(word_set.to_entropy()?) } -/// Standartized derivation protocol +/// Standardized derivation protocol pub fn derivations<'a>(recipient: &'a str, order: &'a str) -> FullDerivation<'a> { FullDerivation { junctions: vec![DeriveJunction::hard(recipient), DeriveJunction::hard(order)], From 771c51e7c4a0d6759ecc0126b2cd35f42965e091 Mon Sep 17 00:00:00 2001 From: Fluid <90795031+fluiderson@users.noreply.github.com> Date: Tue, 18 Jun 2024 22:36:12 +0300 Subject: [PATCH 70/76] Update to latest `jsonrpsee` --- Cargo.lock | 314 +++++++++++++++++++++++-------------- Cargo.toml | 2 +- src/error.rs | 4 + src/signer.rs | 6 +- tests/integration_tests.rs | 5 +- 5 files changed, 207 insertions(+), 124 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index efb8bd5..f1def09 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -163,7 +163,7 @@ dependencies = [ "axum-core", "bytes", "futures-util", - "http 1.1.0", + "http", "http-body", "http-body-util", "hyper", @@ -195,7 +195,7 @@ dependencies = [ "async-trait", "bytes", "futures-util", - "http 1.1.0", + "http", "http-body", "http-body-util", "mime", @@ -220,9 +220,9 @@ dependencies = [ [[package]] name = "backtrace" -version = "0.3.72" +version = "0.3.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17c6a35df3749d2e8bb1b7b21a976d82b15548788d2735b9d82f329268f71a11" +checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" dependencies = [ "addr2line", "cc", @@ -245,12 +245,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6107fe1be6682a68940da878d9e9f5e90ca5745b3dec9fd1bb393c8777d4f581" -[[package]] -name = "base64" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" - [[package]] name = "base64" version = "0.22.1" @@ -317,15 +311,6 @@ dependencies = [ "constant_time_eq 0.3.0", ] -[[package]] -name = "block-buffer" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" -dependencies = [ - "generic-array", -] - [[package]] name = "block-buffer" version = "0.10.4" @@ -376,6 +361,12 @@ dependencies = [ "once_cell", ] +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + [[package]] name = "cfg-if" version = "1.0.0" @@ -434,6 +425,16 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + [[package]] name = "const-oid" version = "0.9.6" @@ -551,16 +552,15 @@ dependencies = [ [[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", @@ -598,22 +598,13 @@ dependencies = [ [[package]] name = "derive_more" -version = "0.99.17" +version = "0.99.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +checksum = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", -] - -[[package]] -name = "digest" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" -dependencies = [ - "generic-array", + "syn 2.0.66", ] [[package]] @@ -622,7 +613,7 @@ version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer 0.10.4", + "block-buffer", "const-oid", "crypto-common", "subtle", @@ -646,7 +637,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" dependencies = [ "der", - "digest 0.10.7", + "digest", "elliptic-curve", "rfc6979", "signature", @@ -685,7 +676,7 @@ checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" dependencies = [ "base16ct", "crypto-bigint", - "digest 0.10.7", + "digest", "ff", "generic-array", "group", @@ -1023,7 +1014,7 @@ dependencies = [ "fnv", "futures-core", "futures-sink", - "http 1.1.0", + "http", "indexmap", "slab", "tokio", @@ -1071,18 +1062,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest 0.10.7", -] - -[[package]] -name = "http" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" -dependencies = [ - "bytes", - "fnv", - "itoa", + "digest", ] [[package]] @@ -1103,7 +1083,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" dependencies = [ "bytes", - "http 1.1.0", + "http", ] [[package]] @@ -1114,16 +1094,16 @@ checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" dependencies = [ "bytes", "futures-util", - "http 1.1.0", + "http", "http-body", "pin-project-lite", ] [[package]] name = "httparse" -version = "1.9.2" +version = "1.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f3935c160d00ac752e09787e6e6bfc26494c2183cc922f1bc678a60d4733bc2" +checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" [[package]] name = "httpdate" @@ -1141,7 +1121,7 @@ dependencies = [ "futures-channel", "futures-util", "h2", - "http 1.1.0", + "http", "http-body", "httparse", "httpdate", @@ -1152,6 +1132,23 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-rustls" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee4be2c948921a1a5320b629c4193916ed787a7f7f293fd3f7f5a6c9de74155" +dependencies = [ + "futures-util", + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + [[package]] name = "hyper-tls" version = "0.6.0" @@ -1177,7 +1174,7 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "http 1.1.0", + "http", "http-body", "hyper", "pin-project-lite", @@ -1308,9 +1305,9 @@ dependencies = [ [[package]] name = "idna" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4716a3a0933a1d01c2f72450e89596eb51dd34ef3c211ccd875acdf1f8fe47ed" +checksum = "44a986806a1cc899952ba462bc1f28afbfd5850ab6cb030ccb20dd02cc527a24" dependencies = [ "icu_normalizer", "icu_properties", @@ -1409,6 +1406,26 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +[[package]] +name = "jni" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6df18c2e3db7e453d3c6ac5b3e9d5182664d28788126d39b91f2d1e22b017ec" +dependencies = [ + "cesu8", + "combine", + "jni-sys", + "log", + "thiserror", + "walkdir", +] + +[[package]] +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.31" @@ -1429,9 +1446,9 @@ dependencies = [ [[package]] name = "jsonrpsee" -version = "0.22.5" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfdb12a2381ea5b2e68c3469ec604a007b367778cdb14d09612c8069ebd616ad" +checksum = "95a130d27083a4001b7b2d72a19f08786299550f76c9bd5307498dce2c2b20fa" dependencies = [ "jsonrpsee-core", "jsonrpsee-types", @@ -1440,16 +1457,18 @@ dependencies = [ [[package]] name = "jsonrpsee-client-transport" -version = "0.22.5" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4978087a58c3ab02efc5b07c5e5e2803024536106fd5506f558db172c889b3aa" +checksum = "039db9fe25cd63b7221c3f8788c1ef4ea07987d40ec25a1e7d7a3c3e3e3fd130" dependencies = [ + "base64", "futures-util", - "http 0.2.12", + "http", "jsonrpsee-core", "pin-project", - "rustls-native-certs", + "rustls", "rustls-pki-types", + "rustls-platform-verifier", "soketto", "thiserror", "tokio", @@ -1461,9 +1480,9 @@ dependencies = [ [[package]] name = "jsonrpsee-core" -version = "0.22.5" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4b257e1ec385e07b0255dde0b933f948b5c8b8c28d42afda9587c3a967b896d" +checksum = "21545a9445fbd582840ff5160a9a3e12b8e6da582151cdb07bde9a1970ba3a24" dependencies = [ "anyhow", "async-trait", @@ -1483,12 +1502,12 @@ dependencies = [ [[package]] name = "jsonrpsee-types" -version = "0.22.5" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "150d6168405890a7a3231a3c74843f58b8959471f6df76078db2619ddee1d07d" +checksum = "f511b714bca46f9a3e97c0e0eb21d2c112e83e444d2db535b5ec7093f5836d73" dependencies = [ - "anyhow", "beef", + "http", "serde", "serde_json", "thiserror", @@ -1496,11 +1515,11 @@ dependencies = [ [[package]] name = "jsonrpsee-ws-client" -version = "0.22.5" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58b9db2dfd5bb1194b0ce921504df9ceae210a345bc2f6c5a61432089bbab070" +checksum = "786c100eb67df2f2d863d231c2c6978bcf80ff4bf606ffc40e7e68ef562da7bf" dependencies = [ - "http 0.2.12", + "http", "jsonrpsee-client-transport", "jsonrpsee-core", "jsonrpsee-types", @@ -1650,9 +1669,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" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "merlin" @@ -1674,9 +1693,9 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "miniz_oxide" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87dfd01fe195c66b572b37921ad8803d010623c0aca821bea2302239d155cdae" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" dependencies = [ "adler", "simd-adler32", @@ -1800,9 +1819,9 @@ dependencies = [ [[package]] name = "object" -version = "0.35.0" +version = "0.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8ec7ab813848ba4522158d5517a6093db1ded27575b070f4177b8d12b41db5e" +checksum = "576dfe1fc8f9df304abb159d767a29d0476f7750fbf8aa7ad07816004a207434" dependencies = [ "memchr", ] @@ -1813,12 +1832,6 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" -[[package]] -name = "opaque-debug" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" - [[package]] name = "openssl" version = "0.10.64" @@ -1961,7 +1974,7 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.1", + "redox_syscall 0.5.2", "smallvec", "windows-targets 0.52.5", ] @@ -1972,7 +1985,7 @@ version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" dependencies = [ - "digest 0.10.7", + "digest", ] [[package]] @@ -2029,12 +2042,6 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" -[[package]] -name = "platforms" -version = "3.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db23d408679286588f4d4644f965003d056e3dd5abcaaa938116871d7ce2fee7" - [[package]] name = "plot_icon" version = "0.3.0" @@ -2157,9 +2164,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" +checksum = "c82cf8cff14456045f55ec4241383baeff27af886adb72ffb2162f99911de0fd" dependencies = [ "bitflags 2.5.0", ] @@ -2210,20 +2217,21 @@ checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" [[package]] name = "reqwest" -version = "0.12.4" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "566cafdd92868e0939d3fb961bd0dc25fcfaaed179291093b3d43e6b3150ea10" +checksum = "c7d6d2a27d57148378eb5e111173f4276ad26340ecc5c49a4a2152167a2d6a37" dependencies = [ - "base64 0.22.1", + "base64", "bytes", "encoding_rs", "futures-core", "futures-util", "h2", - "http 1.1.0", + "http", "http-body", "http-body-util", "hyper", + "hyper-rustls", "hyper-tls", "hyper-util", "ipnet", @@ -2238,7 +2246,7 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", - "sync_wrapper 0.1.2", + "sync_wrapper 1.0.1", "system-configuration", "tokio", "tokio-native-tls", @@ -2317,11 +2325,12 @@ dependencies = [ [[package]] name = "rustls" -version = "0.22.4" +version = "0.23.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" +checksum = "05cff451f60db80f490f3c182b77c35260baace73209e9cdbbe526bfe3a4d402" dependencies = [ "log", + "once_cell", "ring", "rustls-pki-types", "rustls-webpki", @@ -2348,7 +2357,7 @@ version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" dependencies = [ - "base64 0.22.1", + "base64", "rustls-pki-types", ] @@ -2358,6 +2367,33 @@ version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" +[[package]] +name = "rustls-platform-verifier" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5f0d26fa1ce3c790f9590868f0109289a044acb954525f933e2aa3b871c157d" +dependencies = [ + "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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84e217e7fdc8466b5b35d30f8c0a30febd29173df4a3a0c2115d306b9c4117ad" + [[package]] name = "rustls-webpki" version = "0.102.4" @@ -2381,6 +2417,15 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "scale-info" version = "2.11.3" @@ -2463,6 +2508,7 @@ dependencies = [ "core-foundation", "core-foundation-sys", "libc", + "num-bigint", "security-framework-sys", ] @@ -2545,16 +2591,14 @@ dependencies = [ ] [[package]] -name = "sha-1" -version = "0.9.8" +name = "sha1" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ - "block-buffer 0.9.0", "cfg-if", "cpufeatures", - "digest 0.9.0", - "opaque-debug", + "digest", ] [[package]] @@ -2565,7 +2609,7 @@ checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.10.7", + "digest", ] [[package]] @@ -2574,7 +2618,7 @@ version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" dependencies = [ - "digest 0.10.7", + "digest", "keccak", ] @@ -2614,7 +2658,7 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ - "digest 0.10.7", + "digest", "rand_core", ] @@ -2667,17 +2711,17 @@ dependencies = [ [[package]] name = "soketto" -version = "0.7.1" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d1c5305e39e09653383c2c7244f2f78b3bcae37cf50c64cb4789c9f5096ec2" +checksum = "37468c595637c10857701c990f93a40ce0e357cedb0953d1c26c8d8027f9bb53" dependencies = [ - "base64 0.13.1", + "base64", "bytes", "futures", "httparse", "log", "rand", - "sha-1", + "sha1", ] [[package]] @@ -2702,7 +2746,7 @@ checksum = "bc9927a7f81334ed5b8a98a4a978c81324d12bd9713ec76b5c68fd410174c5eb" dependencies = [ "blake2b_simd", "byteorder", - "digest 0.10.7", + "digest", "sha2", "sha3", "twox-hash", @@ -3019,9 +3063,9 @@ dependencies = [ [[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", "rustls-pki-types", @@ -3186,7 +3230,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" dependencies = [ "cfg-if", - "digest 0.10.7", + "digest", "rand", "static_assertions", ] @@ -3233,7 +3277,7 @@ version = "2.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d11a831e3c0b56e438a28308e7c810799e3c118417f342d30ecec080105395cd" dependencies = [ - "base64 0.22.1", + "base64", "log", "once_cell", "serde", @@ -3282,6 +3326,16 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "want" version = "0.3.1" @@ -3373,6 +3427,15 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki-roots" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd7c23921eeb1713a4e851530e9b9756e4fb0e89978582942612524cf09f01cd" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "winapi" version = "0.3.9" @@ -3389,6 +3452,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" diff --git a/Cargo.toml b/Cargo.toml index 90229b9..45891e6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,7 +33,7 @@ tracing = "0.1" scale-info = "2" axum-macros = "0.4" primitive-types = { version = "0.12", features = ["codec"] } -jsonrpsee = { version = "0.22", features = ["ws-client"] } +jsonrpsee = { version = "0.23", features = ["ws-client"] } thiserror = "1" frame-metadata = "16" hex = "0.4" diff --git a/src/error.rs b/src/error.rs index 2a4a31f..b38ef4b 100644 --- a/src/error.rs +++ b/src/error.rs @@ -6,6 +6,7 @@ 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}; @@ -280,6 +281,9 @@ pub enum ChainError { actual: String, rpc: String, }, + + #[error("failed to parse JSON data from a block stream")] + Serde(#[from] JsonError), } #[derive(Debug, Error)] diff --git a/src/signer.rs b/src/signer.rs index 47b758a..0017fba 100644 --- a/src/signer.rs +++ b/src/signer.rs @@ -32,7 +32,11 @@ pub struct Signer { impl Signer { /// Run once to initialize; this should do **all** secret management - pub fn init(recipient: AccountId32, task_tracker: TaskTracker, seed: String) -> Result { + 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 diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 6fd0a85..4f8a046 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -132,7 +132,10 @@ async fn test_daemon_status_call() { ); assert!(!server_status.server_info.instance_id.is_empty()); assert_eq!(server_status.server_info.debug, Some(true)); - assert_eq!(server_status.server_info.kalatori_remark, Some(KALATORI_REMARK.into())); + 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 { From cfdb1107e399c09956637fbaab086d8517e9d4b2 Mon Sep 17 00:00:00 2001 From: Fluid <90795031+fluiderson@users.noreply.github.com> Date: Fri, 21 Jun 2024 02:30:38 +0300 Subject: [PATCH 71/76] Fix remarks --- Cargo.lock | 295 +++++-------------------------------- src/definitions.rs | 3 +- src/main.rs | 18 ++- src/state.rs | 2 +- tests/integration_tests.rs | 2 +- 5 files changed, 50 insertions(+), 270 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f1def09..17cfdb3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -334,9 +334,9 @@ checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" [[package]] name = "bytemuck" -version = "1.16.0" +version = "1.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78834c15cb5d5efe3452d58b1e8ba890dd62d21907f867f383358198e56ebca5" +checksum = "b236fc92302c97ed75b38da1f4917b5cdda4984745740f153a5d3059e48d725e" [[package]] name = "byteorder" @@ -619,17 +619,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "displaydoc" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.66", -] - [[package]] name = "ecdsa" version = "0.16.9" @@ -1185,134 +1174,14 @@ dependencies = [ "tracing", ] -[[package]] -name = "icu_collections" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" -dependencies = [ - "displaydoc", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_locid" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" -dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_locid_transform" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_locid_transform_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_locid_transform_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" - -[[package]] -name = "icu_normalizer" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "utf16_iter", - "utf8_iter", - "write16", - "zerovec", -] - -[[package]] -name = "icu_normalizer_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" - -[[package]] -name = "icu_properties" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f8ac670d7422d7f76b32e17a5db556510825b29ec9154f235977c9caba61036" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_locid_transform", - "icu_properties_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_properties_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" - -[[package]] -name = "icu_provider" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_provider_macros", - "stable_deref_trait", - "tinystr", - "writeable", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_provider_macros" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.66", -] - [[package]] name = "idna" -version = "1.0.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44a986806a1cc899952ba462bc1f28afbfd5850ab6cb030ccb20dd02cc527a24" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" dependencies = [ - "icu_normalizer", - "icu_properties", - "smallvec", - "utf8_iter", + "unicode-bidi", + "unicode-normalization", ] [[package]] @@ -1630,12 +1499,6 @@ version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" -[[package]] -name = "litemap" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704" - [[package]] name = "lock_api" version = "0.4.12" @@ -2780,12 +2643,6 @@ dependencies = [ "der", ] -[[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" @@ -2860,9 +2717,9 @@ dependencies = [ [[package]] name = "subtle" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" +checksum = "0d0208408ba0c3df17ed26eb06992cb1a1268d41b2c0e12e65203fbe3972cee5" [[package]] name = "syn" @@ -2898,17 +2755,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" -[[package]] -name = "synstructure" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.66", -] - [[package]] name = "system-configuration" version = "0.5.1" @@ -3012,15 +2858,20 @@ dependencies = [ ] [[package]] -name = "tinystr" -version = "0.7.6" +name = "tinyvec" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" dependencies = [ - "displaydoc", - "zerovec", + "tinyvec_macros", ] +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "tokio" version = "1.38.0" @@ -3253,12 +3104,27 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "unicode-bidi" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" + [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "unicode-normalization" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +dependencies = [ + "tinyvec", +] + [[package]] name = "unicode-xid" version = "0.2.4" @@ -3287,27 +3153,15 @@ dependencies = [ [[package]] name = "url" -version = "2.5.1" +version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7c25da092f0a868cdf09e8674cd3b7ef3a7d92a24253e663a2fb85e2496de56" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" dependencies = [ "form_urlencoded", "idna", "percent-encoding", ] -[[package]] -name = "utf16_iter" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" - -[[package]] -name = "utf8_iter" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" - [[package]] name = "utf8parse" version = "0.2.2" @@ -3634,18 +3488,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "write16" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" - -[[package]] -name = "writeable" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" - [[package]] name = "wyz" version = "0.5.1" @@ -3655,30 +3497,6 @@ dependencies = [ "tap", ] -[[package]] -name = "yoke" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5" -dependencies = [ - "serde", - "stable_deref_trait", - "yoke-derive", - "zerofrom", -] - -[[package]] -name = "yoke-derive" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.66", - "synstructure", -] - [[package]] name = "zerocopy" version = "0.7.34" @@ -3699,27 +3517,6 @@ dependencies = [ "syn 2.0.66", ] -[[package]] -name = "zerofrom" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55" -dependencies = [ - "zerofrom-derive", -] - -[[package]] -name = "zerofrom-derive" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.66", - "synstructure", -] - [[package]] name = "zeroize" version = "1.8.1" @@ -3739,25 +3536,3 @@ dependencies = [ "quote", "syn 2.0.66", ] - -[[package]] -name = "zerovec" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb2cc8827d6c0994478a15c53f374f46fbd41bea663d809b14744bc42e6b109c" -dependencies = [ - "yoke", - "zerofrom", - "zerovec-derive", -] - -[[package]] -name = "zerovec-derive" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97cf56601ee5052b4417d90c8755c6683473c926039908196cf35d99f893ebe7" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.66", -] diff --git a/src/definitions.rs b/src/definitions.rs index 03b5c5f..979cb7e 100644 --- a/src/definitions.rs +++ b/src/definitions.rs @@ -266,8 +266,7 @@ pub mod api_v2 { pub struct ServerInfo { pub version: String, pub instance_id: String, - #[serde(skip_serializing_if = "Option::is_none")] - pub debug: Option, + pub debug: bool, #[serde(skip_serializing_if = "Option::is_none")] pub kalatori_remark: Option, } diff --git a/src/main.rs b/src/main.rs index f11661f..625229a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -301,8 +301,14 @@ mod arguments { use shadow::{BUILD_TIME_3339, RUST_VERSION, SHORT_COMMIT}; - pub const SEED: &str = "SEED"; - pub const OLD_SEED: &str = "OLD_SEED_"; + 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"; @@ -350,7 +356,7 @@ mod arguments { #[arg( short, long, - env, + env(env_var_prefix!("CONFIG")), value_name("PATH"), default_value("configs/polkadot.toml") )] @@ -359,7 +365,7 @@ mod arguments { #[arg( short, long, - env, + env(env_var_prefix!("LOG")), value_name("DIRECTIVES"), default_value(logger::default_filter()), default_missing_value(""), @@ -368,10 +374,10 @@ mod arguments { )] pub log: String, - #[arg(long, env, visible_alias("rmrk"), value_name("STRING"))] + #[arg(long, env(env_var_prefix!("REMARK")), visible_alias("rmrk"), value_name("STRING"))] pub remark: Option, - #[arg(short, long, env, value_name("HEX/SS58 ADDRESS"))] + #[arg(short, long, env(env_var_prefix!("RECIPIENT")), value_name("HEX/SS58 ADDRESS"))] pub recipient: String, } diff --git a/src/state.rs b/src/state.rs index 3cfbe37..628970e 100644 --- a/src/state.rs +++ b/src/state.rs @@ -57,7 +57,7 @@ impl State { // TODO version: env!("CARGO_PKG_VERSION").to_string(), instance_id: instance_id.clone(), - debug, + debug: debug.unwrap_or_default(), kalatori_remark: remark.clone(), }; diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 4f8a046..2e942d3 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -131,7 +131,7 @@ async fn test_daemon_status_call() { KALATORI_CARGO_PACKAGE_VERSION ); assert!(!server_status.server_info.instance_id.is_empty()); - assert_eq!(server_status.server_info.debug, Some(true)); + assert_eq!(server_status.server_info.debug, true); assert_eq!( server_status.server_info.kalatori_remark, Some(KALATORI_REMARK.into()) From 04590fb52e9e36d2f5f384a66007fffe8595e8d2 Mon Sep 17 00:00:00 2001 From: Slesarew Date: Mon, 16 Sep 2024 12:01:31 +0300 Subject: [PATCH 72/76] feat: scan whole storage --- shoot.sh | 2 +- src/chain/rpc.rs | 77 ++++++++++++++++++++++++++++++++---------------- 2 files changed, 53 insertions(+), 26 deletions(-) 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/chain/rpc.rs b/src/chain/rpc.rs index dd3addb..9a2094c 100644 --- a/src/chain/rpc.rs +++ b/src/chain/rpc.rs @@ -43,6 +43,12 @@ const BLOCK_NONCE_ERROR: &str = "failed to fetch an account nonce by the scanner 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"; @@ -99,33 +105,54 @@ pub async fn get_keys_from_storage( prefix: &str, storage_name: &str, block: &BlockHash, -) -> Result { +) -> 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 = 100; // TODO make full scan just in case - let start_key: Option<&str> = None; // Start from the beginning + let count = 10; // TODO make full scan just in case + let mut start_key: Option = None; // Start from the beginning - let mut params = vec![ + let params_template = vec![ serde_json::to_value(storage_key_prefix.clone()).unwrap(), serde_json::to_value(count).unwrap(), ]; - if let Some(start_key) = start_key { - params.push(serde_json::to_value(start_key).unwrap()); - } - - params.push(serde_json::to_value(block.to_string()).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()); + } - let keys: Value = client - .request("state_getKeysPaged", params) - .await - .map_err(ChainError::Client)?; + 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) + Ok(keys_vec) } /// fetch genesis hash, must be a hexadecimal string transformable into @@ -297,8 +324,9 @@ pub async fn assets_set_at_block( assets_asset_storage_metadata, assets_metadata_storage_metadata, ) { - let available_keys_assets_asset = + 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 { @@ -517,6 +545,7 @@ pub async fn assets_set_at_block( } } } + } } Ok(assets_set) } @@ -626,8 +655,9 @@ async fn events_at_block( events_entry_metadata: &StorageEntryMetadata, types: &PortableRegistry, ) -> Result, ChainError> { - let keys_from_storage = get_keys_from_storage(client, "System", "Events", block).await?; + 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 { @@ -679,13 +709,13 @@ async fn events_at_block( } } } - return Ok(out); } _ => { tracing::warn!("{keys_from_storage}"); - return Err(ChainError::EventsMissing); } } + } + return Ok(out); } pub async fn current_block_number( @@ -728,12 +758,9 @@ pub async fn get_nonce( Ok(()) } -pub async fn send_stuff(client: &WsClient, data: &str) -> Result<(), ChainError> { +pub async fn send_stuff(client: &WsClient, data: &str) -> Result { let rpc_params = rpc_params![data]; - let mut subscription: Subscription = client - .subscribe("author_submitAndWatchExtrinsic", rpc_params, "") - .await?; - let _reply = subscription.next().await.unwrap(); - //println!("{reply:?}"); // TODO! - Ok(()) + Ok(client + .request("author_submitAndWatchExtrinsic", rpc_params) + .await?) } From 8a5515e47279f46bcd6a1d18d73ce32751e8e383 Mon Sep 17 00:00:00 2001 From: Slesarew Date: Mon, 16 Sep 2024 12:04:33 +0300 Subject: [PATCH 73/76] chore: fmt --- src/chain/rpc.rs | 366 ++++++++++++++++++++++++----------------------- 1 file changed, 188 insertions(+), 178 deletions(-) diff --git a/src/chain/rpc.rs b/src/chain/rpc.rs index 9a2094c..e1a678a 100644 --- a/src/chain/rpc.rs +++ b/src/chain/rpc.rs @@ -128,27 +128,26 @@ pub async fn get_keys_from_storage( } params.push(serde_json::to_value(block.to_string()).unwrap()); - if let Ok(keys) = client - .request("state_getKeysPaged", params) - .await - { + 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 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) + return Ok(keys_vec); } } else { - return Ok(keys_vec) + return Ok(keys_vec); } - } else { - return Ok(keys_vec) + } else { + return Ok(keys_vec); }; keys_vec.push(keys); - } else { - return Ok(keys_vec) + } else { + return Ok(keys_vec); } } @@ -327,114 +326,120 @@ pub async fn assets_set_at_block( 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) + 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::AssetIdFormat) + Err(ChainError::AssetKeyEmpty) } } else { - Err(ChainError::AssetKeyEmpty) + Err(ChainError::AssetKeyNotSingleHash) } - } 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; + }?; + 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; } - 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 + 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 { - for field_data in fields.iter() { - if let Some(field_name) = - &field_data.field_name - { - match field_name.as_str() { + 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()); @@ -502,41 +507,44 @@ pub async fn assets_set_at_block( }, _ => {}, } + } + if name.is_some() + && symbol.is_some() + && decimals.is_some() + { + break; + } } - if name.is_some() - && symbol.is_some() - && decimals.is_some() + if let (Some(symbol), Some(decimals)) = + (symbol, decimals) { - break; + 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, + }, + ); } - } - 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, ); } - } else { - return Err( - ChainError::AssetMetadataUnexpected, - ); } } - } - _ => return Err(ChainError::AssetMetadataType), + _ => return Err(ChainError::AssetMetadataType), + } + } else { + return Err(ChainError::AssetMetadataMapSize); } - } else { - return Err(ChainError::AssetMetadataMapSize); } } } @@ -545,7 +553,6 @@ pub async fn assets_set_at_block( } } } - } } Ok(assets_set) } @@ -658,48 +665,52 @@ async fn events_at_block( 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 + 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 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 + 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 if event.pallet_name == filter.pallet { + } else { out.push(Event(event.to_owned())); } - } else { - out.push(Event(event.to_owned())); } } } @@ -709,13 +720,12 @@ async fn events_at_block( } } } + _ => { + tracing::warn!("{keys_from_storage}"); + } } - _ => { - tracing::warn!("{keys_from_storage}"); - } - } } - return Ok(out); + return Ok(out); } pub async fn current_block_number( From 71dcd2226824f0491f424aaf13da2e9b14f8549e Mon Sep 17 00:00:00 2001 From: Slesarew <33295157+Slesarew@users.noreply.github.com> Date: Mon, 16 Sep 2024 23:55:54 +0300 Subject: [PATCH 74/76] Update src/server.rs Co-authored-by: Vova Lando --- src/server.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/server.rs b/src/server.rs index dd7c0c0..f37943b 100644 --- a/src/server.rs +++ b/src/server.rs @@ -95,9 +95,6 @@ async fn process_order( .parse() .map_err(|_| OrderError::InvalidParameter(AMOUNT.into()))?; - if currency != "USDC" { - return Err(OrderError::UnknownCurrency); - } if amount < 0.07 { return Err(OrderError::LessThanExistentialDeposit(0.07)); From f341a3951fcb3ab0d84963947fe6f0e7cb01b5c0 Mon Sep 17 00:00:00 2001 From: Slesarew Date: Tue, 17 Sep 2024 20:52:56 +0300 Subject: [PATCH 75/76] chore: bump version --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 17cfdb3..54cdf5e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1409,7 +1409,7 @@ dependencies = [ [[package]] name = "kalatori" -version = "0.2.0-rc3" +version = "0.2.0-rc4" dependencies = [ "ahash", "axum", diff --git a/Cargo.toml b/Cargo.toml index 45891e6..27d5954 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "kalatori" authors = ["Alzymologist Oy "] -version = "0.2.0-rc3" +version = "0.2.0-rc4" edition = "2021" description = "A gateway daemon for Kalatori." license = "GPL-3.0-or-later" From 35a373e25d40546698b5bb5042df299679e3dfc0 Mon Sep 17 00:00:00 2001 From: Slesarew Date: Tue, 17 Sep 2024 21:26:47 +0300 Subject: [PATCH 76/76] chore: fmt --- src/server.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/server.rs b/src/server.rs index f37943b..0917b11 100644 --- a/src/server.rs +++ b/src/server.rs @@ -95,7 +95,6 @@ async fn process_order( .parse() .map_err(|_| OrderError::InvalidParameter(AMOUNT.into()))?; - if amount < 0.07 { return Err(OrderError::LessThanExistentialDeposit(0.07)); }