Pre-release v0.2.0

This commit is contained in:
2025-06-10 13:29:49 +03:00
parent 232684509f
commit 74b1cc6e6f
21 changed files with 998 additions and 512 deletions

513
Cargo.lock generated
View File

@@ -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"

View File

@@ -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"

View File

@@ -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

235
src/blockchains/bitcoin.rs Normal file
View File

@@ -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<String, Value>) -> Result<Self, DynError> {
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::<tokio_postgres::Config>()?;
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<deadpool_postgres::Client, DynError> {
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<Option<String>, 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(&copy_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(())
}
}

20
src/blockchains/enums.rs Normal file
View File

@@ -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"),
}
}
}

View File

@@ -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<String, Value>,
) -> Result<Box<dyn WalletChecker>, DynError> {
match blockchain {
Blockchain::Bitcoin => Ok(Box::new(Bitcoin::new(data)?)),
}
}
pub fn create_snapshot_loader(
blockchain: &Blockchain,
data: &HashMap<String, Value>,
) -> Result<Box<dyn SnapshotLoader>, DynError> {
match blockchain {
Blockchain::Bitcoin => Ok(Box::new(Bitcoin::new(data)?)),
}
}

8
src/blockchains/mod.rs Normal file
View File

@@ -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};

18
src/blockchains/traits.rs Normal file
View File

@@ -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<Option<String>, DynError>;
}
#[async_trait]
pub trait SnapshotLoader: Send + Sync {
fn get_blockchain(&self) -> Blockchain;
async fn load_snapshot(&self, snapshot_path: &str) -> Result<(), DynError>;
}

View File

@@ -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<String>,
blockchain: Option<Blockchain>,
#[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 },
}

View File

@@ -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<String, NetworkConfig>,
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<String, serde_yaml::Value>,
}
#[derive(Debug, Deserialize, Clone)]
pub struct NotificationConfig {
pub telegram_bot_token: String,
pub telegram_user_id: String,
}
impl Config {
pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self, Box<dyn std::error::Error>> {
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)
}
}

View File

@@ -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"),
}
}
}

View File

@@ -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<String, Value>,
) -> Result<Box<dyn KeyGenerator>, DynError> {
match algorithm {
KeyAlgorithm::Secp256k1 => Ok(Box::new(Secp256k1Generator::new(data)?)),
}
}

View File

@@ -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;

View File

@@ -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<String, Value>) -> Result<Self, DynError> {
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)
}
}

View File

@@ -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;
}

View File

@@ -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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
Ok(())
}
fn run_network_pipeline(
network_name: &str,
config: &config::NetworkConfig,
notification_cfg: &config::NotificationConfig,
) -> Result<(), Box<dyn std::error::Error>> {
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<KeyAlgorithm, broadcast::Sender<String>> = 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(
&notifier_cfg.telegram_bot_token,
&notifier_cfg.telegram_user_id,
&message,
) {
log::error!("Telegram send error: {}", e);
let (notify_sender, notify_receiver) = async_channel::unbounded::<String>();
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(())
}

View File

@@ -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<String, Value>) -> Result<Self, Box<dyn Error>> {
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<Client, Box<dyn Error>> {
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<i64, Box<dyn Error>> {
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<dyn Error>> {
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(&copy_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(())
}
}

View File

@@ -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<i64, Box<dyn Error>>;
// TODO: Remove
fn load_snapshot(&self, snapshot_path: &str) -> Result<(), Box<dyn Error>>;
}
pub trait SnapshotLoader {
fn load_snapshot(&self, snapshot_path: &str, format: Option<&str>) -> Result<(), Box<dyn Error>>;
}
pub fn create_network(
name: &str,
params: &HashMap<String, Value>,
) -> Result<Box<dyn Network>, Box<dyn Error>> {
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<String, Value>,
) -> Result<Box<dyn SnapshotLoader>, Box<dyn Error>> {
match name {
_ => Err(format!("Unsupported network: {}", name).into()),
}
}

View File

@@ -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<dyn Error>> {
) -> Result<(), DynError> {
let url = format!(
"https://api.telegram.org/bot{}/sendMessage",
bot_token

52
src/settings.rs Normal file
View File

@@ -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<KeyAlgorithm, KeyGeneratorSettings>,
pub blockchains: HashMap<Blockchain, BlockchainSettings>,
pub notifications: NotificationSettings,
}
#[derive(Debug, Deserialize, Clone)]
pub struct KeyGeneratorSettings {
#[serde(default = "default_workers")]
pub workers: usize,
#[serde(flatten)]
pub data: HashMap<String, Value>,
}
#[derive(Debug, Deserialize, Clone)]
pub struct BlockchainSettings {
#[serde(default = "default_workers")]
pub workers: usize,
#[serde(flatten)]
pub data: HashMap<String, Value>,
}
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<Self, DynError> {
Config::builder()
.add_source(File::with_name(path).required(false))
.build()?
.try_deserialize()
.map_err(|e| e.into())
}
}

3
src/utils.rs Normal file
View File

@@ -0,0 +1,3 @@
use std::error::Error;
pub type DynError = Box<dyn Error + Send + Sync>;