diff --git a/Cargo.lock b/Cargo.lock index 7438395..a725402 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,17 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +[[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 = "anstream" version = "0.6.18" @@ -67,6 +78,24 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "async-channel" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + [[package]] name = "async-trait" version = "0.1.88" @@ -99,6 +128,12 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + [[package]] name = "base64" version = "0.21.7" @@ -125,11 +160,17 @@ checksum = "1945a5048598e4189e239d3f809b19bdad4845c4b2ba400d304d2dcf26d2c462" dependencies = [ "bech32", "bitcoin-private", - "bitcoin_hashes", + "bitcoin_hashes 0.12.0", "hex_lit", - "secp256k1", + "secp256k1 0.27.0", ] +[[package]] +name = "bitcoin-io" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b47c4ab7a93edb0c7198c5535ed9b52b63095f4e9b45279c6736cec4b856baf" + [[package]] name = "bitcoin-private" version = "0.1.0" @@ -145,6 +186,16 @@ dependencies = [ "bitcoin-private", ] +[[package]] +name = "bitcoin_hashes" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb18c03d0db0247e147a21a6faafd5a7eb851c743db062de72018b6b7e8e4d16" +dependencies = [ + "bitcoin-io", + "hex-conservative", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -255,6 +306,34 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "config" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23738e11972c7643e4ec947840fc463b6a571afcd3e735bdfce7d03c7a784aca" +dependencies = [ + "async-trait", + "json5", + "lazy_static", + "nom", + "pathdiff", + "ron", + "rust-ini", + "serde", + "serde_json", + "toml", + "yaml-rust", +] + [[package]] name = "console" version = "0.15.11" @@ -293,34 +372,6 @@ dependencies = [ "libc", ] -[[package]] -name = "crossbeam-channel" -version = "0.5.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-deque" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" -dependencies = [ - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" -dependencies = [ - "crossbeam-utils", -] - [[package]] name = "crossbeam-utils" version = "0.8.21" @@ -339,23 +390,27 @@ dependencies = [ [[package]] name = "cryptohunter" -version = "0.1.6" +version = "0.1.7" dependencies = [ + "async-channel", + "async-trait", "bitcoin", + "bytes", "clap", - "crossbeam-channel", + "config", "csv", + "deadpool-postgres", + "futures", + "hex", "indicatif", - "lazy_static", "log", - "postgres", "rand 0.8.5", - "rayon", "reqwest", + "secp256k1 0.31.0", "serde", - "serde_yaml", "simple_logger", "tokio", + "tokio-postgres", ] [[package]] @@ -379,6 +434,40 @@ dependencies = [ "memchr", ] +[[package]] +name = "deadpool" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ed5957ff93768adf7a65ab167a17835c3d2c3c50d084fe305174c112f468e2f" +dependencies = [ + "deadpool-runtime", + "num_cpus", + "tokio", +] + +[[package]] +name = "deadpool-postgres" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d697d376cbfa018c23eb4caab1fd1883dd9c906a8c034e8d9a3cb06a7e0bef9" +dependencies = [ + "async-trait", + "deadpool", + "getrandom 0.2.16", + "tokio", + "tokio-postgres", + "tracing", +] + +[[package]] +name = "deadpool-runtime" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "092966b41edc516079bdf31ec78a2e0588d1d0c08f78b91d8307215928642b2b" +dependencies = [ + "tokio", +] + [[package]] name = "deranged" version = "0.4.0" @@ -411,10 +500,10 @@ dependencies = [ ] [[package]] -name = "either" -version = "1.15.0" +name = "dlv-list" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257" [[package]] name = "encode_unicode" @@ -447,6 +536,27 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "event-listener" +version = "5.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener", + "pin-project-lite", +] + [[package]] name = "fallible-iterator" version = "0.2.0" @@ -489,6 +599,21 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + [[package]] name = "futures-channel" version = "0.3.31" @@ -505,6 +630,17 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + [[package]] name = "futures-io" version = "0.3.31" @@ -540,6 +676,7 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ + "futures-channel", "futures-core", "futures-io", "futures-macro", @@ -568,8 +705,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi 0.11.0+wasi-snapshot-preview1", + "wasm-bindgen", ] [[package]] @@ -609,6 +748,15 @@ dependencies = [ "tracing", ] +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash", +] + [[package]] name = "hashbrown" version = "0.15.3" @@ -621,6 +769,27 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hermit-abi" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f154ce46856750ed433c8649605bf7ed2de3bc35fd9d2a9f30cddd873c80cb08" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hex-conservative" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5313b072ce3c597065a808dbf612c4c8e8590bdbf8b579508bf7a762c5eae6cd" +dependencies = [ + "arrayvec", +] + [[package]] name = "hex_lit" version = "0.1.1" @@ -821,7 +990,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.15.3", ] [[package]] @@ -865,6 +1034,17 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "json5" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b0db21af676c1ce64250b5f40f3ce2cf27e4e47cb91ed91eb6fe9350b430c1" +dependencies = [ + "pest", + "pest_derive", + "serde", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -877,6 +1057,12 @@ version = "0.2.172" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + [[package]] name = "linux-raw-sys" version = "0.9.4" @@ -927,6 +1113,12 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.8.8" @@ -964,12 +1156,32 @@ dependencies = [ "tempfile", ] +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "num-conv" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +[[package]] +name = "num_cpus" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" +dependencies = [ + "hermit-abi", + "libc", +] + [[package]] name = "num_threads" version = "0.1.7" @@ -1050,6 +1262,22 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "ordered-multimap" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccd746e37177e1711c20dd619a1620f34f5c8b569c53590a72dedd5344d8924a" +dependencies = [ + "dlv-list", + "hashbrown 0.12.3", +] + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + [[package]] name = "parking_lot" version = "0.12.4" @@ -1073,12 +1301,63 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "pathdiff" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" + [[package]] name = "percent-encoding" version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "pest" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "198db74531d58c70a361c42201efde7e2591e976d518caf7662a47dc5720e7b6" +dependencies = [ + "memchr", + "thiserror", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d725d9cfd79e87dccc9341a2ef39d1b6f6353d68c4b33c177febbe1a402c97c5" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db7d01726be8ab66ab32f9df467ae8b1148906685bbe75c82d1e65d7f5b3f841" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pest_meta" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f9f832470494906d1fca5329f8ab5791cc60beb230c74815dff541cbd2b5ca0" +dependencies = [ + "once_cell", + "pest", + "sha2", +] + [[package]] name = "phf" version = "0.11.3" @@ -1121,20 +1400,6 @@ version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" -[[package]] -name = "postgres" -version = "0.19.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "363e6dfbdd780d3aa3597b6eb430db76bb315fa9bad7fae595bb8def808b8470" -dependencies = [ - "bytes", - "fallible-iterator", - "futures-util", - "log", - "tokio", - "tokio-postgres", -] - [[package]] name = "postgres-protocol" version = "0.6.8" @@ -1271,26 +1536,6 @@ dependencies = [ "getrandom 0.3.3", ] -[[package]] -name = "rayon" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" -dependencies = [ - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" -dependencies = [ - "crossbeam-deque", - "crossbeam-utils", -] - [[package]] name = "redox_syscall" version = "0.5.12" @@ -1340,6 +1585,27 @@ dependencies = [ "winreg", ] +[[package]] +name = "ron" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88073939a61e5b7680558e6be56b419e208420c2adb92be54921fa6b72283f1a" +dependencies = [ + "base64 0.13.1", + "bitflags 1.3.2", + "serde", +] + +[[package]] +name = "rust-ini" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6d5f2436026b4f6e79dc829837d467cc7e9a55ee40e750d716713540715a2df" +dependencies = [ + "cfg-if", + "ordered-multimap", +] + [[package]] name = "rustc-demangle" version = "0.1.24" @@ -1401,9 +1667,20 @@ version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25996b82292a7a57ed3508f052cfff8640d38d32018784acd714758b43da9c8f" dependencies = [ - "bitcoin_hashes", + "bitcoin_hashes 0.12.0", "rand 0.8.5", - "secp256k1-sys", + "secp256k1-sys 0.8.1", +] + +[[package]] +name = "secp256k1" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a3dff2d01c9aa65c3186a45ff846bfea52cbe6de3b6320ed2a358d90dad0d76" +dependencies = [ + "bitcoin_hashes 0.14.0", + "rand 0.9.1", + "secp256k1-sys 0.11.0", ] [[package]] @@ -1415,6 +1692,15 @@ dependencies = [ "cc", ] +[[package]] +name = "secp256k1-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcb913707158fadaf0d8702c2db0e857de66eb003ccfdda5924b5f5ac98efb38" +dependencies = [ + "cc", +] + [[package]] name = "security-framework" version = "2.11.1" @@ -1482,19 +1768,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_yaml" -version = "0.9.34+deprecated" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" -dependencies = [ - "indexmap", - "itoa", - "ryu", - "serde", - "unsafe-libyaml", -] - [[package]] name = "sha2" version = "0.10.9" @@ -1655,6 +1928,26 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "time" version = "0.3.41" @@ -1791,6 +2084,15 @@ dependencies = [ "tokio", ] +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + [[package]] name = "tower-service" version = "0.3.3" @@ -1804,9 +2106,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "pin-project-lite", + "tracing-attributes", "tracing-core", ] +[[package]] +name = "tracing-attributes" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1ffbcf9c6f6b99d386e7444eb608ba646ae452a36b39737deb9663b610f662" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tracing-core" version = "0.1.33" @@ -1828,6 +2142,12 @@ version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" +[[package]] +name = "ucd-trie" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" + [[package]] name = "unicode-bidi" version = "0.3.18" @@ -1861,12 +2181,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" -[[package]] -name = "unsafe-libyaml" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" - [[package]] name = "url" version = "2.5.4" @@ -2207,6 +2521,15 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" +[[package]] +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] + [[package]] name = "yoke" version = "0.8.0" diff --git a/Cargo.toml b/Cargo.toml index eee725e..5442b3f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,18 +4,22 @@ version = "0.1.7" edition = "2024" [dependencies] -serde = { version = "1.0", features = ["derive"] } -serde_yaml = "0.9" +futures = "0.3.31" +bytes = "1.0" clap = { version = "4.0", features = ["derive"] } -postgres = "0.19" +serde = { version = "1.0", features = ["derive"] } +config = { version = "0.13", features = ["yaml", "json"] } +async-channel = "2.1" rand = "0.8" bitcoin = { version = "0.30", features = ["rand"] } -reqwest = { version = "0.11", features = ["blocking", "json"] } +secp256k1 = { version = "0.31.0", features = ["rand"] } +hex = "0.4.3" +async-trait = "0.1.88" tokio = { version = "1", features = ["full"] } +tokio-postgres = "0.7.13" +deadpool-postgres = "0.14.1" +reqwest = { version = "0.11", features = ["blocking", "json"] } csv = "1.1" -crossbeam-channel = "0.5" log = "0.4" simple_logger = "4.0" -lazy_static = "1.4" indicatif = "0.17" -rayon = "1.10.0" diff --git a/docker-compose.yaml b/docker-compose.yaml index 986ff20..e45f393 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -4,7 +4,7 @@ services: environment: - RUST_LOG=info volumes: - - ./config.yaml:/app/config.yaml + - ./settings.yaml:/app/settings.yaml - ./snapshots:/snapshots depends_on: database: @@ -15,7 +15,7 @@ services: image: postgres:16-alpine environment: POSTGRES_USER: cryptohunter - POSTGRES_PASSWORD: ${CRYPTOHUNTER_DATABASE_PASSWORD:-12345678} + POSTGRES_PASSWORD: ${CRYPTOHUNTER_DATABASE_PASSWORD:-Passw0rd} POSTGRES_DB: cryptohunter volumes: - database-data:/var/lib/postgresql/data diff --git a/src/blockchains/bitcoin.rs b/src/blockchains/bitcoin.rs new file mode 100644 index 0000000..3769dff --- /dev/null +++ b/src/blockchains/bitcoin.rs @@ -0,0 +1,235 @@ +use async_trait::async_trait; +use bitcoin::{ + secp256k1::Secp256k1, + secp256k1::SecretKey, + Address, + Network as BitcoinNetwork, + PrivateKey, + PublicKey, +}; +use bytes::Bytes; +use config::{Config as ConfigBuilder, Value}; +use deadpool_postgres; +use futures::{SinkExt, pin_mut}; +use hex; +use indicatif::{MultiProgress, ProgressBar, ProgressStyle}; +use serde::Deserialize; +use std::collections::HashMap; +use std::time::{Duration, Instant}; +use tokio::fs::File; +use tokio::io::{AsyncReadExt, BufReader}; + +use crate::key_generators::KeyAlgorithm; +use crate::utils::DynError; +use super::enums::Blockchain; +use super::traits::{SnapshotLoader, WalletChecker}; + +pub struct Bitcoin { + database_table: String, + pool: deadpool_postgres::Pool, +} + +#[derive(Deserialize)] +struct BitcoinParameters { + database_url: String, + #[serde(default = "default_database_table")] + database_table: String, + #[serde(default = "default_database_max_connections")] + database_max_connections: usize, +} + +fn default_database_table() -> String { + "bitcoin".into() +} + +fn default_database_max_connections() -> usize { + 20 +} + +impl Bitcoin { + pub fn new(data: &HashMap) -> Result { + let mut builder = ConfigBuilder::builder(); + for (key, value) in data { + builder = builder.set_override(key, value.clone())?; + } + let parameters: BitcoinParameters = builder.build()?.try_deserialize()?; + + let pg_config = parameters.database_url.parse::()?; + let mgr_config = deadpool_postgres::ManagerConfig { + recycling_method: deadpool_postgres::RecyclingMethod::Fast, + }; + let mgr = deadpool_postgres::Manager::from_config(pg_config, tokio_postgres::NoTls, mgr_config); + let pool = deadpool_postgres::Pool::builder(mgr) + .config(deadpool_postgres::PoolConfig::new(parameters.database_max_connections)) + .build()?; + + Ok(Self { + database_table: parameters.database_table, + pool, + }) + } + + async fn get_client(&self) -> Result { + self.pool.get().await.map_err(Into::into) + } +} + +#[async_trait] +impl WalletChecker for Bitcoin { + fn get_key_algorithm(&self) -> KeyAlgorithm { + KeyAlgorithm::Secp256k1 + } + + fn get_blockchain(&self) -> Blockchain { + Blockchain::Bitcoin + } + + async fn get_wallet_info(&self, key_hex: &str) -> Result, DynError> { + let bytes = hex::decode(key_hex)?; + let secret_key = SecretKey::from_slice(&bytes)?; + let secp = Secp256k1::new(); + let private_key = PrivateKey::new(secret_key, BitcoinNetwork::Bitcoin); + let public_key = PublicKey::from_private_key(&secp, &private_key); + let address = Address::p2pkh(&public_key, BitcoinNetwork::Bitcoin).to_string(); + + let client = self.get_client().await?; + let row = client.query_opt( + &format!("SELECT balance FROM {} WHERE address = $1", self.database_table), + &[&address], + ).await?; + let balance = row.map(|r| r.get(0)).unwrap_or(0); + if balance == 0 { + return Ok(None); + } + + Ok(Some(format!( + "**Blockchain**: Bitcoin\n**Address**: `{}`\n**Balance**: `{}`", + address, + balance, + ))) + } +} + +#[async_trait] +impl SnapshotLoader for Bitcoin { + fn get_blockchain(&self) -> Blockchain { + Blockchain::Bitcoin + } + + async fn load_snapshot(&self, snapshot_path: &str) -> Result<(), DynError> { + let start_time = Instant::now(); + let client = self.get_client().await?; + let file = File::open(snapshot_path).await?; + let file_metadata = file.metadata().await?; + let file_size = file_metadata.len(); + let index_name = format!("{}__address__ix", self.database_table); + let multi_progress = MultiProgress::new(); + + // 1. Preparing database + let prep_pb = multi_progress.add(ProgressBar::new_spinner()); + prep_pb.set_style( + ProgressStyle::default_spinner() + .tick_chars("⠁⠂⠄⡀⢀⠠⠐⠈ ") + .template("{spinner} {msg}").unwrap() + ); + prep_pb.set_message("⚙️ Preparing database..."); + prep_pb.enable_steady_tick(Duration::from_millis(100)); + + client.execute("SET synchronous_commit = off", &[]).await?; + client.execute("SET maintenance_work_mem = '4GB'", &[]).await?; + client.execute("SET work_mem = '2GB'", &[]).await?; + client.execute( + &format!("DROP TABLE IF EXISTS {}", self.database_table), + &[], + ).await?; + client.execute( + &format!("DROP INDEX IF EXISTS {}", index_name), + &[], + ).await?; + client.execute( + &format!( + "CREATE TABLE {} (address TEXT, balance BIGINT)", + self.database_table + ), + &[], + ).await?; + + prep_pb.finish_with_message("✅ Database prepared"); + + // 2. Copy data to database + let copy_pb = multi_progress.add(ProgressBar::new(file_size)); + copy_pb.set_style(ProgressStyle::default_bar() + .template("{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {bytes}/{total_bytes} ({eta}) | {binary_bytes_per_sec}") + .unwrap() + .progress_chars("#>-")); + copy_pb.set_message("️📥 Copy data to database..."); + + let copy_stmt = format!( + "COPY {} FROM STDIN WITH (FORMAT csv, DELIMITER E'\t', HEADER)", + self.database_table, + ); + let sink = client.copy_in(©_stmt).await?; + pin_mut!(sink); + let file = File::open(snapshot_path).await?; + let mut reader = BufReader::new(file); + let mut buffer = vec![0u8; 65536]; + + while let Ok(bytes_read) = reader.read(&mut buffer).await { + if bytes_read == 0 { break } + sink.as_mut() + .send(Bytes::copy_from_slice(&buffer[..bytes_read])) + .await?; + copy_pb.inc(bytes_read as u64); + } + sink.as_mut().close().await?; + copy_pb.set_position(file_size); + copy_pb.finish_with_message("✅ Data copied to database"); + + // 3. Creating index + let index_pb = multi_progress.add(ProgressBar::new_spinner()); + index_pb.set_message("🔀 Creating index..."); + index_pb.enable_steady_tick(Duration::from_millis(100)); + + client.execute( + &format!( + "CREATE INDEX {} ON {} USING HASH (address)", + index_name, + self.database_table, + ), + &[], + ).await?; + + index_pb.finish_with_message("✅ Index created"); + + // 4. Reseting temporary settings + let final_pb = multi_progress.add(ProgressBar::new_spinner()); + final_pb.set_message("🔁 Reset database settings"); + final_pb.enable_steady_tick(Duration::from_millis(100)); + + client.execute("RESET synchronous_commit", &[]).await?; + client.execute("RESET maintenance_work_mem", &[]).await?; + client.execute("RESET work_mem", &[]).await?; + + final_pb.finish_with_message("✅ Database settings reseted"); + + // 5. Final + multi_progress.clear()?; + + let records_count: i64 = client.query_one( + &format!( + "SELECT COUNT(*) FROM {}", + self.database_table, + ), + &[], + ).await?.get(0); + + let data_size: f64 = file_size as f64 / (1024 * 1024 * 1024) as f64; + println!("\n🎉 Snapshot loaded successfully!"); + println!("📊 Statistics:"); + println!(" Total records processed: {}", records_count); + println!(" Data size: {:.2} GB", data_size); + println!(" Execution time: {:?}", start_time.elapsed()); + + Ok(()) + } +} diff --git a/src/blockchains/enums.rs b/src/blockchains/enums.rs new file mode 100644 index 0000000..9e6942b --- /dev/null +++ b/src/blockchains/enums.rs @@ -0,0 +1,20 @@ +use clap; +use serde; +use std::fmt::{Display, Formatter, Result}; + +#[derive( + Debug, Clone, Copy, PartialEq, Eq, Hash, + clap::ValueEnum, serde::Serialize, serde::Deserialize, +)] +pub enum Blockchain { + #[serde(alias = "bitcoin")] + Bitcoin, +} + +impl Display for Blockchain { + fn fmt(&self, f: &mut Formatter) -> Result { + match self { + Blockchain::Bitcoin => write!(f, "bitcoin"), + } + } +} \ No newline at end of file diff --git a/src/blockchains/fabrics.rs b/src/blockchains/fabrics.rs new file mode 100644 index 0000000..95ff5a6 --- /dev/null +++ b/src/blockchains/fabrics.rs @@ -0,0 +1,25 @@ +use config::Value; +use std::collections::HashMap; + +use crate::utils::DynError; +use super::bitcoin::Bitcoin; +use super::enums::Blockchain; +use super::traits::{SnapshotLoader, WalletChecker}; + +pub fn create_wallet_checker( + blockchain: &Blockchain, + data: &HashMap, +) -> Result, DynError> { + match blockchain { + Blockchain::Bitcoin => Ok(Box::new(Bitcoin::new(data)?)), + } +} + +pub fn create_snapshot_loader( + blockchain: &Blockchain, + data: &HashMap, +) -> Result, DynError> { + match blockchain { + Blockchain::Bitcoin => Ok(Box::new(Bitcoin::new(data)?)), + } +} \ No newline at end of file diff --git a/src/blockchains/mod.rs b/src/blockchains/mod.rs new file mode 100644 index 0000000..0481314 --- /dev/null +++ b/src/blockchains/mod.rs @@ -0,0 +1,8 @@ +mod bitcoin; +mod enums; +mod fabrics; +mod traits; + +pub use enums::Blockchain; +pub use fabrics::{create_snapshot_loader, create_wallet_checker}; +pub use traits::{SnapshotLoader, WalletChecker}; diff --git a/src/blockchains/traits.rs b/src/blockchains/traits.rs new file mode 100644 index 0000000..9924c8d --- /dev/null +++ b/src/blockchains/traits.rs @@ -0,0 +1,18 @@ +use async_trait::async_trait; + +use crate::key_generators::KeyAlgorithm; +use crate::utils::DynError; +use super::enums::Blockchain; + +#[async_trait] +pub trait WalletChecker: Send + Sync { + fn get_blockchain(&self) -> Blockchain; + fn get_key_algorithm(&self) -> KeyAlgorithm; + async fn get_wallet_info(&self, key_hex: &str) -> Result, DynError>; +} + +#[async_trait] +pub trait SnapshotLoader: Send + Sync { + fn get_blockchain(&self) -> Blockchain; + async fn load_snapshot(&self, snapshot_path: &str) -> Result<(), DynError>; +} \ No newline at end of file diff --git a/src/cli.rs b/src/cli.rs index 6b693f4..1a4853d 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,10 +1,11 @@ use clap::{Parser, Subcommand}; +use crate::blockchains::Blockchain; #[derive(Parser)] #[command(version, about, long_about = None)] pub struct Cli { - #[arg(short, long, default_value = "config.yaml")] - pub config: String, + #[arg(short, long, default_value = "settings.yaml")] + pub settings: String, #[command(subcommand)] pub command: Commands, @@ -12,16 +13,14 @@ pub struct Cli { #[derive(Subcommand)] pub enum Commands { - /// Search for wallets Search { - network: Option, + blockchain: Option, #[command(subcommand)] command: SearchSubcommand, }, - /// Manage snapshots Snapshots { - network: String, + blockchain: Blockchain, #[command(subcommand)] command: SnapshotSubcommand, @@ -30,12 +29,10 @@ pub enum Commands { #[derive(Subcommand)] pub enum SearchSubcommand { - /// Run search process Run, } #[derive(Subcommand)] pub enum SnapshotSubcommand { - /// Load snapshot into database Load { path: String }, } \ No newline at end of file diff --git a/src/config.rs b/src/config.rs deleted file mode 100644 index bc85790..0000000 --- a/src/config.rs +++ /dev/null @@ -1,35 +0,0 @@ -use serde::Deserialize; -use std::collections::HashMap; -use std::fs::File; -use std::io::Read; -use std::path::Path; - -#[derive(Debug, Deserialize)] -pub struct Config { - pub networks: HashMap, - pub notifications: NotificationConfig, -} - -#[derive(Debug, Deserialize, Clone)] -pub struct NetworkConfig { - pub key_generator_tasks: usize, - pub balance_checker_tasks: usize, - #[serde(flatten)] - pub params: HashMap, -} - -#[derive(Debug, Deserialize, Clone)] -pub struct NotificationConfig { - pub telegram_bot_token: String, - pub telegram_user_id: String, -} - -impl Config { - pub fn from_file>(path: P) -> Result> { - let mut file = File::open(path)?; - let mut contents = String::new(); - file.read_to_string(&mut contents)?; - let config: Config = serde_yaml::from_str(&contents)?; - Ok(config) - } -} \ No newline at end of file diff --git a/src/key_generators/enums.rs b/src/key_generators/enums.rs new file mode 100644 index 0000000..84cbe16 --- /dev/null +++ b/src/key_generators/enums.rs @@ -0,0 +1,20 @@ +use clap; +use serde; +use std::fmt::{Display, Formatter, Result}; + +#[derive( + Debug, Clone, Copy, PartialEq, Eq, Hash, + clap::ValueEnum, serde::Serialize, serde::Deserialize +)] +pub enum KeyAlgorithm { + #[serde(alias = "secp256k1")] + Secp256k1, +} + +impl Display for KeyAlgorithm { + fn fmt(&self, f: &mut Formatter) -> Result { + match self { + KeyAlgorithm::Secp256k1 => write!(f, "secp256k1"), + } + } +} \ No newline at end of file diff --git a/src/key_generators/fabrics.rs b/src/key_generators/fabrics.rs new file mode 100644 index 0000000..c995033 --- /dev/null +++ b/src/key_generators/fabrics.rs @@ -0,0 +1,16 @@ +use config::Value; +use std::collections::HashMap; + +use crate::utils::DynError; +use super::enums::KeyAlgorithm; +use super::secp256k1::Secp256k1Generator; +use super::traits::KeyGenerator; + +pub fn create_key_generator( + algorithm: &KeyAlgorithm, + data: &HashMap, +) -> Result, DynError> { + match algorithm { + KeyAlgorithm::Secp256k1 => Ok(Box::new(Secp256k1Generator::new(data)?)), + } +} \ No newline at end of file diff --git a/src/key_generators/mod.rs b/src/key_generators/mod.rs new file mode 100644 index 0000000..50f51fe --- /dev/null +++ b/src/key_generators/mod.rs @@ -0,0 +1,8 @@ +mod enums; +mod fabrics; +mod secp256k1; +mod traits; + +pub use enums::KeyAlgorithm; +pub use fabrics::create_key_generator; +pub use traits::KeyGenerator; diff --git a/src/key_generators/secp256k1.rs b/src/key_generators/secp256k1.rs new file mode 100644 index 0000000..b2666b8 --- /dev/null +++ b/src/key_generators/secp256k1.rs @@ -0,0 +1,29 @@ +use config::Value; +use hex; +use secp256k1::{rand, SecretKey, Secp256k1}; +use std::collections::HashMap; + +use crate::utils::DynError; +use super::enums::KeyAlgorithm; +use super::traits::KeyGenerator; + +pub struct Secp256k1Generator {} + +impl Secp256k1Generator { + pub fn new(data: &HashMap) -> Result { + Ok(Secp256k1Generator{}) + } +} + +impl KeyGenerator for Secp256k1Generator { + fn get_key_algorithm(&self) -> KeyAlgorithm { + KeyAlgorithm::Secp256k1 + } + + fn generate_key_hex(&self) -> String { + let secp = Secp256k1::new(); + let secret_key = SecretKey::new(&mut rand::rng()); + let secret_key_bytes = secret_key.secret_bytes(); + hex::encode(secret_key_bytes) + } +} \ No newline at end of file diff --git a/src/key_generators/traits.rs b/src/key_generators/traits.rs new file mode 100644 index 0000000..f41583d --- /dev/null +++ b/src/key_generators/traits.rs @@ -0,0 +1,6 @@ +use super::enums::KeyAlgorithm; + +pub trait KeyGenerator: Send + Sync { + fn get_key_algorithm(&self) -> KeyAlgorithm; + fn generate_key_hex(&self) -> String; +} diff --git a/src/main.rs b/src/main.rs index 14dc61d..91c6c28 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,58 +1,51 @@ +mod blockchains; mod cli; -mod config; -mod networks; +mod key_generators; mod notification; +mod settings; +mod utils; +use async_channel; use clap::Parser; use cli::Cli; -use config::Config; -use crossbeam_channel::{bounded, unbounded}; -use lazy_static::lazy_static; -use networks::create_network; use std::collections::HashMap; -use std::sync::Arc; use std::thread; -use std::time::Duration; +use tokio::sync::broadcast; -fn main() -> Result<(), Box> { +use crate::blockchains::{ + create_snapshot_loader, + create_wallet_checker, + Blockchain, +}; +use crate::key_generators::{create_key_generator, KeyAlgorithm}; +use crate::notification::send_telegram_message; +use crate::settings::Settings; +use crate::utils::DynError; + +#[tokio::main] +async fn main() -> Result<(), DynError> { simple_logger::init_with_level(log::Level::Info)?; let cli = Cli::parse(); - let config = Config::from_file(&cli.config)?; + let settings = Settings::load(&cli.settings)?; match &cli.command { - cli::Commands::Snapshots { network, command } => { - let network_config = config - .networks - .get(network) - .ok_or(format!("Config for network {} not found", network))?; - let network_obj = create_network(network, &network_config.params)?; + cli::Commands::Snapshots { blockchain, command } => { + let blockchain_settings = settings + .blockchains + .get(blockchain) + .ok_or(format!("Settings for blockchain {} not found", blockchain))?; + let snapshot_loader = create_snapshot_loader(blockchain, &blockchain_settings.data)?; match command { cli::SnapshotSubcommand::Load { path } => { - network_obj.load_snapshot(path)?; - log::info!("Snapshot loaded successfully for {}", network); + snapshot_loader.load_snapshot(path).await?; + log::info!("Snapshot loaded successfully for {}", blockchain); } } } - cli::Commands::Search { network, command } => { + cli::Commands::Search { blockchain, command } => { match command { - cli::SearchSubcommand::Run => { - let networks_to_run: HashMap<_, _> = match network { - Some(net) => { - let cfg = config.networks.get(net).ok_or("Network not found")?; - [(net.as_str(), cfg)].into() - } - None => config - .networks - .iter() - .map(|(k, v)| (k.as_str(), v)) - .collect(), - }; - - for (name, net_cfg) in networks_to_run { - run_network_pipeline(name, net_cfg, &config.notifications)?; - } - }, + cli::SearchSubcommand::Run => run_search(blockchain.as_ref(), &settings).await?, } } } @@ -60,117 +53,102 @@ fn main() -> Result<(), Box> { Ok(()) } -fn run_network_pipeline( - network_name: &str, - config: &config::NetworkConfig, - notification_cfg: &config::NotificationConfig, -) -> Result<(), Box> { - log::info!("Starting pipeline for {}", network_name); - - let network = Arc::new(create_network(network_name, &config.params)?); - let (keypair_sender, keypair_receiver) = bounded(100); - let (notification_sender, notification_receiver) = unbounded(); - - // Клонируем имя сети для использования в замыканиях - let network_name_str = network_name.to_string(); - - // Key generation workers - for _ in 0..config.key_generator_tasks { - let sender = keypair_sender.clone(); - let net = network.clone(); - thread::spawn(move || loop { - let keypair = net.generate_keypair(); - if let Err(e) = sender.send(keypair) { - log::error!("Keypair send error: {}", e); - break; - } - }); +async fn run_search( + blockchain: Option<&Blockchain>, + settings: &Settings, +) -> Result<(), DynError> { + let mut key_senders: HashMap> = HashMap::new(); + for algorithm in settings.key_generators.keys() { + let (sender, _) = broadcast::channel(1_000_000); + key_senders.insert(algorithm.clone(), sender); } - // Balance checker workers - for _ in 0..config.balance_checker_tasks { - let receiver = keypair_receiver.clone(); - let sender = notification_sender.clone(); - let net = network.clone(); - let name = network_name_str.clone(); // Клонируем String - thread::spawn(move || loop { - match receiver.recv() { - Ok((private_key, address)) => { - match net.check_balance(&address) { - Ok(balance) if balance > 0 => { - let formatted_balance = format_balance(balance); - let message = format!( - "💰 *Balance found!*\n\n*Network:* {}\n*Address:* `{}`\n*Private Key:* `{}`\n*Balance:* {}", - name, address, private_key, formatted_balance, - ); - if let Err(e) = sender.send(message) { - log::error!("Notification send error: {}", e); - } - } - Ok(_) => {} // Баланс нулевой, ничего не делаем - Err(e) => log::error!("Balance check error: {}", e), + let mut key_gen_handles = vec![]; + for (algorithm, algo_settings) in &settings.key_generators { + let sender = key_senders.get(algorithm).unwrap().clone(); + + for _ in 0..algo_settings.workers { + let algo_settings = algo_settings.clone(); + let algorithm = algorithm.clone(); + let sender = sender.clone(); // Клонируем sender для каждого потока + let handle = thread::spawn(move || { + let key_generator = create_key_generator(&algorithm, &algo_settings.data) + .expect("Failed to create key generator"); + loop { + let key_hex = key_generator.generate_key_hex(); + if let Err(e) = sender.send(key_hex) { + log::error!("Key generator for {:?} failed to send: {}", algorithm, e); } } - Err(e) => { - log::error!("Keypair receive error: {}", e); - break; - } - } - }); + }); + key_gen_handles.push(handle); + } } - // Notification worker - let notifier_cfg = notification_cfg.clone(); - thread::spawn(move || { - for message in notification_receiver { - if let Err(e) = notification::send_telegram_message( - ¬ifier_cfg.telegram_bot_token, - ¬ifier_cfg.telegram_user_id, - &message, - ) { - log::error!("Telegram send error: {}", e); + let (notify_sender, notify_receiver) = async_channel::unbounded::(); + + let mut wallet_checker_handles = vec![]; + for (blockchain, blockchain_settings) in &settings.blockchains { + let wallet_checker = create_wallet_checker(blockchain, &blockchain_settings.data)?; + let key_algorithm = wallet_checker.get_key_algorithm(); + let key_sender = key_senders.get(&key_algorithm).ok_or(format!( + "No key sender for algorithm {:?} (blockchain {:?})", + key_algorithm, blockchain + ))?; + let key_receiver = key_sender.subscribe(); + + for _ in 0..blockchain_settings.workers { + let mut key_receiver = key_receiver.resubscribe(); + let wallet_checker = create_wallet_checker(blockchain, &blockchain_settings.data)?; + let notify_sender = notify_sender.clone(); + + let handle = tokio::spawn(async move { + loop { + match key_receiver.recv().await { + Ok(key_hex) => { + match wallet_checker.get_wallet_info(&key_hex).await { + Ok(Some(info)) => { + if let Err(e) = notify_sender.send(info).await { + log::error!("Failed to send notification: {}", e); + } + } + Ok(None) => {} + Err(e) => { + log::error!( + "Error getting wallet info: {}", + e + ); + } + } + } + Err(broadcast::error::RecvError::Closed) => { + log::info!("Key channel closed"); + break; + } + Err(broadcast::error::RecvError::Lagged(skipped)) => { + log::warn!("Skipped {} keys", skipped); + } + } + } + }); + wallet_checker_handles.push(handle); + } + } + + // Клонируем необходимые данные для нотификации + let bot_token = settings.notifications.telegram_bot_token.clone(); + let user_id = settings.notifications.telegram_user_id.clone(); + + let _ = tokio::spawn(async move { + while let Ok(message) = notify_receiver.recv().await { + if let Err(e) = send_telegram_message(&bot_token, &user_id, &message).await { + log::error!("Failed to send Telegram message: {}", e); } } }); - log::info!("Pipeline started for {}", network_name); - loop { - thread::sleep(Duration::from_secs(60)); - } -} - -lazy_static! { - static ref THRESHOLDS: Vec<(u64, &'static str)> = vec![ - (1_000_000_000_000_000_000u64, " E"), - (1_000_000_000_000_000u64, " P"), - (1_000_000_000_000u64, " T"), - (1_000_000_000u64, " G"), - (1_000_000u64, " M"), - (1_000u64, " k"), - ]; -} - -fn format_balance(balance: i64) -> String { - let abs_balance = balance.abs() as u64; - - let mut divider = 1; - let mut suffix = " sat"; - - for (thresh, s) in THRESHOLDS.iter() { - if abs_balance >= *thresh { - divider = thresh.clone(); - suffix = s; - break; - } - } - - let quotient = abs_balance / divider; - let remainder = abs_balance % divider; - - if remainder == 0 { - format!("{}{}", quotient, suffix) - } else { - let fractional = remainder as f64 / divider as f64; - format!("{:.4}{}", quotient as f64 + fractional, suffix) - } -} + // Ждем сигнала Ctrl+C для завершения + tokio::signal::ctrl_c().await?; + log::info!("Shutting down"); + Ok(()) +} \ No newline at end of file diff --git a/src/networks/bitcoin.rs b/src/networks/bitcoin.rs deleted file mode 100644 index 8f02c88..0000000 --- a/src/networks/bitcoin.rs +++ /dev/null @@ -1,187 +0,0 @@ -use bitcoin::{ - secp256k1, Address, Network, PrivateKey, PublicKey, -}; -use indicatif::{MultiProgress, ProgressBar, ProgressStyle}; -use postgres::{Client, NoTls}; -use serde_yaml::Value; -use std::collections::HashMap; -use std::error::Error; -use std::fs::File; -use std::io::{BufReader, Read, Write}; -use std::time::{Duration, Instant}; - -pub struct Bitcoin { - database_url: String, - database_table: String, -} - -impl Bitcoin { - pub fn new(params: &HashMap) -> Result> { - let database_url = params - .get("database_url") - .and_then(Value::as_str) - .ok_or("Missing database_url for Bitcoin")? - .to_string(); - - let database_table = params - .get("database_table") - .and_then(Value::as_str) - .ok_or("Missing database_table for Bitcoin")? - .to_string(); - - Ok(Self { - database_url, - database_table, - }) - } - - fn connect(&self) -> Result> { - let client = Client::connect(&self.database_url, NoTls)?; - Ok(client) - } -} - -impl super::Network for Bitcoin { - fn generate_keypair(&self) -> (String, String) { - let secp = secp256k1::Secp256k1::new(); - let private_key = PrivateKey::new(secp256k1::SecretKey::new(&mut rand::thread_rng()), Network::Bitcoin); - let public_key = PublicKey::from_private_key(&secp, &private_key); - let address = Address::p2pkh(&public_key, Network::Bitcoin).to_string(); - - (private_key.to_wif(), address) - } - - fn check_balance(&self, address: &str) -> Result> { - let mut client = self.connect()?; - let row = client.query_opt( - &format!( - "SELECT balance FROM {} WHERE address = $1", - self.database_table - ), - &[&address], - )?; - - Ok(row.map(|r| r.get(0)).unwrap_or(0)) - } - - fn load_snapshot(&self, snapshot_path: &str) -> Result<(), Box> { - let start_time = Instant::now(); - let mut client = self.connect()?; - let file = File::open(snapshot_path)?; - let file_size = file.metadata()?.len(); - let index_name = format!("{}__address__ix", self.database_table); - let multi_progress = MultiProgress::new(); - - // 1. Preparing database - let prep_pb = multi_progress.add(ProgressBar::new_spinner()); - prep_pb.set_style( - ProgressStyle::default_spinner() - .tick_chars("⠁⠂⠄⡀⢀⠠⠐⠈ ") - .template("{spinner} {msg}").unwrap() - ); - prep_pb.set_message("⚙️ Preparing database..."); - prep_pb.enable_steady_tick(Duration::from_millis(100)); - - client.execute("SET synchronous_commit = off", &[])?; - client.execute("SET maintenance_work_mem = '4GB'", &[])?; - client.execute("SET work_mem = '2GB'", &[])?; - client.execute( - &format!("DROP TABLE IF EXISTS {}", self.database_table), - &[], - )?; - client.execute( - &format!("DROP INDEX IF EXISTS {}", index_name), - &[], - )?; - client.execute( - &format!( - "CREATE TABLE {} (address TEXT, balance BIGINT)", - self.database_table - ), - &[], - )?; - - prep_pb.finish_with_message("✅ Database prepared"); - - // 2. Copy data to database - let copy_pb = multi_progress.add(ProgressBar::new(file_size)); - copy_pb.set_style(ProgressStyle::default_bar() - .template("{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {bytes}/{total_bytes} ({eta}) | {binary_bytes_per_sec}") - .unwrap() - .progress_chars("#>-")); - copy_pb.set_message("️📥 Copy data to database..."); - - let copy_stmt = format!( - "COPY {} FROM STDIN WITH (FORMAT csv, DELIMITER E'\t', HEADER)", - self.database_table, - ); - let mut writer = client.copy_in(©_stmt)?; - - let file = File::open(snapshot_path)?; - let mut reader = BufReader::new(file); - let mut buffer = [0u8; 65536]; // 64KB буфер - let mut total_bytes = 0; - - loop { - let bytes_read = reader.read(&mut buffer)?; - if bytes_read == 0 { - break; - } - - writer.write_all(&buffer[..bytes_read])?; - total_bytes += bytes_read as u64; - copy_pb.set_position(total_bytes); - } - - writer.finish()?; - copy_pb.set_position(file_size); - copy_pb.finish_with_message("✅ Data copied to database"); - - // 3. Creating index - let index_pb = multi_progress.add(ProgressBar::new_spinner()); - index_pb.set_message("🔀 Creating index..."); - index_pb.enable_steady_tick(Duration::from_millis(100)); - - client.execute( - &format!( - "CREATE INDEX {} ON {} USING HASH (address)", - index_name, - self.database_table, - ), - &[], - )?; - - index_pb.finish_with_message("✅ Index created"); - - // 4. Reseting temporary settings - let final_pb = multi_progress.add(ProgressBar::new_spinner()); - final_pb.set_message("🔁 Reset database settings"); - final_pb.enable_steady_tick(Duration::from_millis(100)); - - client.execute("RESET synchronous_commit", &[])?; - client.execute("RESET maintenance_work_mem", &[])?; - client.execute("RESET work_mem", &[])?; - - final_pb.finish_with_message("✅ Database settings reseted"); - - // 5. Final - multi_progress.clear()?; - - let records_count: i64 = client.query_one( - &format!( - "SELECT COUNT(*) FROM {}", - self.database_table, - ), - &[], - ).map(|row| row.get(0))?; - - let data_size: f64 = file_size as f64 / (1024 * 1024 * 1024) as f64; - println!("\n🎉 Snapshot loaded successfully!"); - println!("📊 Statistics:"); - println!(" Total records processed: {}", records_count); - println!(" Data size: {:.2} GB", data_size); - println!(" Execution time: {:?}", start_time.elapsed()); - - Ok(()) - } -} diff --git a/src/networks/mod.rs b/src/networks/mod.rs deleted file mode 100644 index b4c8eb4..0000000 --- a/src/networks/mod.rs +++ /dev/null @@ -1,35 +0,0 @@ -pub mod bitcoin; - -use serde_yaml::Value; -use std::collections::HashMap; -use std::error::Error; - -pub trait Network: Send + Sync { - fn generate_keypair(&self) -> (String, String); - fn check_balance(&self, address: &str) -> Result>; - // TODO: Remove - fn load_snapshot(&self, snapshot_path: &str) -> Result<(), Box>; -} - -pub trait SnapshotLoader { - fn load_snapshot(&self, snapshot_path: &str, format: Option<&str>) -> Result<(), Box>; -} - -pub fn create_network( - name: &str, - params: &HashMap, -) -> Result, Box> { - match name { - "bitcoin" => Ok(Box::new(bitcoin::Bitcoin::new(params)?)), - _ => Err(format!("Unsupported network: {}", name).into()), - } -} - -pub fn create_snapshot_loader( - name: &str, - params: &HashMap, -) -> Result, Box> { - match name { - _ => Err(format!("Unsupported network: {}", name).into()), - } -} \ No newline at end of file diff --git a/src/notification.rs b/src/notification.rs index 52d2184..4ad6a06 100644 --- a/src/notification.rs +++ b/src/notification.rs @@ -1,11 +1,12 @@ use reqwest::blocking::Client; -use std::error::Error; -pub fn send_telegram_message( +use crate::utils::DynError; + +pub async fn send_telegram_message( bot_token: &str, user_id: &str, message: &str, -) -> Result<(), Box> { +) -> Result<(), DynError> { let url = format!( "https://api.telegram.org/bot{}/sendMessage", bot_token diff --git a/src/settings.rs b/src/settings.rs new file mode 100644 index 0000000..bfdca83 --- /dev/null +++ b/src/settings.rs @@ -0,0 +1,52 @@ +use config::{Config, File, Value}; +use serde::Deserialize; +use std::collections::HashMap; +use std::thread::available_parallelism; + +use crate::blockchains::Blockchain; +use crate::key_generators::KeyAlgorithm; +use crate::utils::DynError; + +#[derive(Debug, Deserialize)] +pub struct Settings { + pub key_generators: HashMap, + pub blockchains: HashMap, + pub notifications: NotificationSettings, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct KeyGeneratorSettings { + #[serde(default = "default_workers")] + pub workers: usize, + #[serde(flatten)] + pub data: HashMap, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct BlockchainSettings { + #[serde(default = "default_workers")] + pub workers: usize, + #[serde(flatten)] + pub data: HashMap, + +} + +fn default_workers() -> usize { + available_parallelism().unwrap().get() +} + +#[derive(Debug, Deserialize, Clone)] +pub struct NotificationSettings { + pub telegram_bot_token: String, + pub telegram_user_id: String, +} + +impl Settings { + pub fn load(path: &str) -> Result { + Config::builder() + .add_source(File::with_name(path).required(false)) + .build()? + .try_deserialize() + .map_err(|e| e.into()) + } +} \ No newline at end of file diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..e2bca46 --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,3 @@ +use std::error::Error; + +pub type DynError = Box; \ No newline at end of file