diff --git a/rln-prover/Cargo.lock b/rln-prover/Cargo.lock index 9b61320473..d8daf23288 100644 --- a/rln-prover/Cargo.lock +++ b/rln-prover/Cargo.lock @@ -2,6 +2,17 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "ahash" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" +dependencies = [ + "getrandom 0.2.16", + "once_cell", + "version_check", +] + [[package]] name = "ahash" version = "0.8.12" @@ -23,6 +34,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "aliasable" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" + [[package]] name = "allocator-api2" version = "0.2.21" @@ -592,7 +609,7 @@ dependencies = [ "alloy-json-abi", "alloy-sol-macro-input", "const-hex", - "heck", + "heck 0.5.0", "indexmap 2.12.0", "proc-macro-error2", "proc-macro2", @@ -611,7 +628,7 @@ dependencies = [ "alloy-json-abi", "const-hex", "dunce", - "heck", + "heck 0.5.0", "macro-string", "proc-macro2", "quote", @@ -722,7 +739,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8e52276fdb553d3c11563afad2898f4085165e4093604afe3d78b69afbf408f" dependencies = [ "alloy-primitives", - "darling", + "darling 0.21.3", "proc-macro2", "quote", "syn 2.0.107", @@ -817,7 +834,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e0c292754729c8a190e50414fd1a37093c786c709899f29c9f7daccecfa855e" dependencies = [ - "ahash", + "ahash 0.8.12", "ark-crypto-primitives-macros", "ark-ec", "ark-ff 0.5.0", @@ -850,7 +867,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43d68f2d516162846c1238e755a7c4d131b892b70cc70c471a8e3ca3ed818fce" dependencies = [ - "ahash", + "ahash 0.8.12", "ark-ff 0.5.0", "ark-poly", "ark-serialize 0.5.0", @@ -1012,7 +1029,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "579305839da207f02b89cd1679e50e67b4331e2f9294a57693e5051b7703fe27" dependencies = [ - "ahash", + "ahash 0.8.12", "ark-ff 0.5.0", "ark-serialize 0.5.0", "ark-std 0.5.0", @@ -1202,6 +1219,15 @@ dependencies = [ "rustc_version 0.4.1", ] +[[package]] +name = "atoi" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" +dependencies = [ + "num-traits", +] + [[package]] name = "atomic-waker" version = "1.1.2" @@ -1309,6 +1335,20 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" +[[package]] +name = "bigdecimal" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "560f42649de9fa436b73517378a147ec21f6c997a546581df4b4b31677828934" +dependencies = [ + "autocfg", + "libm", + "num-bigint", + "num-integer", + "num-traits", + "serde", +] + [[package]] name = "bimap" version = "0.6.3" @@ -1397,6 +1437,9 @@ name = "bitflags" version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +dependencies = [ + "serde_core", +] [[package]] name = "bitvec" @@ -1440,6 +1483,29 @@ dependencies = [ "zeroize", ] +[[package]] +name = "borsh" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad8646f98db542e39fc66e68a20b2144f6a732636df7c2354e74645faaa433ce" +dependencies = [ + "borsh-derive", + "cfg_aliases", +] + +[[package]] +name = "borsh-derive" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdd1d3c0c2f5833f22386f252fe8ed005c7f59fdcddeef025c01b4c3b9fd9ac3" +dependencies = [ + "once_cell", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.107", +] + [[package]] name = "bumpalo" version = "3.19.0" @@ -1452,6 +1518,28 @@ version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7575182f7272186991736b70173b0ea045398f984bf5ebbb3804736ce1330c9d" +[[package]] +name = "bytecheck" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2" +dependencies = [ + "bytecheck_derive", + "ptr_meta", + "simdutf8", +] + +[[package]] +name = "bytecheck_derive" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "byteorder" version = "1.5.0" @@ -1625,7 +1713,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46efb9cbf691f5505d0b7b2c8055aec0c9a770eaac8a06834b6d84b5be93279a" dependencies = [ "clap", - "heck", + "heck 0.5.0", "proc-macro2", "quote", "serde", @@ -1638,7 +1726,7 @@ version = "4.5.49" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro2", "quote", "syn 2.0.107", @@ -1712,6 +1800,16 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation" version = "0.10.1" @@ -1814,6 +1912,15 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "crossbeam-queue" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.21" @@ -1848,14 +1955,37 @@ dependencies = [ "typenum", ] +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core 0.20.11", + "darling_macro 0.20.11", +] + [[package]] name = "darling" version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" dependencies = [ - "darling_core", - "darling_macro", + "darling_core 0.21.3", + "darling_macro 0.21.3", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "syn 2.0.107", ] [[package]] @@ -1873,13 +2003,24 @@ dependencies = [ "syn 2.0.107", ] +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core 0.20.11", + "quote", + "syn 2.0.107", +] + [[package]] name = "darling_macro" version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" dependencies = [ - "darling_core", + "darling_core 0.21.3", "quote", "syn 2.0.107", ] @@ -1911,6 +2052,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" dependencies = [ "const-oid", + "pem-rfc7468", "zeroize", ] @@ -1988,6 +2130,12 @@ dependencies = [ "syn 2.0.107", ] +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + [[package]] name = "dunce" version = "1.0.5" @@ -2092,6 +2240,17 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "etcetera" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" +dependencies = [ + "cfg-if", + "home", + "windows-sys 0.48.0", +] + [[package]] name = "event-listener" version = "5.4.1" @@ -2175,6 +2334,17 @@ version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" +[[package]] +name = "flume" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" +dependencies = [ + "futures-core", + "futures-sink", + "spin", +] + [[package]] name = "fnv" version = "1.0.7" @@ -2193,6 +2363,21 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" +[[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.2" @@ -2218,6 +2403,21 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" +[[package]] +name = "function_name" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1ab577a896d09940b5fe12ec5ae71f9d8211fff62c919c03a3750a9901e98a7" +dependencies = [ + "function_name-proc-macro", +] + +[[package]] +name = "function_name-proc-macro" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673464e1e314dd67a0fd9544abc99e8eb28d0c7e3b69b033bcff9b2d00b87333" + [[package]] name = "funty" version = "2.0.0" @@ -2266,6 +2466,17 @@ dependencies = [ "futures-util", ] +[[package]] +name = "futures-intrusive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" +dependencies = [ + "futures-core", + "lock_api", + "parking_lot 0.12.5", +] + [[package]] name = "futures-io" version = "0.3.31" @@ -2418,6 +2629,9 @@ 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" @@ -2446,6 +2660,21 @@ dependencies = [ "serde", ] +[[package]] +name = "hashlink" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" +dependencies = [ + "hashbrown 0.15.5", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "heck" version = "0.5.0" @@ -2473,6 +2702,15 @@ dependencies = [ "arrayvec", ] +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + [[package]] name = "hmac" version = "0.12.1" @@ -2482,6 +2720,15 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "home" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "http" version = "1.3.1" @@ -2786,6 +3033,26 @@ dependencies = [ "serde_core", ] +[[package]] +name = "indoc" +version = "2.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" +dependencies = [ + "rustversion", +] + +[[package]] +name = "inherent" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c727f80bfa4a6c6e2508d2f05b6f4bfce242030bd88ed15ae5331c5b5d30fba7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.107", +] + [[package]] name = "instant" version = "0.1.13" @@ -2917,6 +3184,9 @@ name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] [[package]] name = "lazycell" @@ -2946,6 +3216,17 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" +[[package]] +name = "libredox" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" +dependencies = [ + "bitflags 2.10.0", + "libc", + "redox_syscall 0.5.18", +] + [[package]] name = "librocksdb-sys" version = "0.17.2+9.10.0" @@ -2960,6 +3241,17 @@ dependencies = [ "zstd-sys", ] +[[package]] +name = "libsqlite3-sys" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + [[package]] name = "libz-sys" version = "1.1.22" @@ -3049,6 +3341,16 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest 0.10.7", +] + [[package]] name = "memchr" version = "2.7.6" @@ -3073,7 +3375,7 @@ version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25dea7ac8057892855ec285c440160265225438c3c45072613c25a4b26e98ef5" dependencies = [ - "ahash", + "ahash 0.8.12", "portable-atomic", ] @@ -3143,6 +3445,23 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" +[[package]] +name = "native-tls" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework 2.11.1", + "security-framework-sys", + "tempfile", +] + [[package]] name = "nom" version = "7.1.3" @@ -3181,6 +3500,22 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-bigint-dig" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" +dependencies = [ + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand 0.8.5", + "smallvec", + "zeroize", +] + [[package]] name = "num-conv" version = "0.1.0" @@ -3196,6 +3531,23 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-packer" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dca6ebc301ac35719463119b0bec0eb0e99644e6fc5f554cf1cd2564200cb6c1" + [[package]] name = "num-traits" version = "0.2.19" @@ -3270,10 +3622,48 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" [[package]] -name = "openssl-probe" -version = "0.1.6" +name = "openssl" +version = "0.10.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" +checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" +dependencies = [ + "bitflags 2.10.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.107", +] + +[[package]] +name = "openssl-probe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" + +[[package]] +name = "openssl-sys" +version = "0.9.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] [[package]] name = "opentelemetry" @@ -3351,6 +3741,39 @@ dependencies = [ "tokio-stream", ] +[[package]] +name = "ordered-float" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bb71e1b3fa6ca1c61f383464aaf2bb0e2f8e772a1f01d486832464de363b951" +dependencies = [ + "num-traits", +] + +[[package]] +name = "ouroboros" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0f050db9c44b97a94723127e6be766ac5c340c48f2c4bb3ffa11713744be59" +dependencies = [ + "aliasable", + "ouroboros_macro", + "static_assertions", +] + +[[package]] +name = "ouroboros_macro" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c7028bdd3d43083f6d8d4d5187680d0d3560d54df4cc9d752005268b41e64d0" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "proc-macro2-diagnostics", + "quote", + "syn 2.0.107", +] + [[package]] name = "parity-scale-codec" version = "3.7.5" @@ -3439,6 +3862,15 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + [[package]] name = "percent-encoding" version = "2.3.2" @@ -3465,6 +3897,15 @@ dependencies = [ "indexmap 2.12.0", ] +[[package]] +name = "pgvector" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc58e2d255979a31caa7cabfa7aac654af0354220719ab7a68520ae7a91e8c0b" +dependencies = [ + "serde", +] + [[package]] name = "pharos" version = "0.5.3" @@ -3507,6 +3948,17 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + [[package]] name = "pkcs8" version = "0.10.2" @@ -3551,6 +4003,16 @@ dependencies = [ "plotters-backend", ] +[[package]] +name = "pluralizer" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b3eba432a00a1f6c16f39147847a870e94e2e9b992759b503e330efec778cbe" +dependencies = [ + "once_cell", + "regex", +] + [[package]] name = "portable-atomic" version = "1.11.1" @@ -3642,6 +4104,19 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "proc-macro2-diagnostics" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.107", + "version_check", + "yansi", +] + [[package]] name = "proptest" version = "1.8.0" @@ -3678,7 +4153,7 @@ version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac6c3320f9abac597dcbc668774ef006702672474aad53c6d596b62e487b40b1" dependencies = [ - "heck", + "heck 0.5.0", "itertools 0.14.0", "log", "multimap", @@ -3734,6 +4209,7 @@ dependencies = [ "clap_config", "criterion", "derive_more", + "function_name", "futures", "http", "lazy_static", @@ -3743,10 +4219,15 @@ dependencies = [ "num-bigint", "parking_lot 0.12.5", "prost", + "prover_db_entity", + "prover_db_migration", + "prover_merkle_tree", + "prover_pmtree", "rayon", "rln", "rln_proof", "rocksdb", + "sea-orm", "serde", "serde_json", "smart_contract", @@ -3799,6 +4280,60 @@ dependencies = [ "tonic-prost-build", ] +[[package]] +name = "prover_db_entity" +version = "0.1.0" +dependencies = [ + "sea-orm", + "serde", +] + +[[package]] +name = "prover_db_migration" +version = "0.1.0" +dependencies = [ + "sea-orm-migration", + "tokio", +] + +[[package]] +name = "prover_merkle_tree" +version = "0.1.0" +dependencies = [ + "num-packer", + "prover_db_entity", + "prover_pmtree", + "sea-orm", + "thiserror", +] + +[[package]] +name = "prover_pmtree" +version = "0.1.0" +dependencies = [ + "rayon", +] + +[[package]] +name = "ptr_meta" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" +dependencies = [ + "ptr_meta_derive", +] + +[[package]] +name = "ptr_meta_derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "pulldown-cmark" version = "0.13.0" @@ -4092,6 +4627,15 @@ version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" +[[package]] +name = "rend" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c" +dependencies = [ + "bytecheck", +] + [[package]] name = "reqwest" version = "0.12.24" @@ -4157,6 +4701,35 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rkyv" +version = "0.7.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9008cd6385b9e161d8229e1f6549dd23c3d022f132a2ea37ac3a10ac4935779b" +dependencies = [ + "bitvec", + "bytecheck", + "bytes", + "hashbrown 0.12.3", + "ptr_meta", + "rend", + "rkyv_derive", + "seahash", + "tinyvec", + "uuid", +] + +[[package]] +name = "rkyv_derive" +version = "0.7.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "503d1d27590a2b0a3a4ca4c94755aa2875657196ecbf401a42eff41d7de532c0" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "rln" version = "0.9.0" @@ -4198,8 +4771,9 @@ dependencies = [ "ark-groth16", "ark-relations", "ark-serialize 0.5.0", - "criterion", + "prover_pmtree", "rln", + "serde", "zerokit_utils", ] @@ -4222,6 +4796,26 @@ dependencies = [ "librocksdb-sys", ] +[[package]] +name = "rsa" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40a0376c50d0358279d9d643e4bf7b7be212f1f4ff1da9070a7b54d22ef75c88" +dependencies = [ + "const-oid", + "digest 0.10.7", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core 0.6.4", + "signature", + "spki", + "subtle", + "zeroize", +] + [[package]] name = "ruint" version = "1.17.0" @@ -4256,6 +4850,22 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" +[[package]] +name = "rust_decimal" +version = "1.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35affe401787a9bd846712274d97654355d21b2a2c092a3139aabe31e9022282" +dependencies = [ + "arrayvec", + "borsh", + "bytes", + "num-traits", + "rand 0.8.5", + "rkyv", + "serde", + "serde_json", +] + [[package]] name = "rustc-hash" version = "1.1.0" @@ -4330,7 +4940,7 @@ dependencies = [ "openssl-probe", "rustls-pki-types", "schannel", - "security-framework", + "security-framework 3.5.1", ] [[package]] @@ -4422,10 +5032,184 @@ dependencies = [ ] [[package]] -name = "scopeguard" -version = "1.2.0" +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sea-bae" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f694a6ab48f14bc063cfadff30ab551d3c7e46d8f81836c51989d548f44a2a25" +dependencies = [ + "heck 0.4.1", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.107", +] + +[[package]] +name = "sea-orm" +version = "2.0.0-rc.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6dda57d64724c4c3e2b39ce17ca5f4084561656a3518b65b26edc5b36e4607" +dependencies = [ + "async-stream", + "async-trait", + "bigdecimal", + "chrono", + "derive_more", + "futures-util", + "itertools 0.14.0", + "log", + "ouroboros", + "pgvector", + "rust_decimal", + "sea-orm-macros", + "sea-query", + "sea-query-sqlx", + "sea-schema", + "serde", + "serde_json", + "sqlx", + "strum", + "thiserror", + "time", + "tracing", + "url", + "uuid", +] + +[[package]] +name = "sea-orm-cli" +version = "2.0.0-rc.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d63b7fcf2623bfc47e4fcca48fd35f77fd376611935862a6e316991d035ac85c" +dependencies = [ + "chrono", + "clap", + "dotenvy", + "glob", + "indoc", + "regex", + "sea-schema", + "sqlx", + "tokio", + "tracing", + "tracing-subscriber 0.3.20", + "url", +] + +[[package]] +name = "sea-orm-macros" +version = "2.0.0-rc.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7674a565e093a4bfffbfd6d7fd79a5dc8d75463d442ffb44d0fc3a3dcce5a6" +dependencies = [ + "heck 0.5.0", + "pluralizer", + "proc-macro2", + "quote", + "sea-bae", + "syn 2.0.107", + "unicode-ident", +] + +[[package]] +name = "sea-orm-migration" +version = "2.0.0-rc.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02c77522b82141205bd99137be96b81b4540531f9ff7773b77d70f5749c39dcc" +dependencies = [ + "async-trait", + "clap", + "dotenvy", + "sea-orm", + "sea-orm-cli", + "sea-schema", + "tracing", + "tracing-subscriber 0.3.20", +] + +[[package]] +name = "sea-query" +version = "1.0.0-rc.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c71f6d768c8bb1003bbfce01431374f677abbcf7582d6a0ec4ea4c5ae20adbb" +dependencies = [ + "bigdecimal", + "chrono", + "inherent", + "ordered-float", + "rust_decimal", + "sea-query-derive", + "serde_json", + "time", + "uuid", +] + +[[package]] +name = "sea-query-derive" +version = "1.0.0-rc.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "365d236217f5daa4f40d3c9998ff3921351b53472da50308e384388162353b3a" +dependencies = [ + "darling 0.20.11", + "heck 0.4.1", + "proc-macro2", + "quote", + "syn 2.0.107", + "thiserror", +] + +[[package]] +name = "sea-query-sqlx" +version = "0.8.0-rc.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68873fa1776b4c25a26e7679f8ee22332978c721168ec1b0b32b6583d5a9381d" +dependencies = [ + "bigdecimal", + "chrono", + "rust_decimal", + "sea-query", + "serde_json", + "sqlx", + "time", + "uuid", +] + +[[package]] +name = "sea-schema" +version = "0.17.0-rc.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59f99598cda516443eb35c06fe5b4496d60c8f7afca708bd998087b63ac56775" +dependencies = [ + "async-trait", + "sea-query", + "sea-query-sqlx", + "sea-schema-derive", + "sqlx", +] + +[[package]] +name = "sea-schema-derive" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "debdc8729c37fdbf88472f97fd470393089f997a909e535ff67c544d18cfccf0" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "syn 2.0.107", +] + +[[package]] +name = "seahash" +version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" [[package]] name = "sec1" @@ -4463,6 +5247,19 @@ dependencies = [ "cc", ] +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.10.0", + "core-foundation 0.9.4", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + [[package]] name = "security-framework" version = "3.5.1" @@ -4470,7 +5267,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" dependencies = [ "bitflags 2.10.0", - "core-foundation", + "core-foundation 0.10.1", "core-foundation-sys", "libc", "security-framework-sys", @@ -4605,7 +5402,7 @@ version = "3.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7e6c180db0816026a61afa1cff5344fb7ebded7e4d3062772179f2501481c27" dependencies = [ - "darling", + "darling 0.21.3", "proc-macro2", "quote", "syn 2.0.107", @@ -4688,6 +5485,12 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + [[package]] name = "sketches-ddsketch" version = "0.3.0" @@ -4735,6 +5538,7 @@ dependencies = [ "clap", "log", "rustls", + "serde", "thiserror", "tokio", "url", @@ -4750,6 +5554,15 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + [[package]] name = "spki" version = "0.7.3" @@ -4760,6 +5573,214 @@ dependencies = [ "der", ] +[[package]] +name = "sqlx" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fefb893899429669dcdd979aff487bd78f4064e5e7907e4269081e0ef7d97dc" +dependencies = [ + "sqlx-core", + "sqlx-macros", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", +] + +[[package]] +name = "sqlx-core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" +dependencies = [ + "base64", + "bigdecimal", + "bytes", + "chrono", + "crc", + "crossbeam-queue", + "either", + "event-listener", + "futures-core", + "futures-intrusive", + "futures-io", + "futures-util", + "hashbrown 0.15.5", + "hashlink", + "indexmap 2.12.0", + "log", + "memchr", + "native-tls", + "once_cell", + "percent-encoding", + "rust_decimal", + "serde", + "serde_json", + "sha2", + "smallvec", + "thiserror", + "time", + "tokio", + "tokio-stream", + "tracing", + "url", + "uuid", +] + +[[package]] +name = "sqlx-macros" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2d452988ccaacfbf5e0bdbc348fb91d7c8af5bee192173ac3636b5fb6e6715d" +dependencies = [ + "proc-macro2", + "quote", + "sqlx-core", + "sqlx-macros-core", + "syn 2.0.107", +] + +[[package]] +name = "sqlx-macros-core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b" +dependencies = [ + "dotenvy", + "either", + "heck 0.5.0", + "hex", + "once_cell", + "proc-macro2", + "quote", + "serde", + "serde_json", + "sha2", + "sqlx-core", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", + "syn 2.0.107", + "tokio", + "url", +] + +[[package]] +name = "sqlx-mysql" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" +dependencies = [ + "atoi", + "base64", + "bigdecimal", + "bitflags 2.10.0", + "byteorder", + "bytes", + "chrono", + "crc", + "digest 0.10.7", + "dotenvy", + "either", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "generic-array", + "hex", + "hkdf", + "hmac", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "percent-encoding", + "rand 0.8.5", + "rsa", + "rust_decimal", + "serde", + "sha1", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror", + "time", + "tracing", + "uuid", + "whoami", +] + +[[package]] +name = "sqlx-postgres" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" +dependencies = [ + "atoi", + "base64", + "bigdecimal", + "bitflags 2.10.0", + "byteorder", + "chrono", + "crc", + "dotenvy", + "etcetera", + "futures-channel", + "futures-core", + "futures-util", + "hex", + "hkdf", + "hmac", + "home", + "itoa", + "log", + "md-5", + "memchr", + "num-bigint", + "once_cell", + "rand 0.8.5", + "rust_decimal", + "serde", + "serde_json", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror", + "time", + "tracing", + "uuid", + "whoami", +] + +[[package]] +name = "sqlx-sqlite" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea" +dependencies = [ + "atoi", + "chrono", + "flume", + "futures-channel", + "futures-core", + "futures-executor", + "futures-intrusive", + "futures-util", + "libsqlite3-sys", + "log", + "percent-encoding", + "serde", + "serde_urlencoded", + "sqlx-core", + "thiserror", + "time", + "tracing", + "url", + "uuid", +] + [[package]] name = "stable_deref_trait" version = "1.2.1" @@ -4772,6 +5793,17 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "stringprep" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" +dependencies = [ + "unicode-bidi", + "unicode-normalization", + "unicode-properties", +] + [[package]] name = "strsim" version = "0.11.1" @@ -4793,7 +5825,7 @@ version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro2", "quote", "syn 2.0.107", @@ -5286,6 +6318,7 @@ version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ + "log", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -5451,12 +6484,33 @@ version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" +[[package]] +name = "unicode-bidi" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" + [[package]] name = "unicode-ident" version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "462eeb75aeb73aea900253ce739c8e18a67423fadf006037cd3ff27e82748a06" +[[package]] +name = "unicode-normalization" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-properties" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" + [[package]] name = "unicode-xid" version = "0.2.6" @@ -5499,6 +6553,17 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "uuid" +version = "1.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" +dependencies = [ + "js-sys", + "serde", + "wasm-bindgen", +] + [[package]] name = "vacp2p_pmtree" version = "2.0.3" @@ -5566,6 +6631,12 @@ dependencies = [ "wit-bindgen", ] +[[package]] +name = "wasite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" + [[package]] name = "wasm-bindgen" version = "0.2.104" @@ -5690,6 +6761,16 @@ dependencies = [ "rustls-pki-types", ] +[[package]] +name = "whoami" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d" +dependencies = [ + "libredox", + "wasite", +] + [[package]] name = "winapi" version = "0.3.9" @@ -5780,6 +6861,15 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + [[package]] name = "windows-sys" version = "0.52.0" @@ -5807,6 +6897,21 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + [[package]] name = "windows-targets" version = "0.52.6" @@ -5840,6 +6945,12 @@ dependencies = [ "windows_x86_64_msvc 0.53.1", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -5852,6 +6963,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" @@ -5864,6 +6981,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -5888,6 +7011,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + [[package]] name = "windows_i686_msvc" version = "0.52.6" @@ -5900,6 +7029,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -5912,6 +7047,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" @@ -5924,6 +7065,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -5985,6 +7132,12 @@ dependencies = [ "tap", ] +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + [[package]] name = "yoke" version = "0.8.0" diff --git a/rln-prover/Cargo.toml b/rln-prover/Cargo.toml index 8dec72d3a9..377f1f025b 100644 --- a/rln-prover/Cargo.toml +++ b/rln-prover/Cargo.toml @@ -5,6 +5,10 @@ members = [ "prover", "prover_cli", "prover_client", + "prover_db_migration", + "prover_db_entity", + "prover_pmtree", + "prover_pmtree_db_impl", ] resolver = "2" @@ -41,11 +45,21 @@ prost = "0.14.1" tonic-prost = "0.14.2" tracing-subscriber = { version = "0.3.20", features = ["env-filter"] } tracing = "0.1.41" +serde = { version = "1.0.228", features = ["derive"] } +sea-orm = { version = "2.0.0-rc.19", default-features = false, features = [ + "runtime-tokio-native-tls", + "sqlx-postgres", + # "sqlx-sqlite", +]} +sea-orm-migration = { version = "2.0.0-rc.19", features = [ + "runtime-tokio-native-tls", + "sqlx-postgres", + # "sqlx-sqlite" +]} -#[build-dependencies] +# for build tonic-prost-build = "0.14.2" - -#[dev.dependencies] +# for becnhmark criterion = { version = "0.7.0", features = ["async_tokio"] } [profile.release] diff --git a/rln-prover/Readme.md b/rln-prover/Readme.md index 820b108a1f..5f737f0127 100644 --- a/rln-prover/Readme.md +++ b/rln-prover/Readme.md @@ -44,4 +44,5 @@ RUST_LOG=debug cargo run -p prover_cli -- --ip 127.0.0.1 --metrics-ip 127.0.0.1 ### Unit tests * cargo test -* cargo test --features anvil \ No newline at end of file +* cargo test --features anvil + diff --git a/rln-prover/prover/Cargo.toml b/rln-prover/prover/Cargo.toml index 16607cd2af..ead43711d4 100644 --- a/rln-prover/prover/Cargo.toml +++ b/rln-prover/prover/Cargo.toml @@ -26,6 +26,7 @@ prost.workspace = true tonic-prost.workspace = true tracing-subscriber.workspace = true tracing.workspace = true +serde.workspace = true tower-http = { version = "0.6.6", features = ["cors"] } futures = "0.3.31" bytesize = "2.1.0" @@ -35,7 +36,6 @@ http = "1.3.1" async-channel = "2.3.1" # rand = "0.9.2" num-bigint = "0.4.6" -serde = { version = "1.0.228", features = ["derive"] } serde_json = "1.0.145" rocksdb = { git = "https://github.com/tillrohrmann/rust-rocksdb", branch = "issues/836" } nom = "8.0.0" @@ -45,6 +45,16 @@ metrics = "0.24.2" metrics-exporter-prometheus = "0.17.2" rayon = "1.11" +# user db 2 +prover_db_entity = { path = "../prover_db_entity" } +prover_merkle_tree = { path = "../prover_pmtree_db_impl" } +prover_pmtree = { path = "../prover_pmtree" } +sea-orm = { version = "2.0.0-rc.18", features = [ + "runtime-tokio-native-tls", + "sqlx-postgres", + "debug-print" +]} + [build-dependencies] tonic-prost-build.workspace = true @@ -54,6 +64,17 @@ ark-groth16.workspace = true tempfile = "3.21" tracing-test = "0.2.5" lazy_static = "1.5.0" +prover_db_migration = { path = "../prover_db_migration" } +function_name = "0.3.0" + +[dev-dependencies.sea-orm] +workspace = true +features = [ + "runtime-tokio-native-tls", + "sqlx-postgres", + "sqlx-sqlite", + "debug-print" +] [[bench]] name = "prover_bench" @@ -62,3 +83,10 @@ harness = false [[bench]] name = "prover_many_subscribers" harness = false + +[features] +postgres = [] + +[lints.rust] +dead_code = "allow" +unused = "allow" diff --git a/rln-prover/prover/benches/prover_bench.rs b/rln-prover/prover/benches/prover_bench.rs index 0f05ddc949..770a09b530 100644 --- a/rln-prover/prover/benches/prover_bench.rs +++ b/rln-prover/prover/benches/prover_bench.rs @@ -17,6 +17,7 @@ use tokio::task::JoinSet; use tonic::Response; // internal use prover::{AppArgs, MockUser, run_prover}; +use prover_db_migration::{Migrator as MigratorCreate, MigratorTrait}; // grpc pub mod prover_proto { @@ -29,6 +30,7 @@ use prover_proto::{ }; use lazy_static::lazy_static; +use sea_orm::{ConnectionTrait, Database, DatabaseConnection, DbErr, Statement}; use std::sync::Once; lazy_static! { @@ -51,6 +53,52 @@ pub fn setup_tracing() { }); } +async fn create_database_connection( + f_name: &str, + test_name: &str, +) -> Result<(String, DatabaseConnection), DbErr> { + // Drop / Create db_name then return a connection to it + + let db_name = format!( + "{}_{}", + std::path::Path::new(f_name) + .file_stem() + .unwrap() + .to_str() + .unwrap(), + test_name + ); + + println!("db_name: {}", db_name); + + let db_url_base = "postgres://myuser:mysecretpassword@localhost"; + let db_url = format!("{}/{}", db_url_base, "mydatabase"); + let db = Database::connect(db_url) + .await + .expect("Database connection 0 failed"); + + db.execute_raw(Statement::from_string( + db.get_database_backend(), + format!("DROP DATABASE IF EXISTS \"{}\";", db_name), + )) + .await?; + db.execute_raw(Statement::from_string( + db.get_database_backend(), + format!("CREATE DATABASE \"{}\";", db_name), + )) + .await?; + + db.close().await?; + + let db_url_final = format!("{}/{}", db_url_base, db_name); + let db = Database::connect(&db_url_final) + .await + .expect("Database connection failed"); + MigratorCreate::up(&db, None).await?; + + Ok((db_url_final, db)) +} + async fn proof_sender(port: u16, addresses: Vec
, proof_count: usize) { let chain_id = GrpcU256 { // FIXME: LE or BE? @@ -165,15 +213,22 @@ fn proof_generation_bench(c: &mut Criterion) { temp_file.flush().unwrap(); let port = 50051; - let temp_folder = tempfile::tempdir().unwrap(); - let temp_folder_tree = tempfile::tempdir().unwrap(); + // let temp_folder = tempfile::tempdir().unwrap(); + // let temp_folder_tree = tempfile::tempdir().unwrap(); + + // create_database_connection("prover_benches", "prover_bench") + // .await + // .unwrap(); + // End Setup db + // let proof_service_count = 4; - let app_args = AppArgs { + let mut app_args = AppArgs { ip: IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), port, ws_rpc_url: None, - db_path: temp_folder.path().to_path_buf(), - merkle_tree_folder: temp_folder_tree.path().to_path_buf(), + db_url: None, + // db_path: temp_folder.path().to_path_buf(), + // merkle_tree_folder: temp_folder_tree.path().to_path_buf(), merkle_tree_count: 1, merkle_tree_max_count: 1, ksc_address: None, @@ -203,6 +258,12 @@ fn proof_generation_bench(c: &mut Criterion) { // Spawn prover let notify_start_1 = notify_start.clone(); rt.spawn(async move { + // Setup db + let (db_url, _db_conn) = create_database_connection("prover_benches", "prover_bench") + .await + .unwrap(); + app_args.db_url = Some(db_url); + tokio::spawn(run_prover(app_args)); tokio::time::sleep(Duration::from_secs(10)).await; println!("Prover is ready, notifying it..."); diff --git a/rln-prover/prover/benches/prover_many_subscribers.rs b/rln-prover/prover/benches/prover_many_subscribers.rs index 00151d316a..01d2e6ccd5 100644 --- a/rln-prover/prover/benches/prover_many_subscribers.rs +++ b/rln-prover/prover/benches/prover_many_subscribers.rs @@ -11,12 +11,14 @@ use std::time::Duration; use alloy::primitives::{Address, U256}; use futures::FutureExt; use parking_lot::RwLock; +use sea_orm::{ConnectionTrait, Database, DatabaseConnection, DbErr, Statement}; use tempfile::NamedTempFile; use tokio::sync::Notify; use tokio::task::JoinSet; use tonic::Response; // internal use prover::{AppArgs, MockUser, run_prover}; +use prover_db_migration::{Migrator as MigratorCreate, MigratorTrait}; // grpc pub mod prover_proto { @@ -28,6 +30,52 @@ use prover_proto::{ SendTransactionRequest, U256 as GrpcU256, Wei as GrpcWei, rln_prover_client::RlnProverClient, }; +async fn create_database_connection( + f_name: &str, + test_name: &str, +) -> Result<(String, DatabaseConnection), DbErr> { + // Drop / Create db_name then return a connection to it + + let db_name = format!( + "{}_{}", + std::path::Path::new(f_name) + .file_stem() + .unwrap() + .to_str() + .unwrap(), + test_name + ); + + println!("db_name: {}", db_name); + + let db_url_base = "postgres://myuser:mysecretpassword@localhost"; + let db_url = format!("{}/{}", db_url_base, "mydatabase"); + let db = Database::connect(db_url) + .await + .expect("Database connection 0 failed"); + + db.execute_raw(Statement::from_string( + db.get_database_backend(), + format!("DROP DATABASE IF EXISTS \"{}\";", db_name), + )) + .await?; + db.execute_raw(Statement::from_string( + db.get_database_backend(), + format!("CREATE DATABASE \"{}\";", db_name), + )) + .await?; + + db.close().await?; + + let db_url_final = format!("{}/{}", db_url_base, db_name); + let db = Database::connect(&db_url_final) + .await + .expect("Database connection failed"); + MigratorCreate::up(&db, None).await?; + + Ok((db_url_final, db)) +} + async fn proof_sender(ip: IpAddr, port: u16, addresses: Vec
, proof_count: usize) { let chain_id = GrpcU256 { // FIXME: LE or BE? @@ -132,12 +180,13 @@ fn proof_generation_bench(c: &mut Criterion) { let temp_folder = tempfile::tempdir().unwrap(); let temp_folder_tree = tempfile::tempdir().unwrap(); // let proof_service_count = 4; - let app_args = AppArgs { + let mut app_args = AppArgs { ip: IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), port, ws_rpc_url: None, - db_path: temp_folder.path().to_path_buf(), - merkle_tree_folder: temp_folder_tree.path().to_path_buf(), + db_url: None, + // db_path: temp_folder.path().to_path_buf(), + // merkle_tree_folder: temp_folder_tree.path().to_path_buf(), merkle_tree_count: 1, merkle_tree_max_count: 1, ksc_address: None, @@ -167,6 +216,12 @@ fn proof_generation_bench(c: &mut Criterion) { // Spawn prover let notify_start_1 = notify_start.clone(); rt.spawn(async move { + // Setup db + let (db_url, _db_conn) = create_database_connection("prover_benches", "prover_bench") + .await + .unwrap(); + app_args.db_url = Some(db_url); + tokio::spawn(run_prover(app_args)); tokio::time::sleep(Duration::from_secs(10)).await; println!("Prover is ready, notifying it..."); diff --git a/rln-prover/prover/src/args.rs b/rln-prover/prover/src/args.rs index 9ec6be9229..47ddff02e0 100644 --- a/rln-prover/prover/src/args.rs +++ b/rln-prover/prover/src/args.rs @@ -70,14 +70,16 @@ pub struct AppArgs { help = "Websocket rpc url (e.g. wss://eth-mainnet.g.alchemy.com/v2/your-api-key)" )] pub ws_rpc_url: Option, - #[arg(long = "db", help = "Db path", default_value = "./storage/db")] - pub db_path: PathBuf, - #[arg( - long = "tree", - help = "Merkle tree folder", - default_value = "./storage/trees" - )] - pub merkle_tree_folder: PathBuf, + // #[arg(long = "db", help = "Db path", default_value = "./storage/db")] + // pub db_path: PathBuf, + // #[arg( + // long = "tree", + // help = "Merkle tree folder", + // default_value = "./storage/trees" + // )] + // pub merkle_tree_folder: PathBuf, + #[arg(long = "db", help = "Db url")] + pub db_url: Option, #[arg(long = "tree-count", help = "Merkle tree count", default_value = "1")] pub merkle_tree_count: u64, #[arg( @@ -274,6 +276,7 @@ mod tests { let config = AppArgsConfig { ip: None, port: Some(config_port), + db_url: None, mock_sc: Some(true), ..Default::default() }; diff --git a/rln-prover/prover/src/epoch_service.rs b/rln-prover/prover/src/epoch_service.rs index d34e36b28a..70ad85d287 100644 --- a/rln-prover/prover/src/epoch_service.rs +++ b/rln-prover/prover/src/epoch_service.rs @@ -9,7 +9,7 @@ use parking_lot::RwLock; use tokio::sync::Notify; use tracing::{debug, error}; // internal -use crate::error::AppError; +use crate::error::AppError2; use crate::metrics::{ EPOCH_SERVICE_CURRENT_EPOCH, EPOCH_SERVICE_CURRENT_EPOCH_SLICE, EPOCH_SERVICE_DRIFT_MILLIS, }; @@ -44,7 +44,7 @@ impl EpochService { // Note: listen_for_new_epoch never ends so no log will happen with #[instrument] // + metrics already tracks the current epoch / epoch_slice // #[instrument(skip(self), fields(self.epoch_slice_duration, self.genesis, self.current_epoch))] - pub(crate) async fn listen_for_new_epoch(&self) -> Result<(), AppError> { + pub(crate) async fn listen_for_new_epoch(&self) -> Result<(), AppError2> { let epoch_slice_count = Self::compute_epoch_slice_count(EPOCH_DURATION, self.epoch_slice_duration); debug!("epoch slice in an epoch: {}", epoch_slice_count); @@ -70,14 +70,14 @@ impl EpochService { error!( "Too many errors while computing the initial wait until, aborting..." ); - return Err(AppError::EpochError(WaitUntilError::TooLow(d1, d2))); + return Err(AppError2::EpochError(WaitUntilError::TooLow(d1, d2))); } } Err(e) => { // Another error (like OutOfRange) - exiting... error!("Error computing the initial wait until: {}", e); - return Err(AppError::EpochError(e)); + return Err(AppError2::EpochError(e)); } }; }; diff --git a/rln-prover/prover/src/epoch_service_tests.rs b/rln-prover/prover/src/epoch_service_tests.rs index 0ef814ba8b..100ae1af6e 100644 --- a/rln-prover/prover/src/epoch_service_tests.rs +++ b/rln-prover/prover/src/epoch_service_tests.rs @@ -13,12 +13,12 @@ mod tests { use tracing_test::traced_test; // internal use crate::epoch_service::{EpochService, WAIT_UNTIL_MIN_DURATION}; - use crate::error::AppError; + use crate::error::AppError2; #[derive(thiserror::Error, Debug)] enum AppErrorExt { #[error("AppError: {0}")] - AppError(#[from] AppError), + AppError(#[from] AppError2), #[error("Future timeout")] Elapsed, } diff --git a/rln-prover/prover/src/error.rs b/rln-prover/prover/src/error.rs index c20ee193d2..a831baaccb 100644 --- a/rln-prover/prover/src/error.rs +++ b/rln-prover/prover/src/error.rs @@ -7,7 +7,8 @@ use smart_contract::{KarmaScError, KarmaTiersError, RlnScError}; use crate::epoch_service::WaitUntilError; use crate::tier::ValidateTierLimitsError; use crate::user_db_error::{ - RegisterError, TxCounterError, UserDbOpenError, UserMerkleTreeIndexError, + GetMerkleTreeProofError2, RegisterError, RegisterError2, TxCounterError, TxCounterError2, + UserDb2OpenError, UserDbOpenError, UserMerkleTreeIndexError, }; #[derive(thiserror::Error, Debug)] @@ -42,6 +43,38 @@ pub enum AppError { MockUserTxCounterError(#[from] TxCounterError), } +#[derive(thiserror::Error, Debug)] +pub enum AppError2 { + #[error("Tonic (grpc) error: {0}")] + Tonic(#[from] tonic::transport::Error), + #[error("Tonic reflection (grpc) error: {0}")] + TonicReflection(#[from] tonic_reflection::server::Error), + #[error("Rpc error 1: {0}")] + RpcError(#[from] RpcError>), + #[error("Rpc transport error 2: {0}")] + RpcTransportError(#[from] RpcError), + #[error("Epoch service error: {0}")] + EpochError(#[from] WaitUntilError), + #[error(transparent)] + RegistryError(#[from] HandleTransferError2), + #[error(transparent)] + KarmaScError(#[from] KarmaScError), + #[error(transparent)] + KarmaTiersError(#[from] KarmaTiersError), + #[error(transparent)] + RlnScError(#[from] RlnScError), + #[error(transparent)] + SignerInitError(#[from] LocalSignerError), + #[error(transparent)] + ValidateTierError(#[from] ValidateTierLimitsError), + #[error(transparent)] + UserDbOpenError(#[from] UserDb2OpenError), + #[error(transparent)] + MockUserRegisterError(#[from] RegisterError2), + #[error(transparent)] + MockUserTxCounterError(#[from] TxCounterError2), +} + #[derive(thiserror::Error, Debug)] pub enum ProofGenerationError { #[error("Proof generation failed: {0}")] @@ -51,7 +84,7 @@ pub enum ProofGenerationError { #[error("Proof serialization failed: {0}")] SerializationWrite(#[from] std::io::Error), #[error(transparent)] - MerkleProofError(#[from] GetMerkleTreeProofError), + MerkleProofError(#[from] GetMerkleTreeProofError2), } /// Same as ProofGenerationError but can be Cloned (can be used in Tokio broadcast channels) @@ -64,7 +97,7 @@ pub enum ProofGenerationStringError { #[error("Proof serialization failed: {0}")] SerializationWrite(String), #[error(transparent)] - MerkleProofError(#[from] GetMerkleTreeProofError), + MerkleProofError(#[from] GetMerkleTreeProofError2), } impl From for ProofGenerationStringError { @@ -100,3 +133,13 @@ pub enum HandleTransferError { #[error("Unable to query balance: {0}")] FetchBalanceOf(#[from] alloy::contract::Error), } + +#[derive(thiserror::Error, Debug)] +pub enum HandleTransferError2 { + #[error(transparent)] + Register(#[from] RegisterError2), + #[error("Fail to register user in RLN SC: {0}")] + ScRegister(#[from] RegisterSCError), + #[error("Unable to query balance: {0}")] + FetchBalanceOf(#[from] alloy::contract::Error), +} diff --git a/rln-prover/prover/src/grpc_e2e.rs b/rln-prover/prover/src/grpc_e2e.rs new file mode 100644 index 0000000000..575ff3148a --- /dev/null +++ b/rln-prover/prover/src/grpc_e2e.rs @@ -0,0 +1,562 @@ +#[cfg(feature = "postgres")] +#[cfg(test)] +mod tests { + use std::io::Write; + use std::net::{IpAddr, Ipv4Addr}; + use std::num::NonZeroU64; + use std::str::FromStr; + use std::sync::Arc; + use std::time::Duration; + // third-party + use alloy::primitives::{Address, U256}; + use futures::FutureExt; + use parking_lot::RwLock; + use tempfile::NamedTempFile; + use tokio::task; + use tokio::task::JoinSet; + use tonic::Response; + use tracing::{debug, info}; + + // use tracing_test::traced_test; + // internal + use crate::{AppArgs, MockUser, run_prover}; + pub mod prover_proto { + // Include generated code (see build.rs) + tonic::include_proto!("prover"); + } + use crate::tests_common::create_database_connection_1; + use prover_proto::get_user_tier_info_reply::Resp; + use prover_proto::{ + Address as GrpcAddress, GetUserTierInfoReply, GetUserTierInfoRequest, RlnProofFilter, + RlnProofReply, SendTransactionReply, SendTransactionRequest, U256 as GrpcU256, + Wei as GrpcWei, rln_prover_client::RlnProverClient, + }; + /* + async fn register_users(port: u16, addresses: Vec
) { + let url = format!("http://127.0.0.1:{}", port); + let mut client = RlnProverClient::connect(url).await.unwrap(); + + for address in addresses { + let addr = GrpcAddress { + value: address.to_vec(), + }; + + let request_0 = RegisterUserRequest { user: Some(addr) }; + let request = tonic::Request::new(request_0); + let response: Response = client.register_user(request).await.unwrap(); + + assert_eq!( + RegistrationStatus::try_from(response.into_inner().status).unwrap(), + RegistrationStatus::Success + ); + } + } + */ + + async fn query_user_info(port: u16, addresses: Vec
) -> Vec { + let url = format!("http://127.0.0.1:{port}"); + let mut client = RlnProverClient::connect(url).await.unwrap(); + + let mut result = vec![]; + for address in addresses { + let addr = GrpcAddress { + value: address.to_vec(), + }; + let request_0 = GetUserTierInfoRequest { user: Some(addr) }; + let request = tonic::Request::new(request_0); + let resp: Response = + client.get_user_tier_info(request).await.unwrap(); + + result.push(resp.into_inner()); + } + + result + } + + /* + #[tokio::test] + #[traced_test] + async fn test_grpc_register_users() { + let addresses = vec![ + Address::from_str("0xd8da6bf26964af9d7eed9e03e53415d37aa96045").unwrap(), + Address::from_str("0xb20a608c624Ca5003905aA834De7156C68b2E1d0").unwrap(), + ]; + + let temp_folder = tempfile::tempdir().unwrap(); + let temp_folder_tree = tempfile::tempdir().unwrap(); + + let port = 50051; + let app_args = AppArgs { + ip: IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), + port, + ws_rpc_url: None, + db_path: temp_folder.path().to_path_buf(), + merkle_tree_path: temp_folder_tree.path().to_path_buf(), + ksc_address: None, + rlnsc_address: None, + tsc_address: None, + mock_sc: Some(true), + mock_user: None, + config_path: Default::default(), + no_config: Some(true), + metrics_ip: IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), + metrics_port: 30031, + broadcast_channel_size: 100, + proof_service_count: 16, + transaction_channel_size: 100, + proof_sender_channel_size: 100, + }; + + info!("Starting prover..."); + let prover_handle = task::spawn(run_prover(app_args)); + // Wait for the prover to be ready + // Note: if unit test is failing - maybe add an optional notification when service is ready + tokio::time::sleep(Duration::from_secs(5)).await; + info!("Registering some users..."); + register_users(port, addresses.clone()).await; + info!("Query info for these new users..."); + let res = query_user_info(port, addresses.clone()).await; + assert_eq!(res.len(), addresses.len()); + info!("Aborting prover..."); + prover_handle.abort(); + tokio::time::sleep(Duration::from_secs(1)).await; + } + */ + + #[derive(Default)] + struct TxData { + chain_id: Option, + gas_price: Option, + estimated_gas_used: Option, + } + + async fn proof_sender(port: u16, addresses: Vec
, proof_count: usize, tx_data: TxData) { + let start = std::time::Instant::now(); + + let url = format!("http://127.0.0.1:{port}"); + let mut client = RlnProverClient::connect(url).await.unwrap(); + + let addr = GrpcAddress { + value: addresses[0].to_vec(), + }; + let chain_id = GrpcU256 { + value: tx_data + .chain_id + .unwrap_or(U256::from(1)) + .to_le_bytes::<32>() + .to_vec(), + }; + + let wei = GrpcWei { + value: tx_data + .gas_price + .unwrap_or(U256::from(1_000)) + .to_le_bytes::<32>() + .to_vec(), + }; + + let estimated_gas_used = tx_data.estimated_gas_used.unwrap_or(1_000); + + let mut count = 0; + for i in 0..proof_count { + let tx_hash = U256::from(42 + i).to_le_bytes::<32>().to_vec(); + + let request_0 = SendTransactionRequest { + gas_price: Some(wei.clone()), + sender: Some(addr.clone()), + chain_id: Some(chain_id.clone()), + transaction_hash: tx_hash, + estimated_gas_used, + }; + + let request = tonic::Request::new(request_0); + let response: Response = + client.send_transaction(request).await.unwrap(); + assert!(response.into_inner().result); + count += 1; + } + + println!( + "[proof_sender] sent {} tx - elapsed: {} secs", + count, + start.elapsed().as_secs_f64() + ); + } + + async fn proof_collector(port: u16, proof_count: usize) -> Vec { + let start = std::time::Instant::now(); + let result = Arc::new(RwLock::new(vec![])); + + let url = format!("http://127.0.0.1:{port}"); + let mut client = RlnProverClient::connect(url).await.unwrap(); + + let request_0 = RlnProofFilter { address: None }; + + let request = tonic::Request::new(request_0); + let stream_ = client.get_proofs(request).await.unwrap(); + + let mut stream = stream_.into_inner(); + + let result_2 = result.clone(); + let mut count = 0; + let mut start_per_message = std::time::Instant::now(); + let receiver = async move { + while let Some(response) = stream.message().await.unwrap() { + result_2.write().push(response); + count += 1; + if count >= proof_count { + break; + } + println!( + "count {count} - elapsed: {} secs", + start_per_message.elapsed().as_secs_f64() + ); + start_per_message = std::time::Instant::now(); + } + }; + + let _res = tokio::time::timeout(Duration::from_secs(500), receiver).await; + println!("_res: {_res:?}"); + let res = std::mem::take(&mut *result.write()); + println!( + "[proof_collector] elapsed: {} secs", + start.elapsed().as_secs_f64() + ); + res + } + + #[tokio::test] + // #[traced_test] + async fn test_grpc_gen_proof() { + let mock_users = vec![ + MockUser { + address: Address::from_str("0xd8da6bf26964af9d7eed9e03e53415d37aa96045").unwrap(), + tx_count: 0, + }, + MockUser { + address: Address::from_str("0xb20a608c624Ca5003905aA834De7156C68b2E1d0").unwrap(), + tx_count: 0, + }, + ]; + let addresses: Vec
= mock_users.iter().map(|u| u.address).collect(); + + // Write mock users to tempfile + let mock_users_as_str = serde_json::to_string(&mock_users).unwrap(); + let mut temp_file = NamedTempFile::new().unwrap(); + let temp_file_path = temp_file.path().to_path_buf(); + temp_file.write_all(mock_users_as_str.as_bytes()).unwrap(); + temp_file.flush().unwrap(); + debug!( + "Mock user temp file path: {}", + temp_file_path.to_str().unwrap() + ); + // + + // Setup db + let (db_url, _db_conn) = create_database_connection_1("grpc_e2e", "test_grpc_gen_proof") + .await + .unwrap(); + // End Setup db + + let temp_folder = tempfile::tempdir().unwrap(); + let temp_folder_tree = tempfile::tempdir().unwrap(); + + let port = 50052; + let app_args = AppArgs { + ip: IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), + port, + ws_rpc_url: None, + db_url: Some(db_url), + // db_path: temp_folder.path().to_path_buf(), + // merkle_tree_folder: temp_folder_tree.path().to_path_buf(), + merkle_tree_count: 1, + merkle_tree_max_count: 1, + ksc_address: None, + rlnsc_address: None, + tsc_address: None, + mock_sc: Some(true), + mock_user: Some(temp_file_path), + config_path: Default::default(), + no_config: true, + metrics_ip: IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), + metrics_port: 30031, + broadcast_channel_size: 500, + proof_service_count: 8, + transaction_channel_size: 500, + proof_sender_channel_size: 500, + registration_min_amount: AppArgs::default_minimal_amount_for_registration(), + rln_identifier: AppArgs::default_rln_identifier_name(), + spam_limit: AppArgs::default_spam_limit(), + no_grpc_reflection: true, + tx_gas_quota: AppArgs::default_tx_gas_quota(), + }; + + info!("Starting prover with args: {:?}", app_args); + let prover_handle = task::spawn(run_prover(app_args)); + // Wait for the prover to be ready + // Note: if unit test is failing - maybe add an optional notification when service is ready + tokio::time::sleep(Duration::from_secs(5)).await; + // info!("Registering some users..."); + // register_users(port, addresses.clone()).await; + info!("Query info for these new users..."); + let res = query_user_info(port, addresses.clone()).await; + assert_eq!(res.len(), addresses.len()); + + info!("Sending tx and collecting proofs..."); + let proof_count = 10; + let mut set = JoinSet::new(); + set.spawn( + proof_sender(port, addresses.clone(), proof_count, Default::default()).map(|_| vec![]), // JoinSet require having the same return type + ); + set.spawn(proof_collector(port, proof_count)); + let res = set.join_all().await; + + println!("res lengths: {} {}", res[0].len(), res[1].len()); + assert_eq!(res[0].len() + res[1].len(), proof_count); + + info!("Aborting prover..."); + prover_handle.abort(); + tokio::time::sleep(Duration::from_secs(1)).await; + } + + async fn proof_sender_2(port: u16, addresses: Vec
, proof_count: usize) { + let start = std::time::Instant::now(); + + let chain_id = GrpcU256 { + // FIXME: LE or BE? + value: U256::from(1).to_le_bytes::<32>().to_vec(), + }; + + let url = format!("http://127.0.0.1:{port}"); + let mut client = RlnProverClient::connect(url).await.unwrap(); + + let addr = GrpcAddress { + value: addresses[0].to_vec(), + }; + let wei = GrpcWei { + // FIXME: LE or BE? + value: U256::from(1000).to_le_bytes::<32>().to_vec(), + }; + + let mut count = 0; + for i in 0..proof_count { + let tx_hash = U256::from(42 + i).to_le_bytes::<32>().to_vec(); + + let request_0 = SendTransactionRequest { + gas_price: Some(wei.clone()), + sender: Some(addr.clone()), + chain_id: Some(chain_id.clone()), + transaction_hash: tx_hash, + estimated_gas_used: 1_000, + }; + + let request = tonic::Request::new(request_0); + let response = client.send_transaction(request).await; + // assert!(response.into_inner().result); + + if response.is_err() { + println!("Error sending tx: {:?}", response.err()); + break; + } + + count += 1; + } + + println!( + "[proof_sender] sent {} tx - elapsed: {} secs", + count, + start.elapsed().as_secs_f64() + ); + } + + #[tokio::test] + // #[traced_test] + async fn test_grpc_user_spamming() { + let mock_users = vec![ + MockUser { + address: Address::from_str("0xd8da6bf26964af9d7eed9e03e53415d37aa96045").unwrap(), + tx_count: 0, + }, + MockUser { + address: Address::from_str("0xb20a608c624Ca5003905aA834De7156C68b2E1d0").unwrap(), + tx_count: 0, + }, + ]; + let addresses: Vec
= mock_users.iter().map(|u| u.address).collect(); + + // Write mock users to tempfile + let mock_users_as_str = serde_json::to_string(&mock_users).unwrap(); + let mut temp_file = NamedTempFile::new().unwrap(); + let temp_file_path = temp_file.path().to_path_buf(); + temp_file.write_all(mock_users_as_str.as_bytes()).unwrap(); + temp_file.flush().unwrap(); + debug!( + "Mock user temp file path: {}", + temp_file_path.to_str().unwrap() + ); + // + // Setup db + let (db_url, _db_conn) = + create_database_connection_1("grpc_e2e", "test_grpc_user_spamming") + .await + .unwrap(); + // End Setup db + + // let temp_folder = tempfile::tempdir().unwrap(); + // let temp_folder_tree = tempfile::tempdir().unwrap(); + + let port = 50053; + let app_args = AppArgs { + ip: IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), + port, + ws_rpc_url: None, + db_url: Some(db_url), + // db_path: temp_folder.path().to_path_buf(), + // merkle_tree_folder: temp_folder_tree.path().to_path_buf(), + merkle_tree_count: 1, + merkle_tree_max_count: 1, + ksc_address: None, + rlnsc_address: None, + tsc_address: None, + mock_sc: Some(true), + mock_user: Some(temp_file_path), + config_path: Default::default(), + no_config: true, + metrics_ip: IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), + metrics_port: 30031, + broadcast_channel_size: 500, + proof_service_count: 8, + transaction_channel_size: 500, + proof_sender_channel_size: 500, + registration_min_amount: AppArgs::default_minimal_amount_for_registration(), + rln_identifier: AppArgs::default_rln_identifier_name(), + spam_limit: 3, + no_grpc_reflection: true, + tx_gas_quota: NonZeroU64::new(1_000).unwrap(), + }; + + info!("Starting prover with args: {:?}", app_args); + let prover_handle = task::spawn(run_prover(app_args)); + // Wait for the prover to be ready + // Note: if unit test is failing - maybe add an optional notification when service is ready + tokio::time::sleep(Duration::from_secs(5)).await; + // info!("Registering some users..."); + // register_users(port, addresses.clone()).await; + info!("Query info for these new users..."); + let res = query_user_info(port, addresses.clone()).await; + assert_eq!(res.len(), addresses.len()); + + info!("Sending tx and collecting proofs..."); + let proof_count = 10; + let mut set = JoinSet::new(); + set.spawn( + proof_sender_2(port, addresses.clone(), proof_count).map(|_| vec![]), // JoinSet require having the same return type + ); + set.spawn(proof_collector(port, 2 + 1)); + let res = set.join_all().await; + + println!("res lengths: {} {}", res[0].len(), res[1].len()); + /* + assert_eq!(res[0].len() + res[1].len(), proof_count); + */ + + info!("Aborting prover..."); + prover_handle.abort(); + tokio::time::sleep(Duration::from_secs(1)).await; + } + + #[tokio::test] + // #[traced_test] + async fn test_grpc_tx_exceed_gas_quota() { + let mock_users = vec![ + MockUser { + address: Address::from_str("0xd8da6bf26964af9d7eed9e03e53415d37aa96045").unwrap(), + tx_count: 0, + }, + MockUser { + address: Address::from_str("0xb20a608c624Ca5003905aA834De7156C68b2E1d0").unwrap(), + tx_count: 0, + }, + ]; + let addresses: Vec
= mock_users.iter().map(|u| u.address).collect(); + + // Write mock users to tempfile + let mock_users_as_str = serde_json::to_string(&mock_users).unwrap(); + let mut temp_file = NamedTempFile::new().unwrap(); + let temp_file_path = temp_file.path().to_path_buf(); + temp_file.write_all(mock_users_as_str.as_bytes()).unwrap(); + temp_file.flush().unwrap(); + debug!( + "Mock user temp file path: {}", + temp_file_path.to_str().unwrap() + ); + // + // Setup db + let (db_url, _db_conn) = + create_database_connection_1("grpc_e2e", "test_grpc_tx_exceed_gas_quota") + .await + .unwrap(); + // End Setup db + + // let temp_folder = tempfile::tempdir().unwrap(); + // let temp_folder_tree = tempfile::tempdir().unwrap(); + + let port = 50054; + let tx_gas_quota = NonZeroU64::new(1_000).unwrap(); + let app_args = AppArgs { + ip: IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), + port, + ws_rpc_url: None, + db_url: Some(db_url), + // db_path: temp_folder.path().to_path_buf(), + // merkle_tree_folder: temp_folder_tree.path().to_path_buf(), + merkle_tree_count: 1, + merkle_tree_max_count: 1, + ksc_address: None, + rlnsc_address: None, + tsc_address: None, + mock_sc: Some(true), + mock_user: Some(temp_file_path), + config_path: Default::default(), + no_config: true, + metrics_ip: IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), + metrics_port: 30031, + broadcast_channel_size: 500, + proof_service_count: 8, + transaction_channel_size: 500, + proof_sender_channel_size: 500, + registration_min_amount: AppArgs::default_minimal_amount_for_registration(), + rln_identifier: AppArgs::default_rln_identifier_name(), + spam_limit: AppArgs::default_spam_limit(), + no_grpc_reflection: true, + tx_gas_quota, + }; + + info!("Starting prover with args: {:?}", app_args); + let _prover_handle = task::spawn(run_prover(app_args)); + // Wait for the prover to be ready + // Note: if unit test is failing - maybe add an optional notification when service is ready + tokio::time::sleep(Duration::from_secs(5)).await; + + let quota_mult = 11; + let tx_data = TxData { + estimated_gas_used: Some(tx_gas_quota.get() * quota_mult), + ..Default::default() + }; + // Send a tx with 11 * the tx_gas_quota + proof_sender(port, addresses.clone(), 1, tx_data).await; + + tokio::time::sleep(Duration::from_secs(5)).await; + let res = query_user_info(port, vec![addresses[0]]).await; + let resp = res[0].resp.as_ref().unwrap(); + match resp { + Resp::Res(r) => { + // Check the tx counter is updated to the right value + assert_eq!(r.tx_count, quota_mult); + } + Resp::Error(e) => { + panic!("Unexpected error {:?}", e); + } + } + } +} diff --git a/rln-prover/prover/src/grpc_service.rs b/rln-prover/prover/src/grpc_service.rs index 51d8c3d630..f4a5ee41b1 100644 --- a/rln-prover/prover/src/grpc_service.rs +++ b/rln-prover/prover/src/grpc_service.rs @@ -21,13 +21,13 @@ use tower_http::cors::{Any, CorsLayer}; use tracing::{debug, error, info, warn}; use url::Url; // internal -use crate::error::{AppError, ProofGenerationStringError}; +use crate::error::{AppError2, ProofGenerationStringError}; use crate::metrics::{ GET_PROOFS_LISTENERS, GET_USER_TIER_INFO_REQUESTS, GaugeWrapper, PROOF_SERVICES_CHANNEL_QUEUE_LEN, SEND_TRANSACTION_REQUESTS, }; use crate::proof_generation::{ProofGenerationData, ProofSendingData}; -use crate::user_db::{UserDb, UserTierInfo}; +use crate::user_db::UserTierInfo; use rln_proof::RlnIdentifier; use smart_contract::{KarmaAmountExt, KarmaSC::KarmaSCInstance, MockKarmaSc}; @@ -39,6 +39,7 @@ pub mod prover_proto { pub(crate) const FILE_DESCRIPTOR_SET: &[u8] = tonic::include_file_descriptor_set!("prover_descriptor"); } +use crate::user_db_2::{UserDb2, UserTierInfo2}; use crate::user_db_types::RateLimit; use prover_proto::{ GetUserTierInfoReply, @@ -77,7 +78,7 @@ const PROVER_TX_HASH_BYTESIZE: usize = 32; #[derive(Debug)] pub struct ProverService { proof_sender: Sender, - user_db: UserDb, + user_db: UserDb2, rln_identifier: Arc, broadcast_channel: ( broadcast::Sender>, @@ -115,8 +116,8 @@ where return Err(Status::invalid_argument("No sender address")); }; - let user_id = if let Some(id) = self.user_db.get_user(&sender) { - id.clone() + let user_id = if let Some(rln_id) = self.user_db.get_user_identity(&sender).await { + rln_id } else { return Err(Status::not_found("Sender not registered")); }; @@ -130,7 +131,8 @@ where // Update the counter as soon as possible (should help to prevent spamming...) let counter = self .user_db - .on_new_tx(&sender, tx_counter_incr) + .on_new_tx(&sender, tx_counter_incr.map(|v| v as i64)) // FIXME: 'as' + .await .unwrap_or_default(); if counter > self.rate_limit { @@ -346,7 +348,7 @@ pub(crate) struct GrpcProverService { ), pub addr: SocketAddr, pub rln_identifier: RlnIdentifier, - pub user_db: UserDb, + pub user_db: UserDb2, pub karma_sc_info: Option<(Url, Address)>, // pub rln_sc_info: Option<(Url, Address)>, pub provider: Option

, @@ -357,7 +359,7 @@ pub(crate) struct GrpcProverService { } impl GrpcProverService

{ - pub(crate) async fn serve(&self) -> Result<(), AppError> { + pub(crate) async fn serve(&self) -> Result<(), AppError2> { let karma_sc = if let Some(karma_sc_info) = self.karma_sc_info.as_ref() && let Some(provider) = self.provider.as_ref() { @@ -430,11 +432,11 @@ impl GrpcProverService

{ .add_optional_service(reflection_service) .add_service(r) .serve(self.addr) - .map_err(AppError::from) + .map_err(AppError2::from) .await } - pub(crate) async fn serve_with_mock(&self) -> Result<(), AppError> { + pub(crate) async fn serve_with_mock(&self) -> Result<(), AppError2> { let prover_service = ProverService { proof_sender: self.proof_sender.clone(), user_db: self.user_db.clone(), @@ -500,7 +502,7 @@ impl GrpcProverService

{ .add_optional_service(reflection_service) .add_service(r) .serve(self.addr) - .map_err(AppError::from) + .map_err(AppError2::from) .await } } @@ -526,6 +528,28 @@ impl From for UserTierInfoResult { } } +/// UserTierInfo2 to UserTierInfoResult (Grpc message) conversion +impl From for UserTierInfoResult { + fn from(tier_info: UserTierInfo2) -> Self { + let mut res = UserTierInfoResult { + current_epoch: tier_info.current_epoch.into(), + // current_epoch_slice: tier_info.current_epoch_slice.into(), + current_epoch_slice: 0, + tx_count: tier_info.epoch_tx_count, + tier: None, + }; + + if tier_info.tier_name.is_some() && tier_info.tier_limit.is_some() { + res.tier = Some(Tier { + name: tier_info.tier_name.unwrap().into(), + quota: tier_info.tier_limit.unwrap().into(), + }) + } + + res + } +} + /// UserTierInfoError to UserTierInfoError (Grpc message) conversion impl From> for UserTierInfoError where @@ -537,3 +561,15 @@ where } } } + +/// UserTierInfoError to UserTierInfoError (Grpc message) conversion +impl From> for UserTierInfoError +where + E: std::error::Error, +{ + fn from(value: crate::user_db_error::UserTierInfoError2) -> Self { + UserTierInfoError { + message: value.to_string(), + } + } +} diff --git a/rln-prover/prover/src/karma_sc_listener.rs b/rln-prover/prover/src/karma_sc_listener.rs index 85e884b326..2855c6fd59 100644 --- a/rln-prover/prover/src/karma_sc_listener.rs +++ b/rln-prover/prover/src/karma_sc_listener.rs @@ -10,15 +10,16 @@ use num_bigint::BigUint; use tonic::codegen::tokio_stream::StreamExt; use tracing::{debug, error, info}; // internal -use crate::error::{AppError, HandleTransferError, RegisterSCError}; -use crate::user_db::UserDb; -use crate::user_db_error::RegisterError; +use crate::error::{AppError2, HandleTransferError2, RegisterSCError}; +// use crate::user_db::UserDb; +use crate::user_db_2::UserDb2; +use crate::user_db_error::RegisterError2; use smart_contract::{KarmaAmountExt, KarmaRLNSC, KarmaSC, RLNRegister}; pub(crate) struct KarmaScEventListener { karma_sc_address: Address, rln_sc_address: Address, - user_db: UserDb, + user_db: UserDb2, minimal_amount: U256, } @@ -26,7 +27,7 @@ impl KarmaScEventListener { pub(crate) fn new( karma_sc_address: Address, rln_sc_address: Address, - user_db: UserDb, + user_db: UserDb2, minimal_amount: U256, ) -> Self { Self { @@ -42,7 +43,7 @@ impl KarmaScEventListener { &self, provider: P, provider_with_signer: PS, - ) -> Result<(), AppError> { + ) -> Result<(), AppError2> { let karma_sc = KarmaSC::new(self.karma_sc_address, provider.clone()); let rln_sc = KarmaRLNSC::new(self.rln_sc_address, provider_with_signer); @@ -65,7 +66,7 @@ impl KarmaScEventListener { Some(&KarmaSC::Transfer::SIGNATURE_HASH) => { self.transfer_event(&log, &karma_sc, &rln_sc) .await - .map_err(AppError::RegistryError)?; + .map_err(AppError2::RegistryError)?; } Some(&KarmaSC::AccountSlashed::SIGNATURE_HASH) => { self.slash_event(&log).await; @@ -131,7 +132,7 @@ impl KarmaScEventListener { log: &Log, karma_sc: &KSC, rln_sc: &RLNSC, - ) -> Result<(), HandleTransferError> { + ) -> Result<(), HandleTransferError2> { match KarmaSC::Transfer::decode_log_data(log.data()) { Ok(transfer_event) => { match self @@ -141,7 +142,7 @@ impl KarmaScEventListener { Ok(addr) => { info!("Registered new user: {}", addr); } - Err(HandleTransferError::Register(RegisterError::AlreadyRegistered( + Err(HandleTransferError2::Register(RegisterError2::AlreadyRegistered( address, ))) => { debug!("Already registered: {}", address); @@ -189,7 +190,7 @@ impl KarmaScEventListener { karma_sc: &KSC, rln_sc: &RLNSC, transfer_event: KarmaSC::Transfer, - ) -> Result { + ) -> Result { let from_address: Address = transfer_event.from; let to_address: Address = transfer_event.to; let amount: U256 = transfer_event.value; @@ -203,7 +204,7 @@ impl KarmaScEventListener { let balance = karma_sc .karma_amount(&to_address) .await - .map_err(|e| HandleTransferError::FetchBalanceOf(e.into()))?; + .map_err(|e| HandleTransferError2::FetchBalanceOf(e.into()))?; // Only register the user if he has a minimal amount of Karma token balance >= self.minimal_amount } @@ -213,11 +214,12 @@ impl KarmaScEventListener { let id_commitment = self .user_db .on_new_user(&to_address) - .map_err(HandleTransferError::Register); + .await + .map_err(HandleTransferError2::Register); // Don't stop the registry_listener if the user_db is full // Prover will still be functional - if let Err(HandleTransferError::Register(RegisterError::TooManyUsers)) = + if let Err(HandleTransferError2::Register(RegisterError2::TooManyUsers)) = id_commitment { error!("Cannot register a new user: {:?}", id_commitment); @@ -231,13 +233,29 @@ impl KarmaScEventListener { if let Err(e) = rln_sc.register_user(&to_address, id_co).await { // Fail to register the user on smart contract // Remove the user in internal Db - if !self.user_db.remove_user(&to_address, false) { - // Fails if DB & SC are inconsistent - panic!("Unable to register user to SC and to remove it from DB..."); + let rem_res = self.user_db.remove_user(&to_address).await; + + match rem_res { + Err(e) => { + // Fails if DB & SC are inconsistent + error!("Fail to remove user ({:?}) from DB: {:?}", to_address, e); + panic!("Fail to register user to SC and to remove it from DB..."); + } + Ok(res) => { + if !res { + error!("Fail to remove user ({:?}) from DB", to_address); + panic!("Fail to register user to SC and to remove it from DB..."); + } else { + debug!( + "Successfully removed user ({:?}), after failing to register him", + to_address + ); + } + } } let e_ = RegisterSCError::from(e.into()); - return Err(HandleTransferError::ScRegister(e_)); + return Err(HandleTransferError2::ScRegister(e_)); } } } @@ -250,8 +268,27 @@ impl KarmaScEventListener { match KarmaSC::AccountSlashed::decode_log_data(log.data()) { Ok(slash_event) => { let address_slashed: Address = slash_event.account; - if !self.user_db.remove_user(&address_slashed, false) { - error!("Cannot remove user ({:?}) from DB", address_slashed); + let rem_res = self.user_db.remove_user(&address_slashed).await; + match rem_res { + Err(e) => { + // Fails if DB & SC are inconsistent + error!( + "Fail to remove slashed user ({:?}) from DB: {:?}", + address_slashed, e + ); + panic!("Fail to register user to SC and to remove it from DB..."); + } + Ok(res) => { + if !res { + error!( + "Fail to remove slashed user ({:?}) from DB", + address_slashed + ); + panic!("Fail to register user to SC and to remove it from DB..."); + } else { + debug!("Removed slashed user ({:?})", address_slashed); + } + } } } Err(e) => { @@ -268,6 +305,7 @@ impl KarmaScEventListener { } } +#[cfg(feature = "postgres")] #[cfg(test)] mod tests { use super::*; @@ -280,8 +318,12 @@ mod tests { use parking_lot::RwLock; // internal use crate::epoch_service::{Epoch, EpochSlice}; - use crate::user_db::{MERKLE_TREE_HEIGHT, UserDbConfig}; + use crate::user_db::MERKLE_TREE_HEIGHT; + // use crate::user_db::{MERKLE_TREE_HEIGHT, UserDbConfig}; + use crate::tests_common::create_database_connection_1; + use crate::user_db_2::UserDb2Config; use crate::user_db_service::UserDbService; + // use function_name::named; // const ADDR_1: Address = address!("0xd8da6bf26964af9d7eed9e03e53415d37aa96045"); const ADDR_2: Address = address!("0xb20a608c624Ca5003905aA834De7156C68b2E1d0"); @@ -316,31 +358,40 @@ mod tests { } #[tokio::test] + #[function_name::named] async fn test_handle_transfer_event() { let epoch = Epoch::from(11); let epoch_slice = EpochSlice::from(42); let epoch_store = Arc::new(RwLock::new((epoch, epoch_slice))); - let temp_folder = tempfile::tempdir().unwrap(); - let temp_folder_tree = tempfile::tempdir().unwrap(); - let config = UserDbConfig { - db_path: PathBuf::from(temp_folder.path()), - merkle_tree_folder: PathBuf::from(temp_folder_tree.path()), + let config = UserDb2Config { tree_count: 1, max_tree_count: 1, tree_depth: MERKLE_TREE_HEIGHT, }; + let (_, db_conn) = create_database_connection_1(file!(), function_name!()) + .await + .unwrap(); let user_db_service = UserDbService::new( + db_conn, config, Default::default(), epoch_store, 10.into(), Default::default(), ) + .await .unwrap(); let user_db = user_db_service.get_user_db(); - assert!(user_db_service.get_user_db().get_user(&ADDR_2).is_none()); + assert!( + user_db_service + .get_user_db() + .get_user(&ADDR_2) + .await + .unwrap() + .is_none() + ); let minimal_amount = U256::from(25); let registry = KarmaScEventListener { @@ -363,6 +414,13 @@ mod tests { .await .unwrap(); - assert!(user_db_service.get_user_db().get_user(&ADDR_2).is_some()); + assert!( + user_db_service + .get_user_db() + .get_user(&ADDR_2) + .await + .unwrap() + .is_some() + ); } } diff --git a/rln-prover/prover/src/lib.rs b/rln-prover/prover/src/lib.rs index b241c4fb50..798d13308f 100644 --- a/rln-prover/prover/src/lib.rs +++ b/rln-prover/prover/src/lib.rs @@ -18,7 +18,12 @@ mod user_db_types; // tests mod epoch_service_tests; +mod grpc_e2e; mod proof_service_tests; +#[cfg(test)] +pub mod tests_common; +mod user_db_2; +mod user_db_2_tests; mod user_db_tests; // std @@ -29,13 +34,14 @@ use std::time::Duration; // third-party use alloy::providers::{ProviderBuilder, WsConnect}; use alloy::signers::local::PrivateKeySigner; +use sea_orm::Database; use tokio::task::JoinSet; use tracing::{debug, info}; use zeroize::Zeroizing; // internal pub use crate::args::{ARGS_DEFAULT_GENESIS, AppArgs, AppArgsConfig}; use crate::epoch_service::EpochService; -use crate::error::AppError; +use crate::error::AppError2; use crate::grpc_service::GrpcProverService; use crate::karma_sc_listener::KarmaScEventListener; pub use crate::mock::MockUser; @@ -43,15 +49,16 @@ use crate::mock::read_mock_user; use crate::proof_service::ProofService; use crate::tier::TierLimits; use crate::tiers_listener::TiersListener; -use crate::user_db::{MERKLE_TREE_HEIGHT, UserDbConfig}; -use crate::user_db_error::RegisterError; +use crate::user_db::MERKLE_TREE_HEIGHT; +use crate::user_db_2::UserDb2Config; +use crate::user_db_error::{RegisterError2, UserDb2OpenError}; use crate::user_db_service::UserDbService; use crate::user_db_types::RateLimit; use rln_proof::RlnIdentifier; use smart_contract::KarmaTiers::KarmaTiersInstance; use smart_contract::{KarmaTiersError, TIER_LIMITS}; -pub async fn run_prover(app_args: AppArgs) -> Result<(), AppError> { +pub async fn run_prover(app_args: AppArgs) -> Result<(), AppError2> { // Epoch let epoch_service = EpochService::try_from((Duration::from_secs(60 * 2), ARGS_DEFAULT_GENESIS)) .expect("Failed to create epoch service"); @@ -103,20 +110,24 @@ pub async fn run_prover(app_args: AppArgs) -> Result<(), AppError> { tier_limits.validate()?; // User db service - let user_db_config = UserDbConfig { - db_path: app_args.db_path.clone(), - merkle_tree_folder: app_args.merkle_tree_folder.clone(), + let user_db_config = UserDb2Config { tree_count: app_args.merkle_tree_count, max_tree_count: app_args.merkle_tree_max_count, tree_depth: MERKLE_TREE_HEIGHT, }; + let db_url = app_args.db_url.unwrap(); + let db_conn = Database::connect(db_url) + .await + .map_err(UserDb2OpenError::from)?; let user_db_service = UserDbService::new( + db_conn, user_db_config, epoch_service.epoch_changes.clone(), epoch_service.current_epoch.clone(), RateLimit::new(app_args.spam_limit), tier_limits, - )?; + ) + .await?; if app_args.mock_sc.is_some() && let Some(user_filepath) = app_args.mock_user.as_ref() @@ -131,17 +142,19 @@ pub async fn run_prover(app_args: AppArgs) -> Result<(), AppError> { ); let user_db = user_db_service.get_user_db(); - if let Err(e) = user_db.on_new_user(&mock_user.address) { + if let Err(e) = user_db.on_new_user(&mock_user.address).await { match e { - RegisterError::AlreadyRegistered(_) => { + RegisterError2::AlreadyRegistered(_) => { debug!("User {} already registered", mock_user.address); } _ => { - return Err(AppError::from(e)); + return Err(AppError2::from(e)); } } } - user_db.on_new_tx(&mock_user.address, Some(mock_user.tx_count))?; + user_db + .on_new_tx(&mock_user.address, Some(mock_user.tx_count)) + .await?; } } diff --git a/rln-prover/prover/src/mock.rs b/rln-prover/prover/src/mock.rs index d8e1c98639..dd878616b3 100644 --- a/rln-prover/prover/src/mock.rs +++ b/rln-prover/prover/src/mock.rs @@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize}; #[derive(Debug, Serialize, Deserialize)] pub struct MockUser { pub address: Address, - pub tx_count: u64, + pub tx_count: i64, } pub fn read_mock_user(path: &PathBuf) -> Result, MockUserError> { diff --git a/rln-prover/prover/src/proof_service.rs b/rln-prover/prover/src/proof_service.rs index 54b8a227cb..acdb34b975 100644 --- a/rln-prover/prover/src/proof_service.rs +++ b/rln-prover/prover/src/proof_service.rs @@ -11,12 +11,13 @@ use rln::protocol::serialize_proof_values; use tracing::{Instrument, debug, debug_span, error, info, warn}; // internal use crate::epoch_service::{Epoch, EpochSlice}; -use crate::error::{AppError, ProofGenerationError, ProofGenerationStringError}; +use crate::error::{AppError2, ProofGenerationError, ProofGenerationStringError}; use crate::metrics::{ BROADCAST_CHANNEL_QUEUE_LEN, PROOF_SERVICE_GEN_PROOF_TIME, PROOF_SERVICE_PROOF_COMPUTED, }; use crate::proof_generation::{ProofGenerationData, ProofSendingData}; -use crate::user_db::UserDb; +// use crate::user_db::UserDb; +use crate::user_db_2::UserDb2; use crate::user_db_types::RateLimit; use rln_proof::{RlnData, compute_rln_proof_and_values}; @@ -28,7 +29,7 @@ pub struct ProofService { broadcast_sender: tokio::sync::broadcast::Sender>, current_epoch: Arc>, - user_db: UserDb, + user_db: UserDb2, rate_limit: RateLimit, id: u64, } @@ -40,7 +41,7 @@ impl ProofService { Result, >, current_epoch: Arc>, - user_db: UserDb, + user_db: UserDb2, rate_limit: RateLimit, id: u64, ) -> Self { @@ -56,7 +57,7 @@ impl ProofService { } } - pub(crate) async fn serve(&self) -> Result<(), AppError> { + pub(crate) async fn serve(&self) -> Result<(), AppError2> { info!( "[ProofService {}] Starting serve() - waiting for messages on channel", self.id @@ -102,6 +103,10 @@ impl ProofService { // Communicate between rayon & current task let (send, recv) = tokio::sync::oneshot::channel(); + let merkle_proof_ = user_db + .get_merkle_proof(&proof_generation_data.tx_sender) + .await; + // Move to a task (as generating the proof can take quite some time) - avoid blocking the tokio runtime // Note: avoid tokio spawn_blocking as it does not perform great for CPU bounds tasks // see https://ryhl.io/blog/async-what-is-blocking/ @@ -114,6 +119,14 @@ impl ProofService { debug!("[ProofService {}] Rayon task started", counter_id); let proof_generation_start = std::time::Instant::now(); + let merkle_proof = match merkle_proof_ { + Ok(proof) => proof, + Err(e) => { + let _ = send.send(Err(ProofGenerationError::MerkleProofError(e))); + return; + } + }; + let message_id = { let mut m_id = proof_generation_data.tx_counter; // Note: Zerokit can only recover user secret hash with 2 messages with the @@ -137,15 +150,6 @@ impl ProofService { }; let epoch = hash_to_field_le(epoch_bytes.as_slice()); - let merkle_proof = match user_db.get_merkle_proof(&proof_generation_data.tx_sender) - { - Ok(merkle_proof) => merkle_proof, - Err(e) => { - let _ = send.send(Err(ProofGenerationError::MerkleProofError(e))); - return; - } - }; - // let compute_proof_start = std::time::Instant::now(); let (proof, proof_values) = match compute_rln_proof_and_values( &proof_generation_data.user_identity, @@ -256,6 +260,7 @@ impl ProofService { } } +#[cfg(feature = "postgres")] #[cfg(test)] mod tests { use super::*; @@ -275,7 +280,9 @@ mod tests { protocol::{deserialize_proof_values, verify_proof}, }; // internal - use crate::user_db::{MERKLE_TREE_HEIGHT, UserDbConfig}; + use crate::tests_common::create_database_connection_1; + use crate::user_db::MERKLE_TREE_HEIGHT; + use crate::user_db_2::UserDb2Config; use crate::user_db_service::UserDbService; use rln_proof::RlnIdentifier; @@ -287,7 +294,7 @@ mod tests { #[derive(thiserror::Error, Debug)] enum AppErrorExt { #[error("AppError: {0}")] - AppError(#[from] AppError), + AppError(#[from] AppError2), #[error("Future timeout")] Elapsed, #[error("Proof generation failed: {0}")] @@ -302,7 +309,7 @@ mod tests { sender: Address, proof_tx: &mut async_channel::Sender, rln_identifier: Arc, - user_db: &UserDb, + user_db: &UserDb2, ) -> Result<(), AppErrorExt> { // used by test_proof_generation unit test @@ -310,9 +317,10 @@ mod tests { debug!("Waiting a bit before sending proof..."); tokio::time::sleep(std::time::Duration::from_secs(1)).await; debug!("Sending proof..."); + let user_identity = user_db.get_user_identity(&ADDR_1).await.unwrap(); proof_tx .send(ProofGenerationData { - user_identity: user_db.get_user(&ADDR_1).unwrap(), + user_identity, rln_identifier, tx_counter: 0, tx_sender: sender, @@ -364,6 +372,7 @@ mod tests { } #[tokio::test] + #[function_name::named] // #[tracing_test::traced_test] async fn test_proof_generation() { // Queues @@ -377,27 +386,28 @@ mod tests { let epoch_store = Arc::new(RwLock::new((epoch, epoch_slice))); // User db - let temp_folder = tempfile::tempdir().unwrap(); - let temp_folder_tree = tempfile::tempdir().unwrap(); - let config = UserDbConfig { - db_path: PathBuf::from(temp_folder.path()), - merkle_tree_folder: PathBuf::from(temp_folder_tree.path()), + let config = UserDb2Config { tree_count: 1, max_tree_count: 1, tree_depth: MERKLE_TREE_HEIGHT, }; + let (_, db_conn) = create_database_connection_1(file!(), function_name!()) + .await + .unwrap(); let user_db_service = UserDbService::new( + db_conn, config, Default::default(), epoch_store.clone(), 10.into(), Default::default(), ) + .await .unwrap(); let user_db = user_db_service.get_user_db(); - user_db.on_new_user(&ADDR_1).unwrap(); - user_db.on_new_user(&ADDR_2).unwrap(); + user_db.on_new_user(&ADDR_1).await.unwrap(); + user_db.on_new_user(&ADDR_2).await.unwrap(); let rln_identifier = Arc::new(RlnIdentifier::new(b"foo bar baz")); diff --git a/rln-prover/prover/src/proof_service_tests.rs b/rln-prover/prover/src/proof_service_tests.rs index 859b1859b0..bfd80f525b 100644 --- a/rln-prover/prover/src/proof_service_tests.rs +++ b/rln-prover/prover/src/proof_service_tests.rs @@ -1,3 +1,4 @@ +#[cfg(feature = "postgres")] #[cfg(test)] mod tests { use std::io::Cursor; @@ -14,16 +15,20 @@ mod tests { use rln::error::ComputeIdSecretError; use rln::protocol::{compute_id_secret, deserialize_proof_values, verify_proof}; use rln::utils::IdSecret; + use sea_orm::{ConnectionTrait, Database, DatabaseConnection, DbErr, Statement}; use tokio::sync::broadcast; use tracing::{debug, info}; // internal use crate::epoch_service::{Epoch, EpochSlice}; - use crate::error::{AppError, ProofGenerationStringError}; + use crate::error::{AppError, AppError2, ProofGenerationStringError}; use crate::proof_generation::{ProofGenerationData, ProofSendingData}; use crate::proof_service::ProofService; - use crate::user_db::{MERKLE_TREE_HEIGHT, UserDb, UserDbConfig}; + // use crate::user_db::{MERKLE_TREE_HEIGHT, UserDb, UserDbConfig}; + use crate::user_db::MERKLE_TREE_HEIGHT; + use crate::user_db_2::{UserDb2, UserDb2Config}; use crate::user_db_service::UserDbService; use crate::user_db_types::RateLimit; + use prover_db_migration::{Migrator as MigratorCreate, MigratorTrait}; use rln_proof::RlnIdentifier; const ADDR_1: Address = address!("0xd8da6bf26964af9d7eed9e03e53415d37aa96045"); @@ -38,7 +43,7 @@ mod tests { #[derive(thiserror::Error, Debug)] enum AppErrorExt { #[error("AppError: {0}")] - AppError(#[from] AppError), + AppError(#[from] AppError2), #[error("Future timeout")] Elapsed, #[error("Proof generation failed: {0}")] @@ -53,11 +58,42 @@ mod tests { RecoveredSecret(IdSecret), } + async fn create_database_connection(db_name: &str) -> Result { + // Drop / Create db_name then return a connection to it + + let db_url_base = "postgres://myuser:mysecretpassword@localhost"; + let db_url = format!("{}/{}", db_url_base, "mydatabase"); + let db = Database::connect(db_url) + .await + .expect("Database connection 0 failed"); + + db.execute_raw(Statement::from_string( + db.get_database_backend(), + format!("DROP DATABASE IF EXISTS \"{}\";", db_name), + )) + .await?; + db.execute_raw(Statement::from_string( + db.get_database_backend(), + format!("CREATE DATABASE \"{}\";", db_name), + )) + .await?; + + db.close().await?; + + let db_url_final = format!("{}/{}", db_url_base, db_name); + let db = Database::connect(db_url_final) + .await + .expect("Database connection failed"); + MigratorCreate::up(&db, None).await?; + + Ok(db) + } + async fn proof_sender( sender: Address, proof_tx: &mut async_channel::Sender, rln_identifier: Arc, - user_db: &UserDb, + user_db: &UserDb2, ) -> Result<(), AppErrorExt> { // used by test_proof_generation unit test @@ -65,9 +101,11 @@ mod tests { debug!("Waiting a bit before sending proof..."); tokio::time::sleep(std::time::Duration::from_secs(1)).await; debug!("Sending proof..."); + + let user_identity = user_db.get_user_identity(&ADDR_1).await.unwrap(); proof_tx .send(ProofGenerationData { - user_identity: user_db.get_user(&ADDR_1).unwrap(), + user_identity, rln_identifier, tx_counter: 0, tx_sender: sender, @@ -134,26 +172,28 @@ mod tests { let epoch_store = Arc::new(RwLock::new((epoch, epoch_slice))); // User db - let temp_folder = tempfile::tempdir().unwrap(); - let temp_folder_tree = tempfile::tempdir().unwrap(); - let config = UserDbConfig { - db_path: PathBuf::from(temp_folder.path()), - merkle_tree_folder: PathBuf::from(temp_folder_tree.path()), + let config = UserDb2Config { tree_count: 1, max_tree_count: 1, tree_depth: MERKLE_TREE_HEIGHT, }; + let db_conn = create_database_connection("proof_service_tests_test_user_not_registered") + .await + .unwrap(); + let user_db_service = UserDbService::new( + db_conn, config, Default::default(), epoch_store.clone(), 10.into(), Default::default(), ) + .await .unwrap(); let user_db = user_db_service.get_user_db(); - user_db.on_new_user(&ADDR_1).unwrap(); + user_db.on_new_user(&ADDR_1).await.unwrap(); // user_db.on_new_user(ADDR_2).unwrap(); let rln_identifier = Arc::new(RlnIdentifier::new(b"foo bar baz")); @@ -246,7 +286,7 @@ mod tests { async fn proof_sender_2( proof_tx: &mut async_channel::Sender, rln_identifier: Arc, - user_db: &UserDb, + user_db: &UserDb2, sender: Address, tx_hashes: ([u8; 32], [u8; 32]), ) -> Result<(), AppErrorExt> { @@ -256,9 +296,10 @@ mod tests { debug!("Waiting a bit before sending proof..."); tokio::time::sleep(std::time::Duration::from_secs(1)).await; debug!("Sending proof..."); + let user_identity = user_db.get_user_identity(&sender).await.unwrap(); proof_tx .send(ProofGenerationData { - user_identity: user_db.get_user(&sender).unwrap(), + user_identity, rln_identifier: rln_identifier.clone(), tx_counter: 0, tx_sender: sender, @@ -271,9 +312,10 @@ mod tests { debug!("Waiting a bit before sending 2nd proof..."); tokio::time::sleep(std::time::Duration::from_secs(1)).await; debug!("Sending 2nd proof..."); + let user_identity = user_db.get_user_identity(&sender).await.unwrap(); proof_tx .send(ProofGenerationData { - user_identity: user_db.get_user(&sender).unwrap(), + user_identity, rln_identifier, tx_counter: 1, tx_sender: sender, @@ -305,27 +347,31 @@ mod tests { let rate_limit = RateLimit::from(1); // User db - let temp_folder = tempfile::tempdir().unwrap(); - let temp_folder_tree = tempfile::tempdir().unwrap(); - let config = UserDbConfig { - db_path: PathBuf::from(temp_folder.path()), - merkle_tree_folder: PathBuf::from(temp_folder_tree.path()), + let config = UserDb2Config { tree_count: 1, max_tree_count: 1, tree_depth: MERKLE_TREE_HEIGHT, }; + let db_conn = create_database_connection("proof_service_tests_test_user_spamming") + .await + .unwrap(); let user_db_service = UserDbService::new( + db_conn, config, Default::default(), epoch_store.clone(), rate_limit, Default::default(), ) + .await .unwrap(); let user_db = user_db_service.get_user_db(); - user_db.on_new_user(&ADDR_1).unwrap(); - let user_addr_1 = user_db.get_user(&ADDR_1).unwrap(); - user_db.on_new_user(&ADDR_2).unwrap(); + user_db.on_new_user(&ADDR_1).await.unwrap(); + // let user_addr_1 = user_db.get_user(&ADDR_1).await.unwrap().unwrap(); + + let user_addr1_identity = user_db.get_user_identity(&ADDR_1).await.unwrap(); + + user_db.on_new_user(&ADDR_2).await.unwrap(); let rln_identifier = Arc::new(RlnIdentifier::new(b"foo bar baz")); @@ -354,7 +400,7 @@ mod tests { match res { Err(AppErrorExt::RecoveredSecret(secret_hash)) => { - assert_eq!(secret_hash, user_addr_1.secret_hash); + assert_eq!(secret_hash, user_addr1_identity.secret_hash); } _ => { panic!("Expected to RecoveredSecret, got: {res:?}"); @@ -381,28 +427,30 @@ mod tests { let rate_limit = RateLimit::from(1); // User db - limit is 1 message per epoch - let temp_folder = tempfile::tempdir().unwrap(); - let temp_folder_tree = tempfile::tempdir().unwrap(); - let config = UserDbConfig { - db_path: PathBuf::from(temp_folder.path()), - merkle_tree_folder: PathBuf::from(temp_folder_tree.path()), + let config = UserDb2Config { tree_count: 1, max_tree_count: 1, tree_depth: MERKLE_TREE_HEIGHT, }; + let db_conn = + create_database_connection("proof_service_tests_test_user_spamming_same_signal") + .await + .unwrap(); let user_db_service = UserDbService::new( + db_conn, config, Default::default(), epoch_store.clone(), rate_limit, Default::default(), ) + .await .unwrap(); let user_db = user_db_service.get_user_db(); - user_db.on_new_user(&ADDR_1).unwrap(); - let user_addr_1 = user_db.get_user(&ADDR_1).unwrap(); + user_db.on_new_user(&ADDR_1).await.unwrap(); + let user_addr_1 = user_db.get_user(&ADDR_1).await.unwrap(); debug!("user_addr_1: {:?}", user_addr_1); - user_db.on_new_user(&ADDR_2).unwrap(); + user_db.on_new_user(&ADDR_2).await.unwrap(); let rln_identifier = Arc::new(RlnIdentifier::new(b"foo bar baz")); diff --git a/rln-prover/prover/src/tests_common.rs b/rln-prover/prover/src/tests_common.rs new file mode 100644 index 0000000000..a599c0b98b --- /dev/null +++ b/rln-prover/prover/src/tests_common.rs @@ -0,0 +1,48 @@ +use prover_db_migration::{Migrator as MigratorCreate, MigratorTrait}; +use sea_orm::{ConnectionTrait, Database, DatabaseConnection, DbErr, Statement}; + +pub async fn create_database_connection_1( + f_name: &str, + test_name: &str, +) -> Result<(String, DatabaseConnection), DbErr> { + // Drop / Create db_name then return a connection to it + + let db_name = format!( + "{}_{}", + std::path::Path::new(f_name) + .file_stem() + .unwrap() + .to_str() + .unwrap(), + test_name + ); + + println!("db_name: {}", db_name); + + let db_url_base = "postgres://myuser:mysecretpassword@localhost"; + let db_url = format!("{}/{}", db_url_base, "mydatabase"); + let db = Database::connect(db_url) + .await + .expect("Database connection 0 failed"); + + db.execute_raw(Statement::from_string( + db.get_database_backend(), + format!("DROP DATABASE IF EXISTS \"{}\";", db_name), + )) + .await?; + db.execute_raw(Statement::from_string( + db.get_database_backend(), + format!("CREATE DATABASE \"{}\";", db_name), + )) + .await?; + + db.close().await?; + + let db_url_final = format!("{}/{}", db_url_base, db_name); + let db = Database::connect(&db_url_final) + .await + .expect("Database connection failed"); + MigratorCreate::up(&db, None).await?; + + Ok((db_url_final, db)) +} diff --git a/rln-prover/prover/src/tier.rs b/rln-prover/prover/src/tier.rs index 5ee4de4aa4..d1f16c400f 100644 --- a/rln-prover/prover/src/tier.rs +++ b/rln-prover/prover/src/tier.rs @@ -3,6 +3,7 @@ use std::ops::ControlFlow; // third-party use alloy::primitives::U256; use derive_more::{Deref, DerefMut, From, Into}; +use serde::{Deserialize, Serialize}; // internal use smart_contract::Tier; @@ -18,7 +19,7 @@ impl From<&str> for TierName { } } -#[derive(Debug, Clone, Default, From, Into, Deref, DerefMut, PartialEq)] +#[derive(Debug, Clone, Default, From, Into, Deref, DerefMut, PartialEq, Serialize, Deserialize)] pub struct TierLimits(Vec); impl From<[Tier; N]> for TierLimits { diff --git a/rln-prover/prover/src/tiers_listener.rs b/rln-prover/prover/src/tiers_listener.rs index 09ad1993fe..a3b5340e3d 100644 --- a/rln-prover/prover/src/tiers_listener.rs +++ b/rln-prover/prover/src/tiers_listener.rs @@ -3,19 +3,20 @@ use alloy::{primitives::Address, providers::Provider, sol_types::SolEvent}; use futures::StreamExt; use tracing::error; // internal -use crate::error::AppError; +use crate::error::AppError2; use crate::tier::TierLimits; -use crate::user_db::UserDb; +// use crate::user_db::UserDb; +use crate::user_db_2::UserDb2; use smart_contract::KarmaTiers; use smart_contract::KarmaTiers::KarmaTiersInstance; pub(crate) struct TiersListener { sc_address: Address, - user_db: UserDb, + user_db: UserDb2, } impl TiersListener { - pub(crate) fn new(sc_address: Address, user_db: UserDb) -> Self { + pub(crate) fn new(sc_address: Address, user_db: UserDb2) -> Self { Self { sc_address, user_db, @@ -23,7 +24,7 @@ impl TiersListener { } /// Listen to Smart Contract specified events - pub(crate) async fn listen(&self, provider: P) -> Result<(), AppError> { + pub(crate) async fn listen(&self, provider: P) -> Result<(), AppError2> { // let provider = self.setup_provider_ws().await.map_err(AppError::from)?; let filter = alloy::rpc::types::Filter::new() @@ -47,13 +48,14 @@ impl TiersListener { "Error while getting tiers limits from smart contract: {}", e ); - return Err(AppError::KarmaTiersError(e)); + return Err(AppError2::KarmaTiersError(e)); } }; if let Err(e) = self .user_db .on_tier_limits_updated(TierLimits::from(tier_limits)) + .await { // If there is an error here, we assume this is an error by the user // updating the Tier limits (and thus we don't want to shut down the prover) diff --git a/rln-prover/prover/src/user_db.rs b/rln-prover/prover/src/user_db.rs index ced020688f..e4ebe9b090 100644 --- a/rln-prover/prover/src/user_db.rs +++ b/rln-prover/prover/src/user_db.rs @@ -806,7 +806,7 @@ mod tests { user_db.register(addr).unwrap(); let (ec, ecs) = user_db.get_tx_counter(&addr).unwrap(); - assert_eq!(ec, 0u64.into()); + assert_eq!(ec, EpochCounter::from(0)); assert_eq!(ecs, EpochSliceCounter::from(0u64)); let ecs_2 = user_db.incr_tx_counter(&addr, Some(42)).unwrap(); diff --git a/rln-prover/prover/src/user_db_2.rs b/rln-prover/prover/src/user_db_2.rs new file mode 100644 index 0000000000..797f485af1 --- /dev/null +++ b/rln-prover/prover/src/user_db_2.rs @@ -0,0 +1,941 @@ +use std::fmt::Formatter; +use std::sync::Arc; +// third-party +use alloy::primitives::{Address, U256}; +use ark_bn254::Fr; +use parking_lot::RwLock; +use tokio::sync::RwLock as TokioRwLock; +// RLN +use rln::{hashers::poseidon_hash, protocol::keygen}; +// db +use sea_orm::sea_query::OnConflict; +use sea_orm::{ + ColumnTrait, DatabaseConnection, DbErr, EntityTrait, IntoActiveModel, PaginatorTrait, + QueryFilter, Set, TransactionTrait, +}; +// internal +use crate::epoch_service::{Epoch, EpochSlice}; +use crate::tier::{TierLimit, TierLimits, TierMatch, TierName}; +use crate::user_db::UserTierInfo; +use crate::user_db_error::{ + GetMerkleTreeProofError2, RegisterError2, SetTierLimitsError2, TxCounterError2, + UserTierInfoError2, +}; +use crate::user_db_types::{EpochCounter, EpochSliceCounter, RateLimit}; +use prover_db_entity::{m_tree_config, tier_limits, tx_counter, user}; +use prover_merkle_tree::{ + MemoryDb, MemoryDbConfig, PersistentDb, PersistentDbConfig, PersistentDbError, +}; +use prover_pmtree::tree::MerkleProof; +use prover_pmtree::{MerkleTree, PmtreeErrorKind}; +use rln_proof::{ProverPoseidonHash, RlnUserIdentity}; +use smart_contract::KarmaAmountExt; + +const TIER_LIMITS_KEY: &str = "CURRENT"; +const TIER_LIMITS_NEXT_KEY: &str = "NEXT"; + +type ProverMerkleTree = MerkleTree; + +#[derive(Debug, PartialEq)] +pub struct UserTierInfo2 { + pub(crate) current_epoch: Epoch, + pub(crate) current_epoch_slice: EpochSlice, + pub(crate) epoch_tx_count: u64, + pub(crate) karma_amount: U256, + pub(crate) tier_name: Option, + pub(crate) tier_limit: Option, +} + +#[derive(Clone, Debug)] +pub struct UserDb2Config { + pub(crate) tree_count: u64, + pub(crate) max_tree_count: u64, + pub(crate) tree_depth: u8, +} + +#[derive(Clone)] +pub(crate) struct UserDb2 { + db: DatabaseConnection, + config: UserDb2Config, + rate_limit: RateLimit, + pub(crate) epoch_store: Arc>, + merkle_trees: Arc>>, +} + +impl std::fmt::Debug for UserDb2 { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("UserDb2") + .field("db", &self.db) + .field("config", &self.config) + .field("rate_limit", &self.rate_limit) + .finish() + } +} + +impl UserDb2 { + /// Returns a new `UserDB` instance + pub async fn new( + db: DatabaseConnection, + config: UserDb2Config, + epoch_store: Arc>, + tier_limits: TierLimits, + rate_limit: RateLimit, + ) -> Result { + debug_assert!(config.tree_count <= config.max_tree_count); + + // tier limits + debug_assert!(tier_limits.validate().is_ok()); + let _res_delete = tier_limits::Entity::delete_many() + .filter(tier_limits::Column::Name.eq(TIER_LIMITS_KEY)) + .exec(&db) + .await?; + + let tier_limits_value = serde_json::to_value(tier_limits).unwrap(); + let tier_limits_active_model = tier_limits::ActiveModel { + name: Set(TIER_LIMITS_KEY.to_string()), + tier_limits: Set(Some(tier_limits_value)), + ..Default::default() + }; + tier_limits::Entity::insert(tier_limits_active_model) + .exec(&db) + .await?; + + // merkle trees + let merkle_tree_count = Self::get_merkle_tree_count_from_db(&db).await?; + let mut merkle_trees = Vec::with_capacity(merkle_tree_count as usize); + + if merkle_tree_count == 0 { + // FIXME: 'as' + for i in 0..(config.tree_count as i16) { + let persistent_db_config = PersistentDbConfig { + db_conn: db.clone(), + tree_index: i, + insert_batch_size: 10_000, // TODO: no hardcoded value + }; + + let mt = ProverMerkleTree::new( + config.tree_depth as usize, // FIXME: no 'as' + MemoryDbConfig, + persistent_db_config.clone(), + ) + .await + .unwrap(); + + merkle_trees.push(mt); + } + } else { + for i in 0..(merkle_tree_count as i16) { + let persistent_db_config = PersistentDbConfig { + db_conn: db.clone(), + tree_index: i, + insert_batch_size: 10_000, // TODO: no hardcoded value + }; + + let mt = ProverMerkleTree::load(MemoryDbConfig, persistent_db_config.clone()) + .await + .unwrap(); + + merkle_trees.push(mt); + } + } + + Ok(Self { + db, + config, + rate_limit, + epoch_store, + merkle_trees: Arc::new(TokioRwLock::new(merkle_trees)), + }) + } + + // (Internal) Simple Db related methods + + pub(crate) async fn has_user(&self, address: &Address) -> Result { + let res = user::Entity::find() + .filter(user::Column::Address.eq(address.to_string())) + .one(&self.db) + .await?; + Ok(res.is_some()) + } + + pub(crate) async fn get_user(&self, address: &Address) -> Result, DbErr> { + user::Entity::find() + .filter(user::Column::Address.eq(address.to_string())) + .one(&self.db) + .await + } + + pub(crate) async fn get_user_identity(&self, address: &Address) -> Option { + let res = self.get_user(address).await.ok()??; + // FIXME: deser directly when query with orm? + serde_json::from_value(res.rln_id).ok() + } + + async fn get_tier_limits(&self) -> Result { + let res = tier_limits::Entity::find() + .filter(tier_limits::Column::Name.eq(TIER_LIMITS_KEY)) + .one(&self.db) + .await? + .unwrap() // unwrap safe - db is always initialized with this row + ; + + // unwrap safe - db is initialized with valid tier limits + Ok(serde_json::from_value(res.tier_limits.unwrap()).unwrap()) + } + + async fn set_tier_limits(&self, tier_limits: TierLimits) -> Result<(), DbErr> { + let tier_limits_active_model = tier_limits::ActiveModel { + name: Set(TIER_LIMITS_NEXT_KEY.to_string()), + tier_limits: Set(Some(serde_json::to_value(tier_limits).unwrap())), + ..Default::default() + }; + + // upsert + tier_limits::Entity::insert(tier_limits_active_model) + .on_conflict( + OnConflict::column(tier_limits::Column::Name) + .update_column(tier_limits::Column::TierLimits) + .to_owned(), + ) + .exec(&self.db) + .await?; + Ok(()) + } + + async fn get_merkle_tree_count_from_db(db: &DatabaseConnection) -> Result { + m_tree_config::Entity::find().count(db).await + } + + // internal methods for tx_counter + + async fn incr_tx_counter( + &self, + address: &Address, + incr_value: Option, + ) -> Result { + let incr_value = incr_value.unwrap_or(1); + let (epoch, _epoch_slice) = *self.epoch_store.read(); + + let txn = self.db.begin().await?; + + let res = tx_counter::Entity::find() + .filter(tx_counter::Column::Address.eq(address.to_string())) + .one(&txn) + .await?; + + let new_tx_counter = if let Some(res) = res { + let mut res_active = res.into_active_model(); + + // unwrap safe: res_active.epoch/epoch_slice cannot be null + let model_epoch = res_active.epoch.clone().unwrap(); + let model_epoch_counter = res_active.epoch_counter.clone().unwrap(); + // let model_epoch_slice = res_active.epoch_slice.clone().unwrap(); + // let model_epoch_slice_counter = res_active.epoch_slice_counter.clone().unwrap(); + + if model_epoch == 0 { + res_active.epoch = Set(epoch.into()); + res_active.epoch_counter = Set(incr_value); + // res_active.epoch_slice = Set(epoch_slice.into()); + // res_active.epoch_slice_counter = Set(incr_value); + } else if epoch != Epoch::from(model_epoch) { + // New epoch + res_active.epoch = Set(epoch.into()); + res_active.epoch_counter = Set(incr_value); + // res_active.epoch_slice = Set(0); + // res_active.epoch_slice_counter = Set(incr_value); + } else { + // Same epoch + res_active.epoch_counter = Set(model_epoch_counter.saturating_add(incr_value)); + } + + // res_active.update(&txn).await?; + tx_counter::Entity::update(res_active).exec(&txn).await? + } else { + // first time - need to create a new entry + let new_tx_counter = tx_counter::ActiveModel { + address: Set(address.to_string()), + epoch: Set(epoch.into()), + epoch_counter: Set(incr_value), + // epoch_slice: Set(epoch_slice.into()), + // epoch_slice_counter: Set(incr_value), + ..Default::default() + }; + + // new_tx_counter.insert(&txn).await?; + tx_counter::Entity::insert(new_tx_counter) + .exec_with_returning(&txn) + .await? + }; + + txn.commit().await?; + // FIXME: no 'as' + Ok((new_tx_counter.epoch_counter as u64).into()) + } + + pub(crate) async fn get_tx_counter( + &self, + address: &Address, + ) -> Result { + let res = tx_counter::Entity::find() + .filter(tx_counter::Column::Address.eq(address.to_string())) + .one(&self.db) + .await?; + + match res { + None => Err(TxCounterError2::NotRegistered(*address)), + Some(res) => Ok(self.counters_from_key(res)), + } + } + + fn counters_from_key(&self, model: tx_counter::Model) -> EpochCounter { + let (epoch, _epoch_slice) = *self.epoch_store.read(); + let cmp = (model.epoch == i64::from(epoch)); + + match cmp { + true => { + // EpochCounter stored in DB == epoch store + // We query for an epoch and this is what is stored in the Db + (model.epoch_counter as u64).into() + } + false => { + // EpochCounter.epoch (stored in DB) != epoch_store.epoch + // We query for an epoch after what is stored in Db + // This can happen if no Tx has updated the epoch counter (yet) + EpochCounter::from(0) + } + } + } + + /* + fn counters_from_key(&self, model: tx_counter::Model) -> (EpochCounter, EpochSliceCounter) { + let (epoch, epoch_slice) = *self.epoch_store.read(); + let cmp = ( + model.epoch == i64::from(epoch), + model.epoch_slice == i64::from(epoch_slice), + ); + + match cmp { + (true, true) => { + // EpochCounter stored in DB == epoch store + // We query for an epoch / epoch slice and this is what is stored in the Db + // Return the counters + ( + // FIXME: as + (model.epoch_counter as u64).into(), + // FIXME: as + (model.epoch_slice_counter as u64).into(), + ) + } + (true, false) => { + // EpochCounter.epoch_slice (stored in Db) != epoch_store.epoch_slice + // We query for an epoch slice after what is stored in Db + // This can happen if no Tx has updated the epoch slice counter (yet) + // FIXME: as + ( + (model.epoch_counter as u64).into(), + EpochSliceCounter::from(0), + ) + } + (false, true) => { + // EpochCounter.epoch (stored in DB) != epoch_store.epoch + // We query for an epoch after what is stored in Db + // This can happen if no Tx has updated the epoch counter (yet) + (EpochCounter::from(0), EpochSliceCounter::from(0)) + } + (false, false) => { + // EpochCounter (stored in DB) != epoch_store + // Outdated value (both for epoch & epoch slice) + (EpochCounter::from(0), EpochSliceCounter::from(0)) + } + } + } + */ + + // user register & delete (with app logic) + + pub(crate) async fn register_user(&self, address: Address) -> Result { + // Generate RLN identity + let (identity_secret_hash, id_commitment) = keygen(); + + let rln_identity = RlnUserIdentity::from(( + id_commitment, + identity_secret_hash, + Fr::from(self.rate_limit), + )); + + if self.has_user(&address).await? { + return Err(RegisterError2::AlreadyRegistered(address)); + } + + let rate_commit = poseidon_hash(&[id_commitment, Fr::from(u64::from(self.rate_limit))]); + + let mut guard = self.merkle_trees.write().await; + + let found = guard + .iter_mut() + .enumerate() + .find(|(_, tree)| tree.leaves_set() < tree.capacity()); + + let (last_tree_index, last_index_in_mt) = if let Some((tree_index, tree_to_set)) = found { + // Found a tree that can accept our new user + let index_in_mt = tree_to_set.leaves_set(); + tree_to_set + .set(index_in_mt, rate_commit) + .await + .map_err(RegisterError2::TreeError)?; + + (tree_index, index_in_mt) + } else { + // All trees are full, let's create a new one that can accept our new user + + // as safe : assume sizeof usize == sizeof 64 (see user_db_types.rs) + let tree_count = guard.len() as u64; + + if tree_count == self.config.max_tree_count { + return Err(RegisterError2::TooManyUsers); + } + + let persistent_db_config = PersistentDbConfig { + db_conn: self.db.clone(), + tree_index: tree_count as i16, // FIXME: as + insert_batch_size: 10_000, // TODO: no hardcoded value + }; + + let mut mt = ProverMerkleTree::new( + self.config.tree_depth as usize, + MemoryDbConfig, + persistent_db_config.clone(), + ) + .await + .unwrap(); + + mt.set(0, rate_commit) + .await + .map_err(RegisterError2::TreeError)?; + + guard.push(mt); + + (tree_count as usize, 0) + }; + + drop(guard); + + let txn = self.db.begin().await?; + + // TODO: unwrap safe? + let user_active_model = user::ActiveModel { + address: Set(address.to_string()), + rln_id: Set(serde_json::to_value(rln_identity).unwrap()), + tree_index: Set(last_tree_index as i64), + index_in_merkle_tree: Set(last_index_in_mt as i64), // FIXME + ..Default::default() + }; + + user::Entity::insert(user_active_model).exec(&txn).await?; + + let tx_counter_active_model = tx_counter::ActiveModel { + address: Set(address.to_string()), + ..Default::default() + }; + + tx_counter::Entity::insert(tx_counter_active_model) + .exec(&txn) + .await?; + + txn.commit().await?; + + Ok(id_commitment) + } + + pub(crate) async fn remove_user(&self, address: &Address) -> Result { + let user = self + .get_user(address) + .await + .map_err(|e| MerkleTreeError::PDb(e.into()))?; + + if user.is_none() { + // User not found (User not registered) + return Ok(false); + } + + let user = user.unwrap(); // Unwrap safe: just checked above + let tree_index = user.tree_index as usize; + let index_in_merkle_tree = user.index_in_merkle_tree as usize; + + let mut guard = self.merkle_trees.write().await; + // FIXME: unwrap safe? + let mt = guard.get_mut(tree_index).unwrap(); + // Only delete it if this is the last index + // Note: No reuse of index in PmTree (as this is a generic impl and could lead to security issue: + // like replay attack...) + if mt.leaves_set().saturating_sub(1) == index_in_merkle_tree { + mt.delete(index_in_merkle_tree).await?; + } else { + // FIXME + println!("Not the last {} {}", index_in_merkle_tree, mt.leaves_set()); + } + + // TODO: delete in merkle tree in txn + // FIXME: map_err repetitions? + let txn = self + .db + .begin() + .await + .map_err(|e| MerkleTreeError::PDb(e.into()))?; + user::Entity::delete_many() + .filter(user::Column::Address.eq(address.to_string())) + .exec(&txn) + .await + .map_err(|e| MerkleTreeError::PDb(e.into()))?; + tx_counter::Entity::delete_many() + .filter(tx_counter::Column::Address.eq(address.to_string())) + .exec(&txn) + .await + .map_err(|e| MerkleTreeError::PDb(e.into()))?; + txn.commit() + .await + .map_err(|e| MerkleTreeError::PDb(e.into()))?; + + Ok(true) + } + + // Merkle tree methods + pub async fn get_merkle_proof( + &self, + address: &Address, + ) -> Result, GetMerkleTreeProofError2> { + let (tree_index, index_in_mt) = { + let user = self.get_user(address).await?; + if user.is_none() { + return Err(GetMerkleTreeProofError2::NotRegistered(*address)); + } + let user = user.unwrap(); + (user.tree_index, user.index_in_merkle_tree) + }; + + let guard = self.merkle_trees.read().await; + // FIXME: no 'as' + let proof = guard[tree_index as usize] + .proof(index_in_mt as usize) + .map_err(GetMerkleTreeProofError2::from)?; + + Ok(proof) + } + + // external UserDb methods + + pub async fn on_new_user(&self, address: &Address) -> Result { + self.register_user(*address).await + } + + pub async fn on_new_tx( + &self, + address: &Address, + incr_value: Option, + ) -> Result { + let has_user = self.has_user(address).await?; + + if has_user { + let epoch_counter = self.incr_tx_counter(address, incr_value).await?; + Ok(epoch_counter) + } else { + Err(TxCounterError2::NotRegistered(*address)) + } + } + + pub async fn on_tier_limits_updated( + &self, + tier_limits: TierLimits, + ) -> Result<(), SetTierLimitsError2> { + tier_limits.validate()?; + self.set_tier_limits(tier_limits) + .await + .map_err(SetTierLimitsError2::Db) + } + + /// Get user tier info + pub(crate) async fn user_tier_info>( + &self, + address: &Address, + karma_sc: &KSC, + ) -> Result> { + let has_user = self + .has_user(address) + .await + .map_err(UserTierInfoError2::Db)?; + + if !has_user { + return Err(UserTierInfoError2::NotRegistered(*address)); + } + + let karma_amount = karma_sc + .karma_amount(address) + .await + .map_err(|e| UserTierInfoError2::Contract(e))?; + + // TODO + let epoch_tx_count = self.get_tx_counter(address).await?; + // TODO: avoid db query the tier limits (keep it in memory) + let tier_limits = self.get_tier_limits().await?; + let tier_match = tier_limits.get_tier_by_karma(&karma_amount); + + let user_tier_info = { + let (current_epoch, current_epoch_slice) = *self.epoch_store.read(); + let mut t = UserTierInfo2 { + current_epoch, + current_epoch_slice, + epoch_tx_count: epoch_tx_count.into(), + // epoch_slice_tx_count: epoch_slice_tx_count.into(), + karma_amount, + tier_name: None, + tier_limit: None, + }; + + if let TierMatch::Matched(tier) = tier_match { + t.tier_name = Some(tier.name.into()); + t.tier_limit = Some(TierLimit::from(tier.tx_per_epoch)); + } + + t + }; + + Ok(user_tier_info) + } +} + +// Test only functions +#[cfg(test)] +impl UserDb2 { + pub(crate) async fn get_db_tree_count(&self) -> Result { + Self::get_merkle_tree_count_from_db(&self.db).await + } + + pub(crate) async fn get_vec_tree_count(&self) -> usize { + self.merkle_trees.read().await.len() + } + + pub(crate) async fn get_user_indexes(&self, address: &Address) -> (i64, i64) { + let user_model = self.get_user(address).await.unwrap().unwrap(); + + (user_model.tree_index, user_model.index_in_merkle_tree) + } +} + +#[derive(thiserror::Error, Debug)] +pub enum MerkleTreeError { + #[error(transparent)] + PmtreeError(#[from] PmtreeErrorKind), + #[error(transparent)] + PDb(#[from] PersistentDbError), +} + +#[cfg(feature = "postgres")] +#[cfg(test)] +mod tests { + use super::*; + // std + // third-party + use alloy::primitives::{U256, address}; + use async_trait::async_trait; + use claims::assert_matches; + use derive_more::Display; + use sea_orm::{ConnectionTrait, Database, Statement}; + use tracing_test::traced_test; + // internal + use prover_db_migration::{Migrator as MigratorCreate, MigratorTrait}; + + #[derive(Debug, Display, thiserror::Error)] + struct DummyError(); + + struct MockKarmaSc {} + + #[async_trait] + impl KarmaAmountExt for MockKarmaSc { + type Error = DummyError; + + async fn karma_amount(&self, _address: &Address) -> Result { + Ok(U256::from(10)) + } + } + + const ADDR_1: Address = address!("0xd8da6bf26964af9d7eed9e03e53415d37aa96045"); + const ADDR_2: Address = address!("0xb20a608c624Ca5003905aA834De7156C68b2E1d0"); + pub(crate) const MERKLE_TREE_HEIGHT: u8 = 20; + + async fn create_database_connection(db_name: &str) -> Result { + // Drop / Create db_name then return a connection to it + + let db_url_base = "postgres://myuser:mysecretpassword@localhost"; + let db_url = format!("{}/{}", db_url_base, "mydatabase"); + let db = Database::connect(db_url) + .await + .expect("Database connection 0 failed"); + + db.execute_raw(Statement::from_string( + db.get_database_backend(), + format!("DROP DATABASE IF EXISTS \"{}\";", db_name), + )) + .await?; + db.execute_raw(Statement::from_string( + db.get_database_backend(), + format!("CREATE DATABASE \"{}\";", db_name), + )) + .await?; + + db.close().await?; + + let db_url_final = format!("{}/{}", db_url_base, db_name); + let db = Database::connect(db_url_final) + .await + .expect("Database connection failed"); + MigratorCreate::up(&db, None).await?; + + Ok(db) + } + + #[tokio::test] + // #[traced_test] + async fn test_user_register() { + // Use this to see sea_orm traces + // tracing_subscriber::fmt() + // .with_max_level(tracing::Level::DEBUG) + // .with_test_writer() + // .init(); + + let epoch_store = Arc::new(RwLock::new(Default::default())); + let config = UserDb2Config { + tree_count: 1, + max_tree_count: 1, + tree_depth: MERKLE_TREE_HEIGHT, + }; + let db_conn = create_database_connection("user_db_test_user_register") + .await + .unwrap(); + + let user_db = UserDb2::new( + db_conn, + config, + epoch_store, + Default::default(), + Default::default(), + ) + .await + .expect("Cannot create UserDb"); + + let addr = Address::new([0; 20]); + user_db.register_user(addr).await.unwrap(); + assert_matches!( + user_db.register_user(addr).await, + Err(RegisterError2::AlreadyRegistered(_)) + ); + + assert!(user_db.get_user_identity(&addr).await.is_some()); + assert_eq!( + user_db.get_tx_counter(&addr).await.unwrap(), + EpochCounter::from(0) + ); + + assert!(user_db.get_user_identity(&ADDR_1).await.is_none()); + user_db.register_user(ADDR_1).await.unwrap(); + assert!(user_db.get_user_identity(&ADDR_1).await.is_some()); + assert_eq!( + user_db.get_tx_counter(&addr).await.unwrap(), + EpochCounter::from(0) + ); + + user_db.incr_tx_counter(&addr, Some(42)).await.unwrap(); + assert_eq!( + user_db.get_tx_counter(&addr).await.unwrap(), + EpochCounter::from(42) + ); + } + + #[tokio::test] + async fn test_get_tx_counter() { + let epoch_store = Arc::new(RwLock::new(Default::default())); + let config = UserDb2Config { + tree_count: 1, + max_tree_count: 1, + tree_depth: MERKLE_TREE_HEIGHT, + }; + let db_conn = create_database_connection("user_db_test_tx_counter") + .await + .unwrap(); + + let user_db = UserDb2::new( + db_conn, + config, + epoch_store, + Default::default(), + Default::default(), + ) + .await + .expect("Cannot create UserDb"); + + let addr = Address::new([0; 20]); + + user_db.register_user(addr).await.unwrap(); + + let ec = user_db.get_tx_counter(&addr).await.unwrap(); + assert_eq!(ec, EpochCounter::from(0)); + // assert_eq!(ecs, EpochSliceCounter::from(0u64)); + + let ecs_2 = user_db.incr_tx_counter(&addr, Some(42)).await.unwrap(); + // TODO + assert_eq!(ecs_2, EpochCounter::from(42)); + } + + #[tokio::test] + async fn test_incr_tx_counter() { + let epoch_store = Arc::new(RwLock::new(Default::default())); + let config = UserDb2Config { + tree_count: 1, + max_tree_count: 1, + tree_depth: MERKLE_TREE_HEIGHT, + }; + let db_conn = create_database_connection("user_db_test_incr_tx_counter") + .await + .unwrap(); + + let user_db = UserDb2::new( + db_conn, + config, + epoch_store, + Default::default(), + Default::default(), + ) + .await + .expect("Cannot create UserDb"); + + let addr = Address::new([0; 20]); + + // Try to update tx counter without registering first + assert_matches!( + user_db.on_new_tx(&addr, None).await, + Err(TxCounterError2::NotRegistered(_)) + ); + + let tier_info = user_db.user_tier_info(&addr, &MockKarmaSc {}).await; + // User is not registered -> no tier info + assert!(matches!( + tier_info, + Err(UserTierInfoError2::NotRegistered(_)) + )); + // Register user + user_db.register_user(addr).await.unwrap(); + // Now update user tx counter + assert_eq!( + user_db.on_new_tx(&addr, None).await, + Ok(EpochCounter::from(1)) + ); + let tier_info = user_db + .user_tier_info(&addr, &MockKarmaSc {}) + .await + .unwrap(); + assert_eq!(tier_info.epoch_tx_count, 1); + // assert_eq!(tier_info.epoch_slice_tx_count, 1); + } + + #[tokio::test] + async fn test_user_remove() { + let epoch_store = Arc::new(RwLock::new(Default::default())); + let config = UserDb2Config { + tree_count: 1, + max_tree_count: 1, + tree_depth: crate::user_db::MERKLE_TREE_HEIGHT, + }; + let db_conn = create_database_connection("user_db_test_user_remove") + .await + .unwrap(); + + let user_db = UserDb2::new( + db_conn, + config, + epoch_store, + Default::default(), + Default::default(), + ) + .await + .expect("Cannot create UserDb"); + + user_db.register_user(ADDR_1).await.unwrap(); + let guard = user_db.merkle_trees.read().await; + let mtree_index_add_addr_1 = guard[0].leaves_set(); + // Note: need to drop read guard before registering user as register_user tries to acquire + // write lock on merkle trees (and will wait indefinitely if a read lock is held) + drop(guard); + user_db.register_user(ADDR_2).await.unwrap(); + let guard = user_db.merkle_trees.read().await; + let mtree_index_add_addr_2 = guard[0].leaves_set(); + drop(guard); + assert_ne!(mtree_index_add_addr_1, mtree_index_add_addr_2); + println!("index addr 1: {}", mtree_index_add_addr_1); + println!("index addr 2: {}", mtree_index_add_addr_2); + + user_db.remove_user(&ADDR_2).await.unwrap(); + let guard = user_db.merkle_trees.read().await; + let mtree_index_after_rm_addr_2 = guard[0].leaves_set(); + drop(guard); + assert_eq!(user_db.has_user(&ADDR_1).await, Ok(true)); + assert_eq!(user_db.has_user(&ADDR_2).await, Ok(false)); + // No reuse of index in PmTree (as this is a generic impl and could lead to security issue: + // like replay attack...) + assert_eq!(mtree_index_after_rm_addr_2, mtree_index_add_addr_2); + } + + #[tokio::test] + // #[traced_test] + async fn test_user_reg_merkle_tree_fail() { + // Try to register some users but init UserDb so the merkle tree write will fail (after 1st register) + // This tests ensures that the DB and the MerkleTree stays in sync + + let epoch_store = Arc::new(RwLock::new(Default::default())); + let config = UserDb2Config { + tree_count: 1, + max_tree_count: 1, + tree_depth: 1, + }; + let db_conn = create_database_connection("user_db_test_user_reg_merkle_tree_fail") + .await + .unwrap(); + + let user_db = UserDb2::new( + db_conn, + config, + epoch_store, + Default::default(), + Default::default(), + ) + .await + .expect("Cannot create UserDb"); + + let addr = Address::new([0; 20]); + { + let guard = user_db.merkle_trees.read().await; + let mt = guard.first().unwrap(); + assert_eq!(mt.leaves_set(), 0); + } + user_db.register_user(addr).await.unwrap(); + { + let guard = user_db.merkle_trees.read().await; + let mt = guard.first().unwrap(); + assert_eq!(mt.leaves_set(), 1); + } + user_db.register_user(ADDR_1).await.unwrap(); + { + let guard = user_db.merkle_trees.read().await; + let mt = guard.first().unwrap(); + assert_eq!(mt.leaves_set(), 2); + } + + let res = user_db.register_user(ADDR_2).await; + assert_matches!(res, Err(RegisterError2::TooManyUsers)); + assert_eq!(user_db.has_user(&ADDR_1).await, Ok(true)); + assert_eq!(user_db.has_user(&ADDR_2).await, Ok(false)); + { + let guard = user_db.merkle_trees.read().await; + let mt = guard.first().unwrap(); + assert_eq!(mt.leaves_set(), 2); + } + } +} diff --git a/rln-prover/prover/src/user_db_2_tests.rs b/rln-prover/prover/src/user_db_2_tests.rs new file mode 100644 index 0000000000..18ae0a819f --- /dev/null +++ b/rln-prover/prover/src/user_db_2_tests.rs @@ -0,0 +1,392 @@ +#[cfg(feature = "postgres")] +#[cfg(test)] +mod tests { + // std + use std::sync::Arc; + // third-party + use crate::epoch_service::{Epoch, EpochSlice}; + use crate::user_db::MERKLE_TREE_HEIGHT; + use crate::user_db_2::{UserDb2, UserDb2Config}; + use alloy::primitives::{Address, address}; + use claims::assert_matches; + use parking_lot::RwLock; + use sea_orm::{ConnectionTrait, Database, DatabaseConnection, DbErr, Statement}; + // internal + use crate::user_db_error::RegisterError2; + use crate::user_db_types::{EpochCounter, EpochSliceCounter}; + use prover_db_migration::{Migrator as MigratorCreate, MigratorTrait}; + + const ADDR_1: Address = address!("0xd8da6bf26964af9d7eed9e03e53415d37aa96045"); + const ADDR_2: Address = address!("0xb20a608c624Ca5003905aA834De7156C68b2E1d0"); + const ADDR_3: Address = address!("0x6d2e03b7EfFEae98BD302A9F836D0d6Ab0002766"); + const ADDR_4: Address = address!("0x7A4d20b913B97aD2F30B30610e212D7db11B4BC3"); + + async fn create_database_connection( + db_name: &str, + db_refresh: bool, + ) -> Result { + // Drop / Create db_name then return a connection to it + + let db_url_base = "postgres://myuser:mysecretpassword@localhost"; + let db_url = format!("{}/{}", db_url_base, "mydatabase"); + + if db_refresh { + let db = Database::connect(db_url) + .await + .expect("Database connection 0 failed"); + + db.execute_raw(Statement::from_string( + db.get_database_backend(), + format!("DROP DATABASE IF EXISTS \"{}\";", db_name), + )) + .await?; + db.execute_raw(Statement::from_string( + db.get_database_backend(), + format!("CREATE DATABASE \"{}\";", db_name), + )) + .await?; + + db.close().await?; + } + + let db_url_final = format!("{}/{}", db_url_base, db_name); + let db = Database::connect(db_url_final) + .await + .expect("Database connection failed"); + MigratorCreate::up(&db, None).await?; + + Ok(db) + } + + #[tokio::test] + async fn test_incr_tx_counter_2() { + // Same as test_incr_tx_counter but multi users AND multi incr + + let epoch_store = Arc::new(RwLock::new(Default::default())); + let epoch = 1; + let epoch_slice = 42; + *epoch_store.write() = (Epoch::from(epoch), EpochSlice::from(epoch_slice)); + + let config = UserDb2Config { + tree_count: 1, + max_tree_count: 1, + tree_depth: MERKLE_TREE_HEIGHT, + }; + + let db_conn = create_database_connection("user_db_tests_test_incr_tx_counter_2", true) + .await + .unwrap(); + + let user_db = UserDb2::new( + db_conn, + config, + epoch_store, + Default::default(), + Default::default(), + ) + .await + .expect("Cannot create UserDb"); + + // Register users + user_db.register_user(ADDR_1).await.unwrap(); + user_db.register_user(ADDR_2).await.unwrap(); + + assert_eq!( + user_db.get_tx_counter(&ADDR_1).await, + Ok(EpochCounter::from(0)) + ); + assert_eq!( + user_db.get_tx_counter(&ADDR_2).await, + Ok(EpochCounter::from(0)) + ); + + // Now update user tx counter + assert_eq!( + user_db.on_new_tx(&ADDR_1, None).await, + Ok(EpochCounter::from(1)) + ); + assert_eq!( + user_db.on_new_tx(&ADDR_1, None).await, + Ok(EpochCounter::from(2)) + ); + assert_eq!( + user_db.on_new_tx(&ADDR_1, Some(2)).await, + Ok(EpochCounter::from(4)) + ); + + assert_eq!( + user_db.on_new_tx(&ADDR_2, None).await, + Ok(EpochCounter::from(1)) + ); + + assert_eq!( + user_db.on_new_tx(&ADDR_2, None).await, + Ok(EpochCounter::from(2)) + ); + + assert_eq!( + user_db.get_tx_counter(&ADDR_1).await, + Ok(EpochCounter::from(4)) + ); + + assert_eq!( + user_db.get_tx_counter(&ADDR_2).await, + Ok(EpochCounter::from(2)) + ); + } + + #[tokio::test] + async fn test_persistent_storage() { + let epoch_store = Arc::new(RwLock::new(Default::default())); + let config = UserDb2Config { + tree_count: 1, + max_tree_count: 1, + tree_depth: MERKLE_TREE_HEIGHT, + }; + + let addr = Address::new([0; 20]); + { + let db_conn = create_database_connection("user_db_tests_test_persistent_storage", true) + .await + .unwrap(); + + let user_db = UserDb2::new( + db_conn.clone(), + config.clone(), + epoch_store.clone(), + Default::default(), + Default::default(), + ) + .await + .expect("Cannot create UserDb"); + + // Register user + user_db.register_user(ADDR_1).await.unwrap(); + + // + 1 user + user_db.register_user(ADDR_2).await.unwrap(); + + let user_model = user_db.get_user(&ADDR_1).await.unwrap().unwrap(); + assert_eq!( + (user_model.tree_index, user_model.index_in_merkle_tree), + (0, 0) + ); + let user_model = user_db.get_user(&ADDR_2).await.unwrap().unwrap(); + assert_eq!( + (user_model.tree_index, user_model.index_in_merkle_tree), + (0, 1) + ); + + assert_eq!( + user_db.on_new_tx(&ADDR_1, Some(2)).await, + Ok(EpochCounter::from(2)) + ); + assert_eq!( + user_db.on_new_tx(&ADDR_2, Some(1000)).await, + Ok(EpochCounter::from(1000)) + ); + + db_conn.close().await.unwrap(); + // user_db is dropped at the end of the scope, but let's make it explicit + drop(user_db); + } + + { + // Reopen Db and check that is inside + let db_conn = + create_database_connection("user_db_tests_test_persistent_storage", false) + .await + .unwrap(); + + let user_db = UserDb2::new( + db_conn, + config, + epoch_store, + Default::default(), + Default::default(), + ) + .await + .expect("Cannot create UserDb"); + + assert!(!user_db.has_user(&addr).await.unwrap()); + assert!(user_db.has_user(&ADDR_1).await.unwrap()); + assert!(user_db.has_user(&ADDR_2).await.unwrap()); + assert_eq!( + user_db.get_tx_counter(&ADDR_1).await.unwrap(), + EpochCounter::from(2) + ); + assert_eq!( + user_db.get_tx_counter(&ADDR_2).await.unwrap(), + EpochCounter::from(1000) + ); + + let user_model = user_db.get_user(&ADDR_1).await.unwrap().unwrap(); + assert_eq!( + (user_model.tree_index, user_model.index_in_merkle_tree), + (0, 0) + ); + let user_model = user_db.get_user(&ADDR_2).await.unwrap().unwrap(); + assert_eq!( + (user_model.tree_index, user_model.index_in_merkle_tree), + (0, 1) + ); + } + } + + #[tokio::test] + async fn test_multi_tree() { + let epoch_store = Arc::new(RwLock::new(Default::default())); + let tree_count = 3; + let config = UserDb2Config { + tree_count, + max_tree_count: 3, + tree_depth: 1, + }; + + { + let db_conn = create_database_connection("user_db_tests_test_multi_tree", true) + .await + .unwrap(); + + let user_db = UserDb2::new( + db_conn.clone(), + config.clone(), + epoch_store.clone(), + Default::default(), + Default::default(), + ) + .await + .expect("Cannot create UserDb"); + + assert_eq!(user_db.get_db_tree_count().await.unwrap(), tree_count); + assert_eq!(user_db.get_vec_tree_count().await as u64, tree_count); + + user_db.register_user(ADDR_1).await.unwrap(); + user_db.register_user(ADDR_2).await.unwrap(); + user_db.register_user(ADDR_3).await.unwrap(); + user_db.register_user(ADDR_4).await.unwrap(); + + assert_eq!(user_db.get_user_indexes(&ADDR_1).await, (0, 0)); + assert_eq!(user_db.get_user_indexes(&ADDR_2).await, (0, 1)); + assert_eq!(user_db.get_user_indexes(&ADDR_3).await, (1, 0)); + assert_eq!(user_db.get_user_indexes(&ADDR_4).await, (1, 1)); + + drop(user_db); + } + + { + // reload UserDb from disk and check indexes + + let db_conn = create_database_connection("user_db_tests_test_multi_tree", false) + .await + .unwrap(); + + let user_db = UserDb2::new( + db_conn, + config, + epoch_store, + Default::default(), + Default::default(), + ) + .await + .expect("Cannot create UserDb"); + + assert_eq!(user_db.get_db_tree_count().await.unwrap(), tree_count); + assert_eq!(user_db.get_vec_tree_count().await as u64, tree_count); + + let addr = Address::random(); + user_db.register_user(addr).await.unwrap(); + + assert_eq!(user_db.get_user_indexes(&ADDR_1).await, (0, 0)); + assert_eq!(user_db.get_user_indexes(&ADDR_2).await, (0, 1)); + assert_eq!(user_db.get_user_indexes(&ADDR_3).await, (1, 0)); + assert_eq!(user_db.get_user_indexes(&ADDR_4).await, (1, 1)); + assert_eq!(user_db.get_user_indexes(&addr).await, (2, 0)); + } + } + + #[tokio::test] + async fn test_new_multi_tree() { + // Check if UserDb add a new tree is a tree is full + + let epoch_store = Arc::new(RwLock::new(Default::default())); + let tree_depth = 1; + let tree_count_initial = 1; + let config = UserDb2Config { + tree_count: tree_count_initial, + max_tree_count: 2, + tree_depth, + }; + + let db_conn = create_database_connection("user_db_tests_test_new_multi_tree", true) + .await + .unwrap(); + + let user_db = UserDb2::new( + db_conn.clone(), + config.clone(), + epoch_store.clone(), + Default::default(), + Default::default(), + ) + .await + .expect("Cannot create UserDb"); + + assert_eq!( + user_db.get_db_tree_count().await.unwrap(), + tree_count_initial + ); + assert_eq!( + user_db.get_vec_tree_count().await as u64, + tree_count_initial + ); + + user_db.register_user(ADDR_1).await.unwrap(); + assert_eq!(user_db.get_user_indexes(&ADDR_1).await, (0, 0)); + user_db.register_user(ADDR_2).await.unwrap(); + assert_eq!(user_db.get_user_indexes(&ADDR_2).await, (0, 1)); + user_db.register_user(ADDR_3).await.unwrap(); + assert_eq!(user_db.get_user_indexes(&ADDR_3).await, (1, 0)); + user_db.register_user(ADDR_4).await.unwrap(); + assert_eq!(user_db.get_user_indexes(&ADDR_4).await, (1, 1)); + + let addr = Address::random(); + let res = user_db.register_user(addr).await; + assert_matches!(res, Err(RegisterError2::TooManyUsers)); + assert_eq!( + user_db.get_db_tree_count().await.unwrap(), + tree_count_initial + 1 + ); + assert_eq!( + user_db.get_vec_tree_count().await as u64, + tree_count_initial + 1 + ); + + drop(user_db); + + { + let db_conn = create_database_connection("user_db_tests_test_new_multi_tree", false) + .await + .unwrap(); + + let user_db = UserDb2::new( + db_conn.clone(), + config.clone(), + epoch_store.clone(), + Default::default(), + Default::default(), + ) + .await + .expect("Cannot create UserDb"); + + assert_eq!( + user_db.get_db_tree_count().await.unwrap(), + tree_count_initial + 1 + ); + assert_eq!( + user_db.get_vec_tree_count().await as u64, + tree_count_initial + 1 + ); + } + } +} diff --git a/rln-prover/prover/src/user_db_error.rs b/rln-prover/prover/src/user_db_error.rs index 0010ce01c4..64155f2bac 100644 --- a/rln-prover/prover/src/user_db_error.rs +++ b/rln-prover/prover/src/user_db_error.rs @@ -1,9 +1,13 @@ use std::num::TryFromIntError; // third-party use alloy::primitives::Address; +use prover_pmtree::PmtreeErrorKind; +use sea_orm::DbErr; use zerokit_utils::error::{FromConfigError, ZerokitMerkleTreeError}; // internal use crate::tier::ValidateTierLimitsError; +// TODO: define MerkleTreeError here? +use crate::user_db_2::MerkleTreeError; #[derive(Debug, thiserror::Error)] pub enum UserDbOpenError { @@ -92,3 +96,74 @@ pub enum UserTierInfoError { #[error(transparent)] Db(#[from] rocksdb::Error), } + +// UserDb2 +#[derive(Debug, thiserror::Error)] +pub enum UserDb2OpenError { + #[error(transparent)] + Db(#[from] DbErr), +} + +#[derive(thiserror::Error, Debug)] +pub enum RegisterError2 { + #[error("User (address: {0:?}) has already been registered")] + AlreadyRegistered(Address), + #[error(transparent)] + Db(#[from] DbErr), + #[error("Too many users, exceeding merkle tree capacity...")] + TooManyUsers, + #[error("Merkle tree error: {0}")] + TreeError(MerkleTreeError), + #[error(transparent)] + Io(#[from] std::io::Error), + #[error(transparent)] + FromConfig(#[from] FromConfigError), +} + +#[derive(thiserror::Error, Debug, PartialEq)] +pub enum TxCounterError2 { + #[error("User (address: {0:?}) is not registered")] + NotRegistered(Address), + #[error(transparent)] + Db(#[from] DbErr), +} + +#[derive(thiserror::Error, Debug, Clone)] +pub enum GetMerkleTreeProofError2 { + #[error("User (address: {0:?}) is not registered")] + NotRegistered(Address), + #[error(transparent)] + Db(#[from] DbErr), + #[error(transparent)] + MerkleTree(#[from] PmtreeErrorKind), +} + +/* +#[derive(thiserror::Error, Debug, PartialEq, Clone)] +pub enum UserMerkleTreeIndexError2 { + #[error("User (address: {0:?}) is not registered")] + NotRegistered(Address), + #[error(transparent)] + Db(#[from] DbErr), +} +*/ + +#[derive(Debug, thiserror::Error)] +pub enum SetTierLimitsError2 { + #[error(transparent)] + Validate(#[from] ValidateTierLimitsError), + #[error(transparent)] + Db(#[from] DbErr), +} + +#[derive(Debug, thiserror::Error)] +pub enum UserTierInfoError2 { + #[error("User {0} not registered")] + NotRegistered(Address), + #[error(transparent)] + Contract(E), + #[error(transparent)] + TxCounter(#[from] TxCounterError2), + #[error(transparent)] + Db(#[from] DbErr), +} diff --git a/rln-prover/prover/src/user_db_service.rs b/rln-prover/prover/src/user_db_service.rs index e102cfec41..8f2f9b3267 100644 --- a/rln-prover/prover/src/user_db_service.rs +++ b/rln-prover/prover/src/user_db_service.rs @@ -1,44 +1,47 @@ // std use parking_lot::RwLock; +use sea_orm::DatabaseConnection; use std::sync::Arc; // third-party use tokio::sync::Notify; use tracing::debug; // internal use crate::epoch_service::{Epoch, EpochSlice}; -use crate::error::AppError; +use crate::error::AppError2; use crate::tier::TierLimits; -use crate::user_db::{UserDb, UserDbConfig}; -use crate::user_db_error::UserDbOpenError; +// use crate::user_db::{UserDb, UserDbConfig}; +use crate::user_db_2::{UserDb2, UserDb2Config}; +use crate::user_db_error::UserDb2OpenError; use crate::user_db_types::RateLimit; /// Async service to update a UserDb on epoch changes #[derive(Debug)] pub struct UserDbService { - user_db: UserDb, + user_db: UserDb2, epoch_changes: Arc, } impl UserDbService { - pub fn new( - config: UserDbConfig, + pub async fn new( + db_conn: DatabaseConnection, + config: UserDb2Config, epoch_changes_notifier: Arc, epoch_store: Arc>, rate_limit: RateLimit, tier_limits: TierLimits, - ) -> Result { - let user_db = UserDb::new(config, epoch_store, tier_limits, rate_limit)?; + ) -> Result { + let user_db = UserDb2::new(db_conn, config, epoch_store, tier_limits, rate_limit).await?; Ok(Self { user_db, epoch_changes: epoch_changes_notifier, }) } - pub fn get_user_db(&self) -> UserDb { + pub fn get_user_db(&self) -> UserDb2 { self.user_db.clone() } - pub async fn listen_for_epoch_changes(&self) -> Result<(), AppError> { + pub async fn listen_for_epoch_changes(&self) -> Result<(), AppError2> { let (mut current_epoch, mut current_epoch_slice) = *self.user_db.epoch_store.read(); loop { @@ -65,11 +68,13 @@ impl UserDbService { current_epoch_slice: &mut EpochSlice, new_epoch_slice: EpochSlice, ) { + /* if new_epoch > *current_epoch { self.user_db.on_new_epoch() } else if new_epoch_slice > *current_epoch_slice { self.user_db.on_new_epoch_slice() } + */ *current_epoch = new_epoch; *current_epoch_slice = new_epoch_slice; diff --git a/rln-prover/prover/src/user_db_types.rs b/rln-prover/prover/src/user_db_types.rs index 9215bc3ee9..dc2549196c 100644 --- a/rln-prover/prover/src/user_db_types.rs +++ b/rln-prover/prover/src/user_db_types.rs @@ -81,6 +81,18 @@ impl From for Fr { #[derive(Debug, Default, Clone, Copy, PartialEq, From, Into, Add)] pub(crate) struct EpochCounter(u64); +impl PartialEq for EpochCounter { + fn eq(&self, other: &RateLimit) -> bool { + self.0 == other.0 + } +} + +impl PartialOrd for EpochCounter { + fn partial_cmp(&self, other: &RateLimit) -> Option { + Some(self.0.cmp(&other.0)) + } +} + /// A Tx counter for a user in a given epoch slice #[derive(Debug, Default, Clone, Copy, PartialEq, From, Into, Add)] pub(crate) struct EpochSliceCounter(u64); diff --git a/rln-prover/prover/tests/grpc_e2e.rs b/rln-prover/prover/tests/grpc_e2e.rs deleted file mode 100644 index dc752641ba..0000000000 --- a/rln-prover/prover/tests/grpc_e2e.rs +++ /dev/null @@ -1,535 +0,0 @@ -use std::io::Write; -use std::net::{IpAddr, Ipv4Addr}; -use std::num::NonZeroU64; -use std::str::FromStr; -use std::sync::Arc; -use std::time::Duration; -// third-party -use alloy::primitives::{Address, U256}; -use futures::FutureExt; -use parking_lot::RwLock; -use tempfile::NamedTempFile; -use tokio::task; -use tokio::task::JoinSet; -use tonic::Response; -use tracing::{debug, info}; -// use tracing_test::traced_test; -// internal -use prover::{AppArgs, MockUser, run_prover}; -pub mod prover_proto { - // Include generated code (see build.rs) - tonic::include_proto!("prover"); -} -use crate::prover_proto::get_user_tier_info_reply::Resp; -use crate::prover_proto::{ - Address as GrpcAddress, GetUserTierInfoReply, GetUserTierInfoRequest, RlnProofFilter, - RlnProofReply, SendTransactionReply, SendTransactionRequest, U256 as GrpcU256, Wei as GrpcWei, - rln_prover_client::RlnProverClient, -}; -/* -async fn register_users(port: u16, addresses: Vec

) { - let url = format!("http://127.0.0.1:{}", port); - let mut client = RlnProverClient::connect(url).await.unwrap(); - - for address in addresses { - let addr = GrpcAddress { - value: address.to_vec(), - }; - - let request_0 = RegisterUserRequest { user: Some(addr) }; - let request = tonic::Request::new(request_0); - let response: Response = client.register_user(request).await.unwrap(); - - assert_eq!( - RegistrationStatus::try_from(response.into_inner().status).unwrap(), - RegistrationStatus::Success - ); - } -} -*/ - -async fn query_user_info(port: u16, addresses: Vec
) -> Vec { - let url = format!("http://127.0.0.1:{port}"); - let mut client = RlnProverClient::connect(url).await.unwrap(); - - let mut result = vec![]; - for address in addresses { - let addr = GrpcAddress { - value: address.to_vec(), - }; - let request_0 = GetUserTierInfoRequest { user: Some(addr) }; - let request = tonic::Request::new(request_0); - let resp: Response = - client.get_user_tier_info(request).await.unwrap(); - - result.push(resp.into_inner()); - } - - result -} - -/* -#[tokio::test] -#[traced_test] -async fn test_grpc_register_users() { - let addresses = vec![ - Address::from_str("0xd8da6bf26964af9d7eed9e03e53415d37aa96045").unwrap(), - Address::from_str("0xb20a608c624Ca5003905aA834De7156C68b2E1d0").unwrap(), - ]; - - let temp_folder = tempfile::tempdir().unwrap(); - let temp_folder_tree = tempfile::tempdir().unwrap(); - - let port = 50051; - let app_args = AppArgs { - ip: IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), - port, - ws_rpc_url: None, - db_path: temp_folder.path().to_path_buf(), - merkle_tree_path: temp_folder_tree.path().to_path_buf(), - ksc_address: None, - rlnsc_address: None, - tsc_address: None, - mock_sc: Some(true), - mock_user: None, - config_path: Default::default(), - no_config: Some(true), - metrics_ip: IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), - metrics_port: 30031, - broadcast_channel_size: 100, - proof_service_count: 16, - transaction_channel_size: 100, - proof_sender_channel_size: 100, - }; - - info!("Starting prover..."); - let prover_handle = task::spawn(run_prover(app_args)); - // Wait for the prover to be ready - // Note: if unit test is failing - maybe add an optional notification when service is ready - tokio::time::sleep(Duration::from_secs(5)).await; - info!("Registering some users..."); - register_users(port, addresses.clone()).await; - info!("Query info for these new users..."); - let res = query_user_info(port, addresses.clone()).await; - assert_eq!(res.len(), addresses.len()); - info!("Aborting prover..."); - prover_handle.abort(); - tokio::time::sleep(Duration::from_secs(1)).await; -} -*/ - -#[derive(Default)] -struct TxData { - chain_id: Option, - gas_price: Option, - estimated_gas_used: Option, -} - -async fn proof_sender(port: u16, addresses: Vec
, proof_count: usize, tx_data: TxData) { - let start = std::time::Instant::now(); - - let url = format!("http://127.0.0.1:{port}"); - let mut client = RlnProverClient::connect(url).await.unwrap(); - - let addr = GrpcAddress { - value: addresses[0].to_vec(), - }; - let chain_id = GrpcU256 { - value: tx_data - .chain_id - .unwrap_or(U256::from(1)) - .to_le_bytes::<32>() - .to_vec(), - }; - - let wei = GrpcWei { - value: tx_data - .gas_price - .unwrap_or(U256::from(1_000)) - .to_le_bytes::<32>() - .to_vec(), - }; - - let estimated_gas_used = tx_data.estimated_gas_used.unwrap_or(1_000); - - let mut count = 0; - for i in 0..proof_count { - let tx_hash = U256::from(42 + i).to_le_bytes::<32>().to_vec(); - - let request_0 = SendTransactionRequest { - gas_price: Some(wei.clone()), - sender: Some(addr.clone()), - chain_id: Some(chain_id.clone()), - transaction_hash: tx_hash, - estimated_gas_used, - }; - - let request = tonic::Request::new(request_0); - let response: Response = - client.send_transaction(request).await.unwrap(); - assert!(response.into_inner().result); - count += 1; - } - - println!( - "[proof_sender] sent {} tx - elapsed: {} secs", - count, - start.elapsed().as_secs_f64() - ); -} - -async fn proof_collector(port: u16, proof_count: usize) -> Vec { - let start = std::time::Instant::now(); - let result = Arc::new(RwLock::new(vec![])); - - let url = format!("http://127.0.0.1:{port}"); - let mut client = RlnProverClient::connect(url).await.unwrap(); - - let request_0 = RlnProofFilter { address: None }; - - let request = tonic::Request::new(request_0); - let stream_ = client.get_proofs(request).await.unwrap(); - - let mut stream = stream_.into_inner(); - - let result_2 = result.clone(); - let mut count = 0; - let mut start_per_message = std::time::Instant::now(); - let receiver = async move { - while let Some(response) = stream.message().await.unwrap() { - result_2.write().push(response); - count += 1; - if count >= proof_count { - break; - } - println!( - "count {count} - elapsed: {} secs", - start_per_message.elapsed().as_secs_f64() - ); - start_per_message = std::time::Instant::now(); - } - }; - - let _res = tokio::time::timeout(Duration::from_secs(500), receiver).await; - println!("_res: {_res:?}"); - let res = std::mem::take(&mut *result.write()); - println!( - "[proof_collector] elapsed: {} secs", - start.elapsed().as_secs_f64() - ); - res -} - -#[tokio::test] -// #[traced_test] -async fn test_grpc_gen_proof() { - let mock_users = vec![ - MockUser { - address: Address::from_str("0xd8da6bf26964af9d7eed9e03e53415d37aa96045").unwrap(), - tx_count: 0, - }, - MockUser { - address: Address::from_str("0xb20a608c624Ca5003905aA834De7156C68b2E1d0").unwrap(), - tx_count: 0, - }, - ]; - let addresses: Vec
= mock_users.iter().map(|u| u.address).collect(); - - // Write mock users to tempfile - let mock_users_as_str = serde_json::to_string(&mock_users).unwrap(); - let mut temp_file = NamedTempFile::new().unwrap(); - let temp_file_path = temp_file.path().to_path_buf(); - temp_file.write_all(mock_users_as_str.as_bytes()).unwrap(); - temp_file.flush().unwrap(); - debug!( - "Mock user temp file path: {}", - temp_file_path.to_str().unwrap() - ); - // - - let temp_folder = tempfile::tempdir().unwrap(); - let temp_folder_tree = tempfile::tempdir().unwrap(); - - let port = 50052; - let app_args = AppArgs { - ip: IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), - port, - ws_rpc_url: None, - db_path: temp_folder.path().to_path_buf(), - merkle_tree_folder: temp_folder_tree.path().to_path_buf(), - merkle_tree_count: 1, - merkle_tree_max_count: 1, - ksc_address: None, - rlnsc_address: None, - tsc_address: None, - mock_sc: Some(true), - mock_user: Some(temp_file_path), - config_path: Default::default(), - no_config: true, - metrics_ip: IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), - metrics_port: 30031, - broadcast_channel_size: 500, - proof_service_count: 8, - transaction_channel_size: 500, - proof_sender_channel_size: 500, - registration_min_amount: AppArgs::default_minimal_amount_for_registration(), - rln_identifier: AppArgs::default_rln_identifier_name(), - spam_limit: AppArgs::default_spam_limit(), - no_grpc_reflection: true, - tx_gas_quota: AppArgs::default_tx_gas_quota(), - }; - - info!("Starting prover with args: {:?}", app_args); - let prover_handle = task::spawn(run_prover(app_args)); - // Wait for the prover to be ready - // Note: if unit test is failing - maybe add an optional notification when service is ready - tokio::time::sleep(Duration::from_secs(5)).await; - // info!("Registering some users..."); - // register_users(port, addresses.clone()).await; - info!("Query info for these new users..."); - let res = query_user_info(port, addresses.clone()).await; - assert_eq!(res.len(), addresses.len()); - - info!("Sending tx and collecting proofs..."); - let proof_count = 10; - let mut set = JoinSet::new(); - set.spawn( - proof_sender(port, addresses.clone(), proof_count, Default::default()).map(|_| vec![]), // JoinSet require having the same return type - ); - set.spawn(proof_collector(port, proof_count)); - let res = set.join_all().await; - - println!("res lengths: {} {}", res[0].len(), res[1].len()); - assert_eq!(res[0].len() + res[1].len(), proof_count); - - info!("Aborting prover..."); - prover_handle.abort(); - tokio::time::sleep(Duration::from_secs(1)).await; -} - -async fn proof_sender_2(port: u16, addresses: Vec
, proof_count: usize) { - let start = std::time::Instant::now(); - - let chain_id = GrpcU256 { - // FIXME: LE or BE? - value: U256::from(1).to_le_bytes::<32>().to_vec(), - }; - - let url = format!("http://127.0.0.1:{port}"); - let mut client = RlnProverClient::connect(url).await.unwrap(); - - let addr = GrpcAddress { - value: addresses[0].to_vec(), - }; - let wei = GrpcWei { - // FIXME: LE or BE? - value: U256::from(1000).to_le_bytes::<32>().to_vec(), - }; - - let mut count = 0; - for i in 0..proof_count { - let tx_hash = U256::from(42 + i).to_le_bytes::<32>().to_vec(); - - let request_0 = SendTransactionRequest { - gas_price: Some(wei.clone()), - sender: Some(addr.clone()), - chain_id: Some(chain_id.clone()), - transaction_hash: tx_hash, - estimated_gas_used: 1_000, - }; - - let request = tonic::Request::new(request_0); - let response = client.send_transaction(request).await; - // assert!(response.into_inner().result); - - if response.is_err() { - println!("Error sending tx: {:?}", response.err()); - break; - } - - count += 1; - } - - println!( - "[proof_sender] sent {} tx - elapsed: {} secs", - count, - start.elapsed().as_secs_f64() - ); -} - -#[tokio::test] -// #[traced_test] -async fn test_grpc_user_spamming() { - let mock_users = vec![ - MockUser { - address: Address::from_str("0xd8da6bf26964af9d7eed9e03e53415d37aa96045").unwrap(), - tx_count: 0, - }, - MockUser { - address: Address::from_str("0xb20a608c624Ca5003905aA834De7156C68b2E1d0").unwrap(), - tx_count: 0, - }, - ]; - let addresses: Vec
= mock_users.iter().map(|u| u.address).collect(); - - // Write mock users to tempfile - let mock_users_as_str = serde_json::to_string(&mock_users).unwrap(); - let mut temp_file = NamedTempFile::new().unwrap(); - let temp_file_path = temp_file.path().to_path_buf(); - temp_file.write_all(mock_users_as_str.as_bytes()).unwrap(); - temp_file.flush().unwrap(); - debug!( - "Mock user temp file path: {}", - temp_file_path.to_str().unwrap() - ); - // - - let temp_folder = tempfile::tempdir().unwrap(); - let temp_folder_tree = tempfile::tempdir().unwrap(); - - let port = 50053; - let app_args = AppArgs { - ip: IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), - port, - ws_rpc_url: None, - db_path: temp_folder.path().to_path_buf(), - merkle_tree_folder: temp_folder_tree.path().to_path_buf(), - merkle_tree_count: 1, - merkle_tree_max_count: 1, - ksc_address: None, - rlnsc_address: None, - tsc_address: None, - mock_sc: Some(true), - mock_user: Some(temp_file_path), - config_path: Default::default(), - no_config: true, - metrics_ip: IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), - metrics_port: 30031, - broadcast_channel_size: 500, - proof_service_count: 8, - transaction_channel_size: 500, - proof_sender_channel_size: 500, - registration_min_amount: AppArgs::default_minimal_amount_for_registration(), - rln_identifier: AppArgs::default_rln_identifier_name(), - spam_limit: 3, - no_grpc_reflection: true, - tx_gas_quota: NonZeroU64::new(1_000).unwrap(), - }; - - info!("Starting prover with args: {:?}", app_args); - let prover_handle = task::spawn(run_prover(app_args)); - // Wait for the prover to be ready - // Note: if unit test is failing - maybe add an optional notification when service is ready - tokio::time::sleep(Duration::from_secs(5)).await; - // info!("Registering some users..."); - // register_users(port, addresses.clone()).await; - info!("Query info for these new users..."); - let res = query_user_info(port, addresses.clone()).await; - assert_eq!(res.len(), addresses.len()); - - info!("Sending tx and collecting proofs..."); - let proof_count = 10; - let mut set = JoinSet::new(); - set.spawn( - proof_sender_2(port, addresses.clone(), proof_count).map(|_| vec![]), // JoinSet require having the same return type - ); - set.spawn(proof_collector(port, 2 + 1)); - let res = set.join_all().await; - - println!("res lengths: {} {}", res[0].len(), res[1].len()); - /* - assert_eq!(res[0].len() + res[1].len(), proof_count); - */ - - info!("Aborting prover..."); - prover_handle.abort(); - tokio::time::sleep(Duration::from_secs(1)).await; -} - -#[tokio::test] -// #[traced_test] -async fn test_grpc_tx_exceed_gas_quota() { - let mock_users = vec![ - MockUser { - address: Address::from_str("0xd8da6bf26964af9d7eed9e03e53415d37aa96045").unwrap(), - tx_count: 0, - }, - MockUser { - address: Address::from_str("0xb20a608c624Ca5003905aA834De7156C68b2E1d0").unwrap(), - tx_count: 0, - }, - ]; - let addresses: Vec
= mock_users.iter().map(|u| u.address).collect(); - - // Write mock users to tempfile - let mock_users_as_str = serde_json::to_string(&mock_users).unwrap(); - let mut temp_file = NamedTempFile::new().unwrap(); - let temp_file_path = temp_file.path().to_path_buf(); - temp_file.write_all(mock_users_as_str.as_bytes()).unwrap(); - temp_file.flush().unwrap(); - debug!( - "Mock user temp file path: {}", - temp_file_path.to_str().unwrap() - ); - // - - let temp_folder = tempfile::tempdir().unwrap(); - let temp_folder_tree = tempfile::tempdir().unwrap(); - - let port = 50054; - let tx_gas_quota = NonZeroU64::new(1_000).unwrap(); - let app_args = AppArgs { - ip: IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), - port, - ws_rpc_url: None, - db_path: temp_folder.path().to_path_buf(), - merkle_tree_folder: temp_folder_tree.path().to_path_buf(), - merkle_tree_count: 1, - merkle_tree_max_count: 1, - ksc_address: None, - rlnsc_address: None, - tsc_address: None, - mock_sc: Some(true), - mock_user: Some(temp_file_path), - config_path: Default::default(), - no_config: true, - metrics_ip: IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), - metrics_port: 30031, - broadcast_channel_size: 500, - proof_service_count: 8, - transaction_channel_size: 500, - proof_sender_channel_size: 500, - registration_min_amount: AppArgs::default_minimal_amount_for_registration(), - rln_identifier: AppArgs::default_rln_identifier_name(), - spam_limit: AppArgs::default_spam_limit(), - no_grpc_reflection: true, - tx_gas_quota, - }; - - info!("Starting prover with args: {:?}", app_args); - let _prover_handle = task::spawn(run_prover(app_args)); - // Wait for the prover to be ready - // Note: if unit test is failing - maybe add an optional notification when service is ready - tokio::time::sleep(Duration::from_secs(5)).await; - - let quota_mult = 11; - let tx_data = TxData { - estimated_gas_used: Some(tx_gas_quota.get() * quota_mult), - ..Default::default() - }; - // Send a tx with 11 * the tx_gas_quota - proof_sender(port, addresses.clone(), 1, tx_data).await; - - tokio::time::sleep(Duration::from_secs(5)).await; - let res = query_user_info(port, vec![addresses[0]]).await; - let resp = res[0].resp.as_ref().unwrap(); - match resp { - Resp::Res(r) => { - // Check the tx counter is updated to the right value - assert_eq!(r.tx_count, quota_mult); - } - Resp::Error(e) => { - panic!("Unexpected error {:?}", e); - } - } -} diff --git a/rln-prover/prover_db_entity/Cargo.toml b/rln-prover/prover_db_entity/Cargo.toml new file mode 100644 index 0000000000..5c74e6cf10 --- /dev/null +++ b/rln-prover/prover_db_entity/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "prover_db_entity" +version = "0.1.0" +edition = "2024" + +[lib] +name = "prover_db_entity" +path = "src/lib.rs" + +[dependencies] +serde.workspace = true + +[dependencies.sea-orm] +version = "2.0.0-rc.18" \ No newline at end of file diff --git a/rln-prover/prover_db_entity/src/lib.rs b/rln-prover/prover_db_entity/src/lib.rs new file mode 100644 index 0000000000..6bb9a52ac3 --- /dev/null +++ b/rln-prover/prover_db_entity/src/lib.rs @@ -0,0 +1,9 @@ +//! `SeaORM` Entity, @generated by sea-orm-codegen 2.0.0-rc.18 + +pub mod prelude; + +pub mod m_tree; +pub mod m_tree_config; +pub mod tier_limits; +pub mod tx_counter; +pub mod user; diff --git a/rln-prover/prover_db_entity/src/m_tree.rs b/rln-prover/prover_db_entity/src/m_tree.rs new file mode 100644 index 0000000000..50940ff643 --- /dev/null +++ b/rln-prover/prover_db_entity/src/m_tree.rs @@ -0,0 +1,21 @@ +//! `SeaORM` Entity, @generated by sea-orm-codegen 2.0.0-rc.18 + +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] +#[sea_orm(table_name = "m_tree")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i64, + #[sea_orm(unique_key = "m_tree_tree_index_index_in_tree_idx")] + pub tree_index: i16, + #[sea_orm(unique_key = "m_tree_tree_index_index_in_tree_idx")] + pub index_in_tree: i64, + #[sea_orm(column_type = "VarBinary(StringLen::None)")] + pub value: Vec, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation {} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/rln-prover/prover_db_entity/src/m_tree_config.rs b/rln-prover/prover_db_entity/src/m_tree_config.rs new file mode 100644 index 0000000000..09079e5ce1 --- /dev/null +++ b/rln-prover/prover_db_entity/src/m_tree_config.rs @@ -0,0 +1,19 @@ +//! `SeaORM` Entity, @generated by sea-orm-codegen 2.0.0-rc.18 + +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] +#[sea_orm(table_name = "m_tree_config")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i32, + #[sea_orm(unique)] + pub tree_index: i16, + pub depth: i64, + pub next_index: i64, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation {} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/rln-prover/prover_db_entity/src/prelude.rs b/rln-prover/prover_db_entity/src/prelude.rs new file mode 100644 index 0000000000..2c755a0d9a --- /dev/null +++ b/rln-prover/prover_db_entity/src/prelude.rs @@ -0,0 +1,7 @@ +//! `SeaORM` Entity, @generated by sea-orm-codegen 2.0.0-rc.18 + +pub use super::m_tree::Entity as MTree; +pub use super::m_tree_config::Entity as MTreeConfig; +pub use super::tier_limits::Entity as TierLimits; +pub use super::tx_counter::Entity as TxCounter; +pub use super::user::Entity as User; diff --git a/rln-prover/prover_db_entity/src/tier_limits.rs b/rln-prover/prover_db_entity/src/tier_limits.rs new file mode 100644 index 0000000000..a5dfcfa3c9 --- /dev/null +++ b/rln-prover/prover_db_entity/src/tier_limits.rs @@ -0,0 +1,18 @@ +//! `SeaORM` Entity, @generated by sea-orm-codegen 2.0.0-rc.18 + +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] +#[sea_orm(table_name = "tier_limits")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i64, + #[sea_orm(column_type = "Text", unique)] + pub name: String, + pub tier_limits: Option, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation {} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/rln-prover/prover_db_entity/src/tx_counter.rs b/rln-prover/prover_db_entity/src/tx_counter.rs new file mode 100644 index 0000000000..3a6b03d2eb --- /dev/null +++ b/rln-prover/prover_db_entity/src/tx_counter.rs @@ -0,0 +1,19 @@ +//! `SeaORM` Entity, @generated by sea-orm-codegen 2.0.0-rc.18 + +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] +#[sea_orm(table_name = "tx_counter")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i64, + #[sea_orm(column_type = "Text", unique)] + pub address: String, + pub epoch: i64, + pub epoch_counter: i64, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation {} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/rln-prover/prover_db_entity/src/user.rs b/rln-prover/prover_db_entity/src/user.rs new file mode 100644 index 0000000000..3a73cb7411 --- /dev/null +++ b/rln-prover/prover_db_entity/src/user.rs @@ -0,0 +1,20 @@ +//! `SeaORM` Entity, @generated by sea-orm-codegen 2.0.0-rc.18 + +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] +#[sea_orm(table_name = "user")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i64, + #[sea_orm(column_type = "Text", unique)] + pub address: String, + pub rln_id: Json, + pub tree_index: i64, + pub index_in_merkle_tree: i64, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation {} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/rln-prover/prover_db_migration/Cargo.toml b/rln-prover/prover_db_migration/Cargo.toml new file mode 100644 index 0000000000..858d7701a3 --- /dev/null +++ b/rln-prover/prover_db_migration/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "prover_db_migration" +version = "0.1.0" +edition = "2024" + +[lib] +name = "prover_db_migration" +path = "src/lib.rs" + +[dependencies] +tokio.workspace = true +sea-orm-migration.workspace = true diff --git a/rln-prover/prover_db_migration/src/lib.rs b/rln-prover/prover_db_migration/src/lib.rs new file mode 100644 index 0000000000..2fc1287195 --- /dev/null +++ b/rln-prover/prover_db_migration/src/lib.rs @@ -0,0 +1,12 @@ +pub use sea_orm_migration::prelude::*; + +mod m20251115_init; + +pub struct Migrator; + +#[async_trait::async_trait] +impl MigratorTrait for Migrator { + fn migrations() -> Vec> { + vec![Box::new(m20251115_init::Migration)] + } +} diff --git a/rln-prover/prover_db_migration/src/m20251115_init.rs b/rln-prover/prover_db_migration/src/m20251115_init.rs new file mode 100644 index 0000000000..dd7c16e3e3 --- /dev/null +++ b/rln-prover/prover_db_migration/src/m20251115_init.rs @@ -0,0 +1,189 @@ +use sea_orm_migration::{prelude::*, schema::*}; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .create_table( + Table::create() + .table(User::Table) + .col(big_pk_auto(User::Id)) + // TODO: address as binary + length limit (20 bytes) + .col(text(User::Address).unique_key()) + // TODO: save this as binary directly? or json only? + .col(json(User::RlnId)) + .col(big_unsigned(User::TreeIndex)) + .col(big_unsigned(User::IndexInMerkleTree)) + .to_owned(), + ) + .await?; + + manager + .create_table( + Table::create() + .table(TxCounter::Table) + .col(big_pk_auto(TxCounter::Id)) + // TODO: should be a foreign key to user table so we could drop user and tx_counter as well (cascade) + // TODO: address as binary + length limit (20 bytes) + .col(text(TxCounter::Address).unique_key()) + .col(big_integer(TxCounter::Epoch).default(0)) + .col(big_integer(TxCounter::EpochCounter).default(0)) + // .col(big_integer(TxCounter::EpochSlice).default(0)) + // .col(big_integer(TxCounter::EpochSliceCounter).default(0)) + .to_owned(), + ) + .await?; + + manager + .create_table( + Table::create() + .table(TierLimits::Table) + .col(big_pk_auto(TierLimits::Id)) + // TODO: Name limit + .col(text(TierLimits::Name).unique_key()) + .col(json_null(TierLimits::TierLimits)) + .to_owned(), + ) + .await?; + + // The merkle tree configurations + manager + .create_table( + Table::create() + .table(MTreeConfig::Table) + .col(pk_auto(MTreeConfig::Id)) + .col(small_unsigned(MTreeConfig::TreeIndex).unique_key()) + .col(big_integer(MTreeConfig::Depth)) + .col(big_integer(MTreeConfig::NextIndex)) + .to_owned(), + ) + .await?; + + // Table to store the merkle tree + // Each row represents a node in the tree + // TreeIndex is the index of the tree (we could have multiple merkle trees) + // IndexInTree is the index of the node in the current tree: depth & index + manager + .create_table( + Table::create() + .table(MTree::Table) + .col(big_pk_auto(MTree::Id)) + .col(small_unsigned(MTree::TreeIndex)) + .col(big_integer(MTree::IndexInTree)) + // TODO: var_binary + size limit + .col(blob(MTree::Value)) + .to_owned(), + ) + .await?; + + // Need tree_index & index_in_tree to be unique (avoid multiple rows with the same index) + manager + .create_index( + Index::create() + .table(MTree::Table) + .name("unique_tree_index_index_in_tree") + .col(MTree::TreeIndex) + .col(MTree::IndexInTree) + .unique() + .to_owned(), + ) + .await?; + + Ok(()) + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .drop_table(Table::drop().table(User::Table).if_exists().to_owned()) + .await?; + + manager + .drop_table(Table::drop().table(TxCounter::Table).if_exists().to_owned()) + .await?; + + manager + .drop_table( + Table::drop() + .table(TierLimits::Table) + .if_exists() + .to_owned(), + ) + .await?; + + manager + .drop_table( + Table::drop() + .table(MTreeConfig::Table) + .if_exists() + .to_owned(), + ) + .await?; + + manager + .drop_table(Table::drop().table(MTree::Table).if_exists().to_owned()) + .await?; + + manager + .drop_index( + Index::drop() + .table(MTree::Table) + .name("unique_tree_index_index_in_tree") + .if_exists() + .to_owned(), + ) + .await?; + + Ok(()) + } +} + +#[derive(DeriveIden)] +enum User { + Table, + Id, + Address, + RlnId, + TreeIndex, + IndexInMerkleTree, +} + +#[derive(DeriveIden)] +enum TxCounter { + Table, + Id, + Address, + Epoch, + // EpochSlice, + EpochCounter, + // EpochSliceCounter, +} + +#[allow(clippy::enum_variant_names)] +#[derive(DeriveIden)] +enum TierLimits { + Table, + Id, + Name, + TierLimits, +} + +#[derive(DeriveIden)] +enum MTree { + Table, + Id, + TreeIndex, + IndexInTree, + Value, +} + +#[derive(DeriveIden)] +enum MTreeConfig { + Table, + Id, + TreeIndex, + Depth, + NextIndex, +} diff --git a/rln-prover/prover_db_migration/src/main.rs b/rln-prover/prover_db_migration/src/main.rs new file mode 100644 index 0000000000..f9fc60780c --- /dev/null +++ b/rln-prover/prover_db_migration/src/main.rs @@ -0,0 +1,6 @@ +use sea_orm_migration::prelude::*; + +#[tokio::main] +async fn main() { + cli::run_cli(prover_db_migration::Migrator).await; +} diff --git a/rln-prover/prover_pmtree/Cargo.toml b/rln-prover/prover_pmtree/Cargo.toml new file mode 100644 index 0000000000..7edd14dd4e --- /dev/null +++ b/rln-prover/prover_pmtree/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "prover_pmtree" +version = "0.1.0" +edition = "2024" + +[dev-dependencies] +# hex-literal = "0.3.4" +# tiny-keccak = { version = "=2.0.2", features = ["keccak"] } +# sled = "=0.34.7" + +[dependencies] +rayon = { version = "1.10.0", optional = true } +# ark-serialize = { version = "0.5.0", default-features = false, optional = true } + +[features] +default = [] +parallel = [ + "rayon", + # "ark-serialize/parallel" +] diff --git a/rln-prover/prover_pmtree/src/database.rs b/rln-prover/prover_pmtree/src/database.rs new file mode 100644 index 0000000000..3ba6b799cb --- /dev/null +++ b/rln-prover/prover_pmtree/src/database.rs @@ -0,0 +1,31 @@ +use crate::*; + +/// Trait that must be implemented for a Database +pub trait Database { + /// Config for database. Default is necessary for a default() pmtree function + type Config: Default; + + /// Creates new instance of db + fn new(config: Self::Config) -> PmtreeResult + where + Self: Sized; + + /// Loades existing db (existence check required) + fn load(config: Self::Config) -> PmtreeResult + where + Self: Sized; + + /// Returns value from db by the key + fn get(&self, key: DBKey) -> PmtreeResult>; + + /// Puts the value to the db by the key + fn put(&mut self, key: DBKey, value: Value) -> PmtreeResult<()>; + + /// Puts the leaves batch to the db + fn put_batch(&mut self, subtree: impl IntoIterator) -> PmtreeResult<()>; + + /// Closes the db connection + fn close(&mut self) -> PmtreeResult<()>; + + // fn dump(&self); +} diff --git a/rln-prover/prover_pmtree/src/hasher.rs b/rln-prover/prover_pmtree/src/hasher.rs new file mode 100644 index 0000000000..376939001f --- /dev/null +++ b/rln-prover/prover_pmtree/src/hasher.rs @@ -0,0 +1,23 @@ +use crate::*; + +use std::fmt::Debug; + +/// Trait that must be implemented for Hash Function +pub trait Hasher { + /// Native type for the hash-function + type Fr: Copy + Eq + Default + Sync + Send + Debug; + + /// Serializes Self::Fr + fn serialize(value: Self::Fr) -> Value; + + /// Deserializes Self::Fr + fn deserialize(value: Value) -> Self::Fr; + + /// Outputs the default leaf (Fr::default()) + fn default_leaf() -> Self::Fr { + Self::Fr::default() + } + + /// Calculates hash-function + fn hash(input: &[Self::Fr]) -> Self::Fr; +} diff --git a/rln-prover/prover_pmtree/src/lib.rs b/rln-prover/prover_pmtree/src/lib.rs new file mode 100644 index 0000000000..0575ddae61 --- /dev/null +++ b/rln-prover/prover_pmtree/src/lib.rs @@ -0,0 +1,67 @@ +//! # pmtree +//! Persistent Merkle Tree in Rust +//! +//! ## How it stored +//! { (usize::MAX - 1) : depth } +//! { (usize::MAX) : next_index} +//! { Position (tuple - (depth, index), converted to DBKey) : Value} + +pub mod database; +pub mod hasher; +pub mod persistent_db; +pub mod tree; + +use std::fmt::{Debug, Display}; + +pub use database::Database; +pub use hasher::Hasher; +pub use tree::{MerkleProof, MerkleTree}; + +/// Denotes keys in a database +pub type DBKey = [u8; 8]; + +/// Denotes values in a database +pub type Value = Vec; + +/// Denotes pmtree Merkle tree errors +#[derive(Debug, Clone)] +pub enum TreeErrorKind { + MerkleTreeIsFull, + InvalidKey, + IndexOutOfBounds, + CustomError(String), +} + +/// Denotes pmtree database errors +#[derive(Debug, Clone)] +pub enum DatabaseErrorKind { + CannotLoadDatabase, + DatabaseExists, + CustomError(String), +} + +/// Denotes pmtree errors +#[derive(Debug, Clone)] +pub enum PmtreeErrorKind { + /// Error in database + DatabaseError(DatabaseErrorKind), + /// Error in tree + TreeError(TreeErrorKind), + /// Custom error + CustomError(String), +} + +impl Display for PmtreeErrorKind { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + PmtreeErrorKind::DatabaseError(e) => write!(f, "Database error: {e:?}"), + PmtreeErrorKind::TreeError(e) => write!(f, "Tree error: {e:?}"), + PmtreeErrorKind::CustomError(e) => write!(f, "Custom error: {e:?}"), + } + } +} + +impl std::error::Error for PmtreeErrorKind {} + +/// Custom `Result` type with custom `Error` type +pub type PmtreeResult = std::result::Result; diff --git a/rln-prover/prover_pmtree/src/persistent_db.rs b/rln-prover/prover_pmtree/src/persistent_db.rs new file mode 100644 index 0000000000..e2cebc615f --- /dev/null +++ b/rln-prover/prover_pmtree/src/persistent_db.rs @@ -0,0 +1,30 @@ +use crate::Value; +use crate::tree::Key; + +pub trait PersistentDatabase { + type Config; + // type Entity; + // type EntityConfig; + type Error; + + /// Creates new instance of db + fn new(config: Self::Config) -> Self; + + /// Puts the value to the db by the key + fn put_cfg(&mut self, key: &str, value: usize); + + /// Puts the value to the db by the key + fn put(&mut self, key: (usize, usize), value: Value); + + /// Puts the leaves batch to the db + fn put_batch<'a>(&mut self, subtree: impl IntoIterator); + + // async fn sync(&mut self) -> Result<(), Self::Error>; + fn fsync(&mut self) -> impl Future>; + + fn get(&self, key: (usize, usize)) -> impl Future, Self::Error>>; + + fn get_all(&self) -> impl Future, Self::Error>>; + + fn get_cfg(&self) -> impl Future, Self::Error>>; +} diff --git a/rln-prover/prover_pmtree/src/tree.rs b/rln-prover/prover_pmtree/src/tree.rs new file mode 100644 index 0000000000..26afdaedc1 --- /dev/null +++ b/rln-prover/prover_pmtree/src/tree.rs @@ -0,0 +1,490 @@ +use crate::*; + +use std::cmp::{max, min}; +use std::collections::HashMap; +use std::error::Error; +use std::marker::PhantomData; +use std::sync::{Arc, RwLock}; + +use crate::persistent_db::PersistentDatabase; + +#[cfg(feature = "parallel")] +use rayon; + +// db[DEPTH_KEY] = depth +const DEPTH_KEY: DBKey = (u64::MAX - 1).to_be_bytes(); + +// db[NEXT_INDEX_KEY] = next_index; +const NEXT_INDEX_KEY: DBKey = u64::MAX.to_be_bytes(); + +// Denotes keys (depth, index) in Merkle Tree. Can be converted to DBKey +// TODO! Think about using hashing for that +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct Key(pub usize, pub usize); +impl From for DBKey { + fn from(key: Key) -> Self { + let cantor_pairing = ((key.0 + key.1) * (key.0 + key.1 + 1) / 2 + key.1) as u64; + cantor_pairing.to_be_bytes() + } +} + +impl Key { + pub fn new(depth: usize, index: usize) -> Self { + Key(depth, index) + } +} + +/// The Merkle Tree structure +pub struct MerkleTree +where + // D: Database, + H: Hasher, +{ + pub db: D, + depth: usize, + next_index: usize, + cache: Vec, + root: H::Fr, + + persistent_db: PDB, + phantom: PhantomData, +} + +/// The Merkle proof structure +#[derive(Clone, PartialEq, Eq)] +pub struct MerkleProof(pub Vec<(H::Fr, u8)>); + +impl MerkleTree +where + D: Database, + H: Hasher, + PDB: PersistentDatabase, + E: Error + From + From, +{ + /// Creates new `MerkleTree` and store it to the specified path/db + pub async fn new( + depth: usize, + db_config: D::Config, + persistent_db_config: PDB::Config, + ) -> Result { + // Create new db instance + let mut db = D::new(db_config)?; + let mut persistent_db = PDB::new(persistent_db_config); + + // Insert depth val into db + let depth_val = depth.to_be_bytes().to_vec(); + db.put(DEPTH_KEY, depth_val)?; + persistent_db.put_cfg("depth", depth); + + // Insert next_index val into db + let next_index = 0usize; + let next_index_val = next_index.to_be_bytes().to_vec(); + db.put(NEXT_INDEX_KEY, next_index_val)?; + persistent_db.put_cfg("next_index", next_index); + + // Cache nodes + let mut cache = vec![H::default_leaf(); depth + 1]; + + // Initialize one branch of the `Merkle Tree` from bottom to top + cache[depth] = H::default_leaf(); + + let k = (depth, 0); + let v = H::serialize(cache[depth]); + db.put(Key(k.0, k.1).into(), v.clone())?; + persistent_db.put((k.0, k.1), v.clone()); + for i in (0..depth).rev() { + cache[i] = H::hash(&[cache[i + 1], cache[i + 1]]); + + let k = (i, 0); + let v = H::serialize(cache[i]); + db.put(Key(k.0, k.1).into(), v.clone())?; + persistent_db.put((k.0, k.1), v.clone()); + } + + let root = cache[0]; + + persistent_db.fsync().await?; + + // end + + Ok(Self { + db, + depth, + next_index, + cache, + root, + persistent_db, + phantom: Default::default(), + }) + } + + /// Loads existing Merkle Tree from the specified path/db + pub async fn load(db_config: D::Config, persistent_db_config: PDB::Config) -> Result { + let persistent_db = PDB::new(persistent_db_config); + + let root_ = persistent_db + .get((0, 0)) + .await? + .ok_or(PmtreeErrorKind::CustomError("Root not found".to_string()))?; + let root = H::deserialize(root_); + + let cfg = persistent_db + .get_cfg() + .await? + .ok_or(PmtreeErrorKind::CustomError( + "Pdb cfg not found".to_string(), + ))?; + + // FIXME: return iterator here? + let all_nodes = persistent_db.get_all().await?; + + let mut db = D::new(db_config)?; + + db.put_batch( + all_nodes + .into_iter() + .map(|(depth, index, v)| (Key(depth, index).into(), v)), + )?; + + // Load cache vec + let depth = cfg.0; + let mut cache = vec![H::default_leaf(); depth + 1]; + cache[depth] = H::default_leaf(); + for i in (0..depth).rev() { + cache[i] = H::hash(&[cache[i + 1], cache[i + 1]]); + } + + let res = Self { + db, + depth: cfg.0, + next_index: cfg.1, + cache, + root, + persistent_db, + phantom: Default::default(), + }; + + Ok(res) + } + + /// Closes the db connection + pub fn close(&mut self) -> PmtreeResult<()> { + self.db.close() + } + + /// Sets a leaf at the specified tree index + pub async fn set(&mut self, key: usize, leaf: H::Fr) -> Result<(), E> { + if key >= self.capacity() { + return Err(PmtreeErrorKind::TreeError(TreeErrorKind::IndexOutOfBounds).into()); + } + + let value = H::serialize(leaf); + self.db.put(Key(self.depth, key).into(), value.clone())?; + self.persistent_db.put((self.depth, key), value); + + self.recalculate_from(key)?; + + // Update next_index in memory + self.next_index = max(self.next_index, key + 1); + + // Update next_index in db + let next_index_val = self.next_index.to_be_bytes().to_vec(); + self.db.put(NEXT_INDEX_KEY, next_index_val)?; + + self.persistent_db.put_cfg("next_index", self.next_index); + self.persistent_db.fsync().await?; + + Ok(()) + } + + // Recalculates `Merkle Tree` from the specified key + fn recalculate_from(&mut self, key: usize) -> PmtreeResult<()> { + let mut depth = self.depth; + let mut i = key; + + loop { + let value = self.hash_couple(depth, i)?; + i >>= 1; + depth -= 1; + + let v = H::serialize(value); + self.db.put(Key(depth, i).into(), v.clone())?; + self.persistent_db.put((depth, i), v); + + if depth == 0 { + self.root = value; + break; + } + } + + Ok(()) + } + + // Hashes the correct couple for the key + fn hash_couple(&self, depth: usize, key: usize) -> PmtreeResult { + let b = key & !1; + + let elem_a = self.get_elem(Key(depth, b)); + let elem_b = self.get_elem(Key(depth, b + 1)); + Ok(H::hash(&[elem_a?, elem_b?])) + } + + // Returns elem by the key + pub fn get_elem(&self, key: Key) -> PmtreeResult { + let res = self + .db + .get(key.into())? + .map_or(self.cache[key.0], |value| H::deserialize(value)); + + Ok(res) + } + + /// Deletes a leaf at the `key` by setting it to its default value + pub async fn delete(&mut self, key: usize) -> Result<(), E> { + if key >= self.next_index { + return Err(PmtreeErrorKind::TreeError(TreeErrorKind::InvalidKey).into()); + } + + self.set(key, H::default_leaf()).await?; + + Ok(()) + } + + /// Inserts a leaf to the next available index + pub async fn update_next(&mut self, leaf: H::Fr) -> Result { + let next_index = self.next_index; + self.set(next_index, leaf).await?; + Ok(next_index) + } + + /// Batch insertion from starting index + pub async fn set_range>( + &mut self, + start: usize, + leaves: I, + ) -> Result<(), E> { + self.batch_insert( + Some(start), + leaves.into_iter().collect::>().as_slice(), + ) + .await + } + + /// Batch insertion, updates the tree in parallel. + pub async fn batch_insert(&mut self, start: Option, leaves: &[H::Fr]) -> Result<(), E> { + let start = start.unwrap_or(self.next_index); + let end = start + leaves.len(); + + if end > self.capacity() { + return Err(PmtreeErrorKind::TreeError(TreeErrorKind::MerkleTreeIsFull).into()); + } + + let mut subtree = HashMap::::new(); + + let root_key = Key(0, 0); + + subtree.insert(root_key, self.root); + self.fill_nodes(root_key, start, end, &mut subtree, leaves, start)?; + + let subtree = Arc::new(RwLock::new(subtree)); + + let root_val = Self::batch_recalculate(root_key, Arc::clone(&subtree), self.depth); + + let subtree = RwLock::into_inner(Arc::try_unwrap(subtree).unwrap()).unwrap(); + + let subtree_iter = subtree + .iter() + .map(|(key, value)| (key, H::serialize(*value))); + + self.db + .put_batch(subtree_iter.clone().map(|(k, v)| ((*k).into(), v)))?; + + // FIXME + self.persistent_db.put_batch(subtree_iter); + + // Update next_index value in db + if end > self.next_index { + self.next_index = end; + self.db + .put(NEXT_INDEX_KEY, self.next_index.to_be_bytes().to_vec())?; + self.persistent_db.put_cfg("next_index", self.next_index); + } + + // Update root value in memory + self.root = root_val; + + self.persistent_db.fsync().await?; + + Ok(()) + } + + // Fills hashmap subtree + fn fill_nodes( + &self, + key: Key, + start: usize, + end: usize, + subtree: &mut HashMap, + leaves: &[H::Fr], + from: usize, + ) -> PmtreeResult<()> { + if key.0 == self.depth { + if key.1 >= from { + subtree.insert(key, leaves[key.1 - from]); + } + return Ok(()); + } + + let left = Key(key.0 + 1, key.1 * 2); + let right = Key(key.0 + 1, key.1 * 2 + 1); + + println!("get elem (left): {:?}", left); + let left_val = self.get_elem(left)?; + println!("get elem (right): {:?}", right); + let right_val = self.get_elem(right)?; + + subtree.insert(left, left_val); + subtree.insert(right, right_val); + + let half = 1 << (self.depth - key.0 - 1); + + if start < half { + self.fill_nodes(left, start, min(end, half), subtree, leaves, from)?; + } + + if end > half { + self.fill_nodes(right, 0, end - half, subtree, leaves, from)?; + } + + Ok(()) + } + + // Recalculates tree in parallel (in-memory) + fn batch_recalculate( + key: Key, + subtree: Arc>>, + depth: usize, + ) -> H::Fr { + let left_child = Key(key.0 + 1, key.1 * 2); + let right_child = Key(key.0 + 1, key.1 * 2 + 1); + + if key.0 == depth || !subtree.read().unwrap().contains_key(&left_child) { + return *subtree.read().unwrap().get(&key).unwrap(); + } + + #[cfg(feature = "parallel")] + let (left, right) = rayon::join( + || Self::batch_recalculate(left_child, Arc::clone(&subtree), depth), + || Self::batch_recalculate(right_child, Arc::clone(&subtree), depth), + ); + + #[cfg(not(feature = "parallel"))] + let (left, right) = ( + Self::batch_recalculate(left_child, Arc::clone(&subtree), depth), + Self::batch_recalculate(right_child, Arc::clone(&subtree), depth), + ); + + let result = H::hash(&[left, right]); + + subtree.write().unwrap().insert(key, result); + + result + } + + /// Computes a Merkle proof for the leaf at the specified index + pub fn proof(&self, index: usize) -> PmtreeResult> { + if index >= self.capacity() { + return Err(PmtreeErrorKind::TreeError(TreeErrorKind::IndexOutOfBounds)); + } + + let mut witness = Vec::with_capacity(self.depth); + + let mut i = index; + let mut depth = self.depth; + while depth != 0 { + i ^= 1; + witness.push(( + self.get_elem(Key(depth, i))?, + (1 - (i & 1)).try_into().unwrap(), + )); + i >>= 1; + depth -= 1; + } + + Ok(MerkleProof(witness)) + } + + /// Verifies a Merkle proof with respect to the input leaf and the tree root + pub fn verify(&self, leaf: &H::Fr, witness: &MerkleProof) -> bool { + let expected_root = witness.compute_root_from(leaf); + self.root() == expected_root + } + + /// Returns the leaf by the key + pub fn get(&self, key: usize) -> PmtreeResult { + if key >= self.capacity() { + return Err(PmtreeErrorKind::TreeError(TreeErrorKind::IndexOutOfBounds)); + } + + self.get_elem(Key(self.depth, key)) + } + + /// Returns the root of the tree + pub fn root(&self) -> H::Fr { + self.root + } + + /// Returns the total number of leaves set + pub fn leaves_set(&self) -> usize { + self.next_index + } + + /// Returns the capacity of the tree, i.e. the maximum number of leaves + pub fn capacity(&self) -> usize { + 1 << self.depth + } + + /// Returns the depth of the tree + pub fn depth(&self) -> usize { + self.depth + } +} + +impl MerkleProof { + /// Computes the Merkle root by iteratively hashing specified Merkle proof with specified leaf + pub fn compute_root_from(&self, leaf: &H::Fr) -> H::Fr { + let mut acc = *leaf; + for w in self.0.iter() { + if w.1 == 0 { + acc = H::hash(&[acc, w.0]); + } else { + acc = H::hash(&[w.0, acc]); + } + } + + acc + } + + /// Computes the leaf index corresponding to a Merkle proof + pub fn leaf_index(&self) -> usize { + self.get_path_index() + .into_iter() + .rev() + .fold(0, |acc, digit| (acc << 1) + usize::from(digit)) + } + + /// Returns the path indexes forming a Merkle Proof + pub fn get_path_index(&self) -> Vec { + self.0.iter().map(|x| x.1).collect() + } + + /// Returns the path elements forming a Merkle proof + pub fn get_path_elements(&self) -> Vec { + self.0.iter().map(|x| x.0).collect() + } + + /// Returns the length of a Merkle proof + pub fn length(&self) -> usize { + self.0.len() + } +} diff --git a/rln-prover/prover_pmtree_db_impl/Cargo.toml b/rln-prover/prover_pmtree_db_impl/Cargo.toml new file mode 100644 index 0000000000..c29f437b4b --- /dev/null +++ b/rln-prover/prover_pmtree_db_impl/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "prover_merkle_tree" +version = "0.1.0" +edition = "2024" + +[dependencies] +thiserror.workspace = true +num-packer = "0.1.2" +prover_db_entity = { path = "../prover_db_entity" } +prover_pmtree = { path = "../prover_pmtree" } +sea-orm.workspace = true \ No newline at end of file diff --git a/rln-prover/prover_pmtree_db_impl/src/lib.rs b/rln-prover/prover_pmtree_db_impl/src/lib.rs new file mode 100644 index 0000000000..36fec95af9 --- /dev/null +++ b/rln-prover/prover_pmtree_db_impl/src/lib.rs @@ -0,0 +1,5 @@ +mod mem_db; +mod persist_db; + +pub use mem_db::{MemoryDb, MemoryDbConfig}; +pub use persist_db::{PersistentDb, PersistentDbConfig, PersistentDbError}; diff --git a/rln-prover/prover_pmtree_db_impl/src/mem_db.rs b/rln-prover/prover_pmtree_db_impl/src/mem_db.rs new file mode 100644 index 0000000000..5f4fb2bf46 --- /dev/null +++ b/rln-prover/prover_pmtree_db_impl/src/mem_db.rs @@ -0,0 +1,40 @@ +use prover_pmtree::Database as PmtreeDatabase; +use prover_pmtree::{DBKey, DatabaseErrorKind, PmtreeErrorKind, PmtreeResult, Value}; +use std::collections::HashMap; + +pub struct MemoryDb(HashMap); + +#[derive(Default)] +pub struct MemoryDbConfig; + +impl PmtreeDatabase for MemoryDb { + type Config = MemoryDbConfig; + + fn new(_db_config: MemoryDbConfig) -> PmtreeResult { + Ok(MemoryDb(HashMap::new())) + } + + fn load(_db_config: MemoryDbConfig) -> PmtreeResult { + Err(PmtreeErrorKind::DatabaseError( + DatabaseErrorKind::CannotLoadDatabase, + )) + } + + fn get(&self, key: DBKey) -> PmtreeResult> { + Ok(self.0.get(&key).cloned()) + } + + fn put(&mut self, key: DBKey, value: Value) -> PmtreeResult<()> { + self.0.insert(key, value); + Ok(()) + } + + fn put_batch(&mut self, subtree: impl IntoIterator) -> PmtreeResult<()> { + self.0.extend(subtree); + Ok(()) + } + + fn close(&mut self) -> PmtreeResult<()> { + Ok(()) + } +} diff --git a/rln-prover/prover_pmtree_db_impl/src/persist_db.rs b/rln-prover/prover_pmtree_db_impl/src/persist_db.rs new file mode 100644 index 0000000000..518adee846 --- /dev/null +++ b/rln-prover/prover_pmtree_db_impl/src/persist_db.rs @@ -0,0 +1,197 @@ +use std::collections::HashMap; +// third-party +use num_packer::U32Packer; +// use sea-orm +use sea_orm::{DatabaseConnection, DbErr, Set, sea_query::OnConflict}; +// sea-orm traits +use sea_orm::{ + ActiveModelTrait, ColumnTrait, EntityTrait, ExprTrait, IntoActiveModel, QueryFilter, + TransactionTrait, +}; +// internal - db +use prover_db_entity::{m_tree, m_tree_config}; +// internal +use prover_pmtree::{Value, persistent_db::PersistentDatabase, tree::Key}; + +#[derive(thiserror::Error, Debug)] +pub enum PersistentDbError { + #[error(transparent)] + Db(#[from] DbErr), + #[error("Invalid config")] + Config, +} + +#[derive(Clone, Debug)] +pub struct PersistentDbConfig { + pub db_conn: DatabaseConnection, + pub tree_index: i16, + pub insert_batch_size: usize, +} + +pub struct PersistentDb { + config: PersistentDbConfig, + put_cfg_store: HashMap, + put_store: Vec, +} + +impl PersistentDatabase for PersistentDb { + // Note - Limits : + // tree_index (i16) -> max 32k tree supported (if required to support more, use u16 serialized as i16) + // depth (u32) -> depth in prover == 20, so this can be reduced down to u8 + // index (u32) -> so max u32::MAX entries - large enough for tree of depth 20 + // if depth is reduced to u8 then index can be set to u56 + + type Config = PersistentDbConfig; + type Error = PersistentDbError; + + fn new(config: Self::Config) -> Self { + PersistentDb { + config, + put_cfg_store: Default::default(), + put_store: vec![], + } + } + + fn put_cfg(&mut self, key: &str, value: usize) { + // FIXME: add debug_assert! if key is not supported + self.put_cfg_store.insert(key.to_string(), value); + } + + fn put(&mut self, key: (usize, usize), value: Value) { + let index_in_tree = i64::pack_u32(key.0 as u32, key.1 as u32); + self.put_store.push(m_tree::ActiveModel { + tree_index: Set(self.config.tree_index), + index_in_tree: Set(index_in_tree), + value: Set(value), + ..Default::default() + }); + } + + fn put_batch<'a>(&mut self, subtree: impl IntoIterator) { + self.put_store.extend(subtree.into_iter().map(|(k, v)| { + // FIXME: factorize + let index_in_tree = i64::pack_u32(k.0 as u32, k.1 as u32); + m_tree::ActiveModel { + tree_index: Set(self.config.tree_index), + index_in_tree: Set(index_in_tree), + value: Set(v), + ..Default::default() + } + })); + } + + async fn fsync(&mut self) -> Result<(), Self::Error> { + let cfg_map = std::mem::take(&mut self.put_cfg_store); + let mut put_list = std::mem::take(&mut self.put_store); + + let txn = self.config.db_conn.begin().await?; + if !cfg_map.is_empty() { + let cfg_ = m_tree_config::Entity::find() + .filter( + ::Column::TreeIndex + .eq(self.config.tree_index), + ) + .one(&txn) + .await?; + + if let Some(cfg_) = cfg_ { + let mut cfg = cfg_.into_active_model(); + if let Some(cfg_value) = cfg_map.get("depth") { + // FIXME + cfg.depth = Set(*cfg_value as i64); + } + if let Some(cfg_value) = cfg_map.get("next_index") { + // FIXME + cfg.next_index = Set(*cfg_value as i64); + } + + cfg.update(&txn).await?; + } else { + // TODO: unwrap safe notes? + let cfg_depth = cfg_map.get("depth").unwrap(); + let cfg_next_index = cfg_map.get("next_index").unwrap(); + + let cfg = m_tree_config::ActiveModel { + tree_index: Set(self.config.tree_index), + depth: Set(*cfg_depth as i64), + next_index: Set(*cfg_next_index as i64), + ..Default::default() + }; + + cfg.insert(&txn).await?; + } + } + + // prepare on_conflict statement for insert_many + let on_conflict = OnConflict::columns([ + ::Column::TreeIndex, + ::Column::IndexInTree, + ]) + .update_column(::Column::Value) + .to_owned(); + + // Note: Postgres has a limit ~ 15k parameters so we need to batch insert + loop { + if put_list.is_empty() { + // No need to call insert_many with 0 items + break; + } else if put_list.len() < self.config.insert_batch_size { + // Final insert_many for remaining items + m_tree::Entity::insert_many::(put_list) + .on_conflict(on_conflict.clone()) + .exec(&txn) + .await?; + break; + } else { + m_tree::Entity::insert_many::( + put_list.drain(..self.config.insert_batch_size), + ) + .on_conflict(on_conflict.clone()) + .exec(&txn) + .await?; + } + } + + txn.commit().await?; + Ok(()) + } + + async fn get(&self, key: (usize, usize)) -> Result, Self::Error> { + let index_in_tree = i64::pack_u32(key.0 as u32, key.1 as u32); + let res = m_tree::Entity::find() + .filter( + ::Column::TreeIndex + .eq(self.config.tree_index) + .and(::Column::IndexInTree.eq(index_in_tree)), + ) + .one(&self.config.db_conn) + .await?; + + Ok(res.map(|m| m.value)) + } + + async fn get_all(&self) -> Result, Self::Error> { + Ok(m_tree::Entity::find() + .filter(::Column::TreeIndex.eq(self.config.tree_index)) + .all(&self.config.db_conn) + .await? + .into_iter() + .map(|m| { + let (depth, index) = i64::unpack_u32(&m.index_in_tree); + (depth as usize, index as usize, m.value) + }) + .collect()) + } + + async fn get_cfg(&self) -> Result, Self::Error> { + let res = m_tree_config::Entity::find() + .filter( + ::Column::TreeIndex + .eq(self.config.tree_index), + ) + .one(&self.config.db_conn) + .await?; + + Ok(res.map(|m| (m.depth as usize, m.next_index as usize))) + } +} diff --git a/rln-prover/rln_proof/Cargo.toml b/rln-prover/rln_proof/Cargo.toml index c0096e6481..5741a03d21 100644 --- a/rln-prover/rln_proof/Cargo.toml +++ b/rln-prover/rln_proof/Cargo.toml @@ -10,10 +10,12 @@ ark-bn254.workspace = true ark-relations.workspace = true ark-groth16.workspace = true ark-serialize.workspace = true +serde = { version = "1.0.228", features = ["derive"] } +prover_pmtree = { path = "../prover_pmtree" } -[dev-dependencies] -criterion.workspace = true +# [dev-dependencies] +# criterion.workspace = true -[[bench]] -name = "generate_proof" -harness = false +# [[bench]] +# name = "generate_proof" +# harness = false diff --git a/rln-prover/rln_proof/benches/generate_proof.rs b/rln-prover/rln_proof/benches/generate_proof.rs index 65a211e5fe..ae2b2a13a4 100644 --- a/rln-prover/rln_proof/benches/generate_proof.rs +++ b/rln-prover/rln_proof/benches/generate_proof.rs @@ -1,3 +1,4 @@ +/* use std::hint::black_box; // std use std::io::{Cursor, Write}; @@ -115,3 +116,4 @@ criterion_group! { targets = criterion_benchmark } criterion_main!(benches); +*/ diff --git a/rln-prover/rln_proof/src/lib.rs b/rln-prover/rln_proof/src/lib.rs index e93cc5f9fd..374a674331 100644 --- a/rln-prover/rln_proof/src/lib.rs +++ b/rln-prover/rln_proof/src/lib.rs @@ -1,6 +1,8 @@ mod proof; -pub use proof::{RlnData, RlnIdentifier, RlnUserIdentity, compute_rln_proof_and_values}; +pub use proof::{ + ProverPoseidonHash, RlnData, RlnIdentifier, RlnUserIdentity, compute_rln_proof_and_values, +}; // re export trait from zerokit utils crate (for prover) pub use zerokit_utils::merkle_tree::merkle_tree::ZerokitMerkleTree; diff --git a/rln-prover/rln_proof/src/proof.rs b/rln-prover/rln_proof/src/proof.rs index e3e288e21e..7a0f9a8dde 100644 --- a/rln-prover/rln_proof/src/proof.rs +++ b/rln-prover/rln_proof/src/proof.rs @@ -4,23 +4,30 @@ use ark_bn254::{Bn254, Fr}; use ark_groth16::{Proof, ProvingKey}; use ark_relations::r1cs::ConstraintMatrices; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use prover_pmtree::{Hasher, Value}; use rln::utils::IdSecret; use rln::{ circuit::zkey_from_folder, error::ProofError, hashers::{hash_to_field_le, poseidon_hash}, - poseidon_tree::MerkleProof, + // poseidon_tree::MerkleProof, protocol::{ RLNProofValues, generate_proof, proof_values_from_witness, rln_witness_from_values, }, }; -use zerokit_utils::ZerokitMerkleProof; +use serde::{Deserialize, Serialize}; +// internal +use prover_pmtree::tree::MerkleProof; /// A RLN user identity & limit -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct RlnUserIdentity { + #[serde(serialize_with = "ark_se", deserialize_with = "ark_de")] pub commitment: Fr, + #[serde(serialize_with = "ark_se", deserialize_with = "ark_de")] pub secret_hash: IdSecret, + #[serde(serialize_with = "ark_se", deserialize_with = "ark_de")] pub user_limit: Fr, } @@ -34,6 +41,26 @@ impl From<(Fr, IdSecret, Fr)> for RlnUserIdentity { } } +fn ark_se(a: &A, s: S) -> Result +where + S: serde::Serializer, +{ + // TODO: with_capacity? + let mut bytes = vec![]; + a.serialize_compressed(&mut bytes) + .map_err(serde::ser::Error::custom)?; + s.serialize_bytes(&bytes) +} + +fn ark_de<'de, D, A: CanonicalDeserialize>(data: D) -> Result +where + D: serde::de::Deserializer<'de>, +{ + let s: Vec = serde::de::Deserialize::deserialize(data)?; + let a = A::deserialize_compressed_unchecked(s.as_slice()); + a.map_err(serde::de::Error::custom) +} + /// RLN info for a channel / group #[derive(Debug, Clone)] pub struct RlnIdentifier { @@ -74,7 +101,7 @@ pub fn compute_rln_proof_and_values( rln_identifier: &RlnIdentifier, rln_data: RlnData, epoch: Fr, - merkle_proof: &MerkleProof, + merkle_proof: &MerkleProof, ) -> Result<(Proof, RLNProofValues), ProofError> { let external_nullifier = poseidon_hash(&[rln_identifier.identifier, epoch]); @@ -102,13 +129,41 @@ pub fn compute_rln_proof_and_values( Ok((proof, proof_values)) } +#[derive(Clone, Copy, PartialEq, Eq)] +pub struct ProverPoseidonHash; + +impl Hasher for ProverPoseidonHash { + type Fr = Fr; + + fn serialize(value: Self::Fr) -> Value { + let mut buffer = vec![]; + // FIXME: unwrap safe? + value.serialize_compressed(&mut buffer).unwrap(); + buffer + } + + fn deserialize(value: Value) -> Self::Fr { + // FIXME: unwrap safe? + CanonicalDeserialize::deserialize_compressed(value.as_slice()).unwrap() + } + + fn default_leaf() -> Self::Fr { + Self::Fr::from(0) + } + fn hash(inputs: &[Self::Fr]) -> Self::Fr { + poseidon_hash(inputs) + } +} + #[cfg(test)] mod tests { - use super::*; - use rln::poseidon_tree::PoseidonTree; - use rln::protocol::{compute_id_secret, keygen}; - use zerokit_utils::ZerokitMerkleTree; + // use super::*; + // use rln::poseidon_tree::PoseidonTree; + // use rln::protocol::{compute_id_secret, keygen}; + // use zerokit_utils::ZerokitMerkleTree; + // FIXME + /* #[test] fn test_recover_secret_hash() { let (user_co, mut user_sh_) = keygen(); @@ -162,4 +217,5 @@ mod tests { let recovered_identity_secret_hash = compute_id_secret(share1, share2).unwrap(); assert_eq!(user_sh, recovered_identity_secret_hash); } + */ } diff --git a/rln-prover/smart_contract/Cargo.toml b/rln-prover/smart_contract/Cargo.toml index 49695b24d1..b5d35bd8b1 100644 --- a/rln-prover/smart_contract/Cargo.toml +++ b/rln-prover/smart_contract/Cargo.toml @@ -24,6 +24,7 @@ async-trait.workspace = true thiserror.workspace = true rustls.workspace = true log = "0.4.28" +serde = { version = "1.0.228", features = ["derive"] } [dev-dependencies] claims = "0.8" diff --git a/rln-prover/smart_contract/src/karma_tiers.rs b/rln-prover/smart_contract/src/karma_tiers.rs index 84a71ff926..f3455fe509 100644 --- a/rln-prover/smart_contract/src/karma_tiers.rs +++ b/rln-prover/smart_contract/src/karma_tiers.rs @@ -6,6 +6,7 @@ use alloy::{ sol, transports::{RpcError, TransportErrorKind}, }; +use serde::{Deserialize, Serialize}; // internal // use crate::common::AlloyWsProvider; @@ -235,7 +236,7 @@ impl KarmaTiers::KarmaTiersInstance

{ } } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct Tier { pub min_karma: U256, pub max_karma: U256,