diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..b6c2f14 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,4 @@ +[submodule "dg_xch_utils"] + path = dg_xch_utils + url = https://github.com/GalactechsLLC/dg_xch_utils.git + branch = protocol_updates diff --git a/Cargo.lock b/Cargo.lock index fa6435b..a022e82 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,11 +4,11 @@ version = 3 [[package]] name = "actix-codec" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "617a8268e3537fe1d8c9ead925fca49ef6400927ee7bc26750e90ecee14ce4b8" +checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.4.1", "bytes", "futures-core", "futures-sink", @@ -21,22 +21,25 @@ dependencies = [ [[package]] name = "actix-http" -version = "3.4.0" +version = "3.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92ef85799cba03f76e4f7c10f533e66d87c9a7e7055f3391f09000ad8351bc9" +checksum = "d223b13fd481fc0d1f83bb12659ae774d9e3601814c68a0bc539731698cca743" dependencies = [ "actix-codec", "actix-rt", "actix-service", "actix-utils", "ahash", - "base64", + "base64 0.21.5", "bitflags 2.4.1", + "brotli", "bytes", "bytestring", "derive_more", "encoding_rs", + "flate2", "futures-core", + "h2 0.3.26", "http 0.2.11", "httparse", "httpdate", @@ -52,13 +55,24 @@ dependencies = [ "tokio", "tokio-util", "tracing", + "zstd", +] + +[[package]] +name = "actix-macros" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" +dependencies = [ + "quote", + "syn 2.0.52", ] [[package]] name = "actix-router" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d66ff4d247d2b160861fa2866457e85706833527840e4133f8f49aa423a38799" +checksum = "d22475596539443685426b6bdadb926ad0ecaefdfc5fb05e5e3441f15463c511" dependencies = [ "bytestring", "http 0.2.11", @@ -117,21 +131,24 @@ dependencies = [ [[package]] name = "actix-web" -version = "4.4.0" +version = "4.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4a5b5e29603ca8c94a77c65cf874718ceb60292c5a5c3e5f4ace041af462b9" +checksum = "43a6556ddebb638c2358714d853257ed226ece6023ef9364f23f0c70737ea984" dependencies = [ "actix-codec", "actix-http", + "actix-macros", "actix-router", "actix-rt", "actix-server", "actix-service", "actix-utils", + "actix-web-codegen", "ahash", "bytes", "bytestring", "cfg-if", + "cookie", "derive_more", "encoding_rs", "futures-core", @@ -152,6 +169,18 @@ dependencies = [ "url", ] +[[package]] +name = "actix-web-codegen" +version = "4.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb1f50ebbb30eca122b188319a4398b3f7bb4a8cdf50ecfb73bfc6a3c3ce54f5" +dependencies = [ + "actix-router", + "proc-macro2", + "quote", + "syn 2.0.52", +] + [[package]] name = "addr2line" version = "0.21.0" @@ -189,6 +218,21 @@ dependencies = [ "memchr", ] +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + [[package]] name = "allocator-api2" version = "0.2.16" @@ -258,12 +302,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "anyhow" -version = "1.0.75" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" - [[package]] name = "arrayref" version = "0.3.7" @@ -278,34 +316,15 @@ checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" [[package]] name = "async-trait" -version = "0.1.77" +version = "0.1.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" +checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" dependencies = [ "proc-macro2", "quote", "syn 2.0.52", ] -[[package]] -name = "atoi" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" -dependencies = [ - "num-traits", -] - -[[package]] -name = "atomic-write-file" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edcdbedc2236483ab103a53415653d6b4442ea6141baf1ffa85df29635e88436" -dependencies = [ - "nix", - "rand", -] - [[package]] name = "autocfg" version = "1.1.0" @@ -333,6 +352,12 @@ version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" +[[package]] +name = "base64" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9475866fec1451be56a3c2400fd081ff546538961565ccb5b7142cbd22bc7a51" + [[package]] name = "base64ct" version = "1.6.0" @@ -354,7 +379,7 @@ dependencies = [ "bitflags 2.4.1", "cexpr", "clang-sys", - "itertools 0.12.1", + "itertools", "lazy_static", "lazycell", "log", @@ -398,9 +423,6 @@ name = "bitflags" version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" -dependencies = [ - "serde", -] [[package]] name = "bitvec" @@ -461,6 +483,27 @@ dependencies = [ "zeroize", ] +[[package]] +name = "brotli" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d640d25bc63c50fb1f0b545ffd80207d2e10a4c965530809b40ba3386825c391" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "2.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e2e4afe60d7dd600fdd3de8d0f08c2b7ec039712e3b6137ff98b7004e82de4f" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + [[package]] name = "bumpalo" version = "3.14.0" @@ -518,6 +561,7 @@ version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" dependencies = [ + "jobserver", "libc", ] @@ -538,9 +582,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.34" +version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bc015644b92d5890fab7489e49d21f879d5c990186827d42ec511919404f38b" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" dependencies = [ "android-tzdata", "iana-time-zone", @@ -563,9 +607,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.1" +version = "4.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c918d541ef2913577a0f9566e9ce27cb35b6df072075769e0b26cb5a554520da" +checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" dependencies = [ "clap_builder", "clap_derive", @@ -573,9 +617,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.1" +version = "4.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f3e7391dad68afb0c2ede1bf619f579a3dc9c2ec67f089baa397123a2f3d1eb" +checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" dependencies = [ "anstream", "anstyle", @@ -585,11 +629,11 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.0" +version = "4.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "307bc0538d5f0f83b8248db3087aa92fe504e4691294d0c96c0eabc33f47ba47" +checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro2", "quote", "syn 2.0.52", @@ -662,6 +706,17 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" +[[package]] +name = "cookie" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" +dependencies = [ + "percent-encoding", + "time", + "version_check", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -688,19 +743,22 @@ dependencies = [ ] [[package]] -name = "crc" -version = "3.0.1" +name = "crc32fast" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86ec7a15cbe22e59248fc7eadb1907dab5ba09372595da4d73dd805ed4417dfe" +checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" dependencies = [ - "crc-catalog", + "cfg-if", ] [[package]] -name = "crc-catalog" -version = "2.4.0" +name = "crossbeam-channel" +version = "0.5.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" +checksum = "ab3db02a9c5b5121e1e42fbdb1aeb65f5e02624cc58c43f2884c6ccac0b82f95" +dependencies = [ + "crossbeam-utils", +] [[package]] name = "crossbeam-deque" @@ -726,15 +784,6 @@ dependencies = [ "scopeguard", ] -[[package]] -name = "crossbeam-queue" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35" -dependencies = [ - "crossbeam-utils", -] - [[package]] name = "crossbeam-utils" version = "0.8.19" @@ -843,8 +892,9 @@ dependencies = [ [[package]] name = "dg_fast_farmer" -version = "1.0.2" +version = "1.0.3" dependencies = [ + "actix-web", "async-trait", "bindgen", "bip39", @@ -861,15 +911,24 @@ dependencies = [ "dg_xch_pos", "dg_xch_puzzles", "dg_xch_serialize", + "dg_xch_servers", "dialoguer", "futures-util", "hex", "home", + "libc", "log", + "notify", + "num-bigint", + "num-integer", "once_cell", + "prometheus", + "protobuf", "rand", "ratatui", - "reqwest", + "rayon", + "regex", + "reqwest 0.12.3", "serde", "serde_json", "serde_yaml", @@ -883,9 +942,7 @@ dependencies = [ [[package]] name = "dg_xch_cli" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d264b1ffe12d69c828ecfd4f3ef88c9216cb7ebbb9aec9f77bc18d6d2df1aee" +version = "2.0.3" dependencies = [ "async-trait", "bip39", @@ -898,6 +955,7 @@ dependencies = [ "dg_xch_pos", "dg_xch_puzzles", "dg_xch_serialize", + "dialoguer", "hex", "lazy_static", "log", @@ -912,9 +970,7 @@ dependencies = [ [[package]] name = "dg_xch_clients" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae95b0fd77324e6325e0206b266b9050b5fb384a97735b860e804b9f3b29f9ba" +version = "2.0.3" dependencies = [ "async-trait", "blst", @@ -929,9 +985,10 @@ dependencies = [ "hyper 1.2.0", "hyper-util", "log", - "reqwest", - "rustls", - "rustls-pemfile", + "prometheus", + "reqwest 0.11.24", + "rustls 0.21.10", + "rustls-pemfile 1.0.4", "serde", "serde_json", "time", @@ -943,9 +1000,7 @@ dependencies = [ [[package]] name = "dg_xch_core" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9094f44256e4927af8ecb11580b5786074440f53368660712a0bbc0da8526c02" +version = "2.0.3" dependencies = [ "async-trait", "bech32", @@ -966,17 +1021,16 @@ dependencies = [ "num-integer", "num-traits", "once_cell", - "paperclip", + "prometheus", "rand", "regex", "rsa", - "rustls", - "rustls-pemfile", + "rustls 0.21.10", + "rustls-pemfile 1.0.4", "serde", "serde_json", "sha2", "simple_logger", - "sqlx", "time", "tokio", "tokio-tungstenite", @@ -986,9 +1040,7 @@ dependencies = [ [[package]] name = "dg_xch_keys" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f761f5241916636affb86dd48e03d67c2a8c97639d11099ed01015ae3076b30c" +version = "2.0.3" dependencies = [ "bech32", "bip39", @@ -1002,9 +1054,7 @@ dependencies = [ [[package]] name = "dg_xch_macros" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d308b4a3976a339bc9afd55898d7133ee15d812758a58ab5ae476bcf4c92572e" +version = "2.0.3" dependencies = [ "proc-macro2", "quote", @@ -1013,9 +1063,7 @@ dependencies = [ [[package]] name = "dg_xch_pos" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d591602efebda32df52d30aac55fc686edcdbce87633f60a5078853ca184d11e" +version = "2.0.3" dependencies = [ "async-trait", "blake3", @@ -1041,9 +1089,7 @@ dependencies = [ [[package]] name = "dg_xch_puzzles" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cfc9fca00bf5a02f78744c70bb973118b8ad43cb78667f11eb31715b724daaa" +version = "2.0.3" dependencies = [ "blst", "dg_xch_core", @@ -1059,15 +1105,41 @@ dependencies = [ [[package]] name = "dg_xch_serialize" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "042d4641272da6cbdcfa011924d16b40f75daa434436dc8229a1a774e6b6ca8b" +version = "2.0.3" dependencies = [ "bytes", "log", + "serde", "sha2", ] +[[package]] +name = "dg_xch_servers" +version = "2.0.3" +dependencies = [ + "async-trait", + "blst", + "dg_xch_clients", + "dg_xch_core", + "dg_xch_keys", + "dg_xch_pos", + "dg_xch_serialize", + "hex", + "http 1.0.0", + "http-body-util", + "hyper 1.2.0", + "hyper-tungstenite", + "hyper-util", + "log", + "prometheus", + "rustls 0.21.10", + "serde", + "tokio", + "tokio-rustls 0.24.1", + "tokio-tungstenite", + "uuid", +] + [[package]] name = "dialoguer" version = "0.11.0" @@ -1093,20 +1165,11 @@ dependencies = [ "subtle", ] -[[package]] -name = "dotenvy" -version = "0.15.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" - [[package]] name = "either" version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" -dependencies = [ - "serde", -] [[package]] name = "encode_unicode" @@ -1139,23 +1202,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "etcetera" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" -dependencies = [ - "cfg-if", - "home", - "windows-sys 0.48.0", -] - -[[package]] -name = "event-listener" -version = "2.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" - [[package]] name = "fastrand" version = "2.0.1" @@ -1174,10 +1220,16 @@ dependencies = [ ] [[package]] -name = "finl_unicode" -version = "1.2.0" +name = "filetime" +version = "0.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fcfdc7a0362c9f4444381a9e697c79d435fe65b52a37466fc2c1184cee9edc6" +checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "windows-sys 0.52.0", +] [[package]] name = "flagset" @@ -1186,14 +1238,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d52a7e408202050813e6f1d9addadcaafef3dca7530c7ddfb005d4081cce6779" [[package]] -name = "flume" -version = "0.11.0" +name = "flate2" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" +checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" dependencies = [ - "futures-core", - "futures-sink", - "spin 0.9.8", + "crc32fast", + "miniz_oxide", ] [[package]] @@ -1212,25 +1263,19 @@ dependencies = [ ] [[package]] -name = "funty" -version = "2.0.0" +name = "fsevent-sys" +version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" +checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" +dependencies = [ + "libc", +] [[package]] -name = "futures" -version = "0.3.29" +name = "funty" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "futures-channel" @@ -1239,7 +1284,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" dependencies = [ "futures-core", - "futures-sink", ] [[package]] @@ -1248,34 +1292,6 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" -[[package]] -name = "futures-executor" -version = "0.3.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-intrusive" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" -dependencies = [ - "futures-core", - "lock_api", - "parking_lot", -] - -[[package]] -name = "futures-io" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" - [[package]] name = "futures-macro" version = "0.3.30" @@ -1305,13 +1321,10 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ - "futures-channel", "futures-core", - "futures-io", "futures-macro", "futures-sink", "futures-task", - "memchr", "pin-project-lite", "pin-utils", "slab", @@ -1385,9 +1398,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.22" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d6250322ef6e60f93f9a2162799302cd6f68f79f6e5d85c8c16f14d1d958178" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" dependencies = [ "bytes", "fnv", @@ -1432,22 +1445,16 @@ dependencies = [ ] [[package]] -name = "hashlink" -version = "0.8.4" +name = "heck" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" -dependencies = [ - "hashbrown", -] +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "heck" -version = "0.4.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" -dependencies = [ - "unicode-segmentation", -] +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" @@ -1531,6 +1538,19 @@ dependencies = [ "http 1.0.0", ] +[[package]] +name = "http-body-util" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d" +dependencies = [ + "bytes", + "futures-core", + "http 1.0.0", + "http-body 1.0.0", + "pin-project-lite", +] + [[package]] name = "httparse" version = "1.8.0" @@ -1553,7 +1573,7 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "h2 0.3.22", + "h2 0.3.26", "http 0.2.11", "http-body 0.4.5", "httparse", @@ -1597,9 +1617,41 @@ dependencies = [ "futures-util", "http 0.2.11", "hyper 0.14.27", - "rustls", + "rustls 0.21.10", + "tokio", + "tokio-rustls 0.24.1", +] + +[[package]] +name = "hyper-rustls" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0bea761b46ae2b24eb4aef630d8d1c398157b6fc29e6350ecf090a0b70c952c" +dependencies = [ + "futures-util", + "http 1.0.0", + "hyper 1.2.0", + "hyper-util", + "rustls 0.22.3", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.25.0", + "tower-service", +] + +[[package]] +name = "hyper-tungstenite" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "096e0568151fedc7a77619c28f56a86b1cbae56aed4dfe443efbd739229c2733" +dependencies = [ + "http-body-util", + "hyper 1.2.0", + "hyper-util", + "pin-project-lite", "tokio", - "tokio-rustls", + "tokio-tungstenite", + "tungstenite", ] [[package]] @@ -1672,7 +1724,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e186cfbae8084e513daff4240b4797e342f988cecda4fb6c939150f96315fd8" [[package]] -name = "ipnet" +name = "inotify" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff" +dependencies = [ + "bitflags 1.3.2", + "inotify-sys", + "libc", +] + +[[package]] +name = "inotify-sys" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" +dependencies = [ + "libc", +] + +[[package]] +name = "ipnet" version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" @@ -1688,15 +1760,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "itertools" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" -dependencies = [ - "either", -] - [[package]] name = "itertools" version = "0.12.1" @@ -1712,6 +1775,15 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +[[package]] +name = "jobserver" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "685a7d121ee3f65ae4fddd72b25a04bb36b6af81bc0828f7d5434c0fe60fa3a2" +dependencies = [ + "libc", +] + [[package]] name = "js-sys" version = "0.3.66" @@ -1721,6 +1793,26 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "kqueue" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7447f1ca1b7b563588a205fe93dea8df60fd981423a768bc1c0ded35ed147d0c" +dependencies = [ + "kqueue-sys", + "libc", +] + +[[package]] +name = "kqueue-sys" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b" +dependencies = [ + "bitflags 1.3.2", + "libc", +] + [[package]] name = "language-tags" version = "0.3.2" @@ -1764,17 +1856,6 @@ version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" -[[package]] -name = "libsqlite3-sys" -version = "0.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4e226dcd58b4be396f7bd3c20da8fdee2911400705297ba7d2d7cc2c30f716" -dependencies = [ - "cc", - "pkg-config", - "vcpkg", -] - [[package]] name = "linux-raw-sys" version = "0.4.12" @@ -1810,9 +1891,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.20" +version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" [[package]] name = "loom" @@ -1846,16 +1927,6 @@ dependencies = [ "regex-automata 0.1.10", ] -[[package]] -name = "md-5" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" -dependencies = [ - "cfg-if", - "digest", -] - [[package]] name = "memchr" version = "2.6.4" @@ -1877,16 +1948,6 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" -[[package]] -name = "mime_guess" -version = "2.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" -dependencies = [ - "mime", - "unicase", -] - [[package]] name = "minimal-lexical" version = "0.2.1" @@ -1914,17 +1975,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "nix" -version = "0.27.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" -dependencies = [ - "bitflags 2.4.1", - "cfg-if", - "libc", -] - [[package]] name = "nom" version = "7.1.3" @@ -1935,6 +1985,25 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "notify" +version = "6.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d" +dependencies = [ + "bitflags 2.4.1", + "crossbeam-channel", + "filetime", + "fsevent-sys", + "inotify", + "kqueue", + "libc", + "log", + "mio", + "walkdir", + "windows-sys 0.48.0", +] + [[package]] name = "ntapi" version = "0.4.1" @@ -2077,79 +2146,6 @@ dependencies = [ "group", ] -[[package]] -name = "paperclip" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2509afd8f138efe07cd367832289f5cc61d1eb1ec7f1eb75172abca6f7b9b66d" -dependencies = [ - "anyhow", - "itertools 0.10.5", - "once_cell", - "paperclip-actix", - "paperclip-core", - "paperclip-macros", - "semver", - "serde", - "serde_derive", - "serde_json", - "serde_yaml", - "thiserror", - "url", -] - -[[package]] -name = "paperclip-actix" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4adf797da91baee514bc03b020fdd6673d2f8c1af8a859e50d6d803a4b3dddd2" -dependencies = [ - "actix-service", - "actix-web", - "futures", - "mime_guess", - "once_cell", - "paperclip-core", - "paperclip-macros", - "serde_json", -] - -[[package]] -name = "paperclip-core" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8db363c823fa71c00da73ff8cee3d6902e1ad66b770cc224a74dc7cf54de3aad" -dependencies = [ - "actix-web", - "mime", - "once_cell", - "paperclip-macros", - "pin-project-lite", - "regex", - "serde", - "serde_json", - "serde_yaml", - "thiserror", -] - -[[package]] -name = "paperclip-macros" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce6e25ce2c5362c8d48dc89e0f9ca076d507f7c1eabd04f0d593cdf5addff90c" -dependencies = [ - "heck", - "http 0.2.11", - "lazy_static", - "mime", - "proc-macro-error", - "proc-macro2", - "quote", - "strum 0.24.1", - "strum_macros 0.24.3", - "syn 1.0.109", -] - [[package]] name = "parking_lot" version = "0.12.1" @@ -2276,37 +2272,34 @@ dependencies = [ ] [[package]] -name = "proc-macro-error" -version = "1.0.4" +name = "proc-macro2" +version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn 1.0.109", - "version_check", + "unicode-ident", ] [[package]] -name = "proc-macro-error-attr" -version = "1.0.4" +name = "prometheus" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +checksum = "449811d15fbdf5ceb5c1144416066429cf82316e2ec8ce0c1f6f8a02e7bbcf8c" dependencies = [ - "proc-macro2", - "quote", - "version_check", + "cfg-if", + "fnv", + "lazy_static", + "memchr", + "parking_lot", + "protobuf", + "thiserror", ] [[package]] -name = "proc-macro2" -version = "1.0.78" +name = "protobuf" +version = "2.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" -dependencies = [ - "unicode-ident", -] +checksum = "106dd99e98437432fed6519dedecfade6a06a73bb7b2a1e019fdd2bee5778d94" [[package]] name = "quote" @@ -2364,20 +2357,20 @@ dependencies = [ "compact_str", "crossterm", "indoc", - "itertools 0.12.1", + "itertools", "lru", "paste", "stability", - "strum 0.26.1", + "strum", "unicode-segmentation", "unicode-width", ] [[package]] name = "rayon" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4963ed1bc86e4f3ee217022bd855b297cef07fb9eac5dfa1f788b220b49b3bd" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" dependencies = [ "either", "rayon-core", @@ -2404,9 +2397,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.3" +version = "1.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" dependencies = [ "aho-corasick", "memchr", @@ -2452,16 +2445,16 @@ version = "0.11.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6920094eb85afde5e4a138be3f2de8bbdf28000f0029e72c45025a56b042251" dependencies = [ - "base64", + "base64 0.21.5", "bytes", "encoding_rs", "futures-core", "futures-util", - "h2 0.3.22", + "h2 0.3.26", "http 0.2.11", "http-body 0.4.5", "hyper 0.14.27", - "hyper-rustls", + "hyper-rustls 0.24.2", "ipnet", "js-sys", "log", @@ -2469,22 +2462,63 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", - "rustls", - "rustls-pemfile", + "rustls 0.21.10", + "rustls-pemfile 1.0.4", "serde", "serde_json", "serde_urlencoded", "sync_wrapper", "system-configuration", "tokio", - "tokio-rustls", + "tokio-rustls 0.24.1", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots 0.25.3", + "winreg 0.50.0", +] + +[[package]] +name = "reqwest" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e6cc1e89e689536eb5aeede61520e874df5a4707df811cd5da4aa5fbb2aae19" +dependencies = [ + "base64 0.22.0", + "bytes", + "futures-core", + "futures-util", + "http 1.0.0", + "http-body 1.0.0", + "http-body-util", + "hyper 1.2.0", + "hyper-rustls 0.26.0", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls 0.22.3", + "rustls-pemfile 2.1.2", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-rustls 0.25.0", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "webpki-roots", - "winreg", + "webpki-roots 0.26.1", + "winreg 0.52.0", ] [[package]] @@ -2564,19 +2598,49 @@ checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba" dependencies = [ "log", "ring", - "rustls-webpki", + "rustls-webpki 0.101.7", "sct", ] +[[package]] +name = "rustls" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99008d7ad0bbbea527ec27bddbc0e432c5b87d8175178cee68d2eec9c4a1813c" +dependencies = [ + "log", + "ring", + "rustls-pki-types", + "rustls-webpki 0.102.2", + "subtle", + "zeroize", +] + [[package]] name = "rustls-pemfile" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" dependencies = [ - "base64", + "base64 0.21.5", +] + +[[package]] +name = "rustls-pemfile" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" +dependencies = [ + "base64 0.22.0", + "rustls-pki-types", ] +[[package]] +name = "rustls-pki-types" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecd36cc4259e3e4514335c4a138c6b43171a8d61d8f5c9348f9fc7529416f247" + [[package]] name = "rustls-webpki" version = "0.101.7" @@ -2587,6 +2651,17 @@ dependencies = [ "untrusted", ] +[[package]] +name = "rustls-webpki" +version = "0.102.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faaa0a62740bedb9b2ef5afa303da42764c012f743917351dc9a237ea1663610" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + [[package]] name = "rustversion" version = "1.0.14" @@ -2599,6 +2674,15 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "scoped-tls" version = "1.0.1" @@ -2623,9 +2707,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.20" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" +checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" [[package]] name = "serde" @@ -2649,9 +2733,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.114" +version = "1.0.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" +checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" dependencies = [ "itoa", "ryu", @@ -2672,9 +2756,9 @@ dependencies = [ [[package]] name = "serde_yaml" -version = "0.9.32" +version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd075d994154d4a774f95b51fb96bdc2832b0ea48425c92546073816cda1f2f" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ "indexmap", "itoa", @@ -2824,9 +2908,6 @@ name = "spin" version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" -dependencies = [ - "lock_api", -] [[package]] name = "spki" @@ -2838,215 +2919,6 @@ dependencies = [ "der", ] -[[package]] -name = "sqlformat" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce81b7bd7c4493975347ef60d8c7e8b742d4694f4c49f93e0a12ea263938176c" -dependencies = [ - "itertools 0.12.1", - "nom", - "unicode_categories", -] - -[[package]] -name = "sqlx" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dba03c279da73694ef99763320dea58b51095dfe87d001b1d4b5fe78ba8763cf" -dependencies = [ - "sqlx-core", - "sqlx-macros", - "sqlx-mysql", - "sqlx-postgres", - "sqlx-sqlite", -] - -[[package]] -name = "sqlx-core" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d84b0a3c3739e220d94b3239fd69fb1f74bc36e16643423bd99de3b43c21bfbd" -dependencies = [ - "ahash", - "atoi", - "byteorder", - "bytes", - "crc", - "crossbeam-queue", - "dotenvy", - "either", - "event-listener", - "futures-channel", - "futures-core", - "futures-intrusive", - "futures-io", - "futures-util", - "hashlink", - "hex", - "indexmap", - "log", - "memchr", - "once_cell", - "paste", - "percent-encoding", - "serde", - "serde_json", - "sha2", - "smallvec", - "sqlformat", - "thiserror", - "time", - "tracing", - "url", -] - -[[package]] -name = "sqlx-macros" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89961c00dc4d7dffb7aee214964b065072bff69e36ddb9e2c107541f75e4f2a5" -dependencies = [ - "proc-macro2", - "quote", - "sqlx-core", - "sqlx-macros-core", - "syn 1.0.109", -] - -[[package]] -name = "sqlx-macros-core" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0bd4519486723648186a08785143599760f7cc81c52334a55d6a83ea1e20841" -dependencies = [ - "atomic-write-file", - "dotenvy", - "either", - "heck", - "hex", - "once_cell", - "proc-macro2", - "quote", - "serde", - "serde_json", - "sha2", - "sqlx-core", - "sqlx-mysql", - "sqlx-postgres", - "sqlx-sqlite", - "syn 1.0.109", - "tempfile", - "url", -] - -[[package]] -name = "sqlx-mysql" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e37195395df71fd068f6e2082247891bc11e3289624bbc776a0cdfa1ca7f1ea4" -dependencies = [ - "atoi", - "base64", - "bitflags 2.4.1", - "byteorder", - "bytes", - "crc", - "digest", - "dotenvy", - "either", - "futures-channel", - "futures-core", - "futures-io", - "futures-util", - "generic-array", - "hex", - "hkdf", - "hmac", - "itoa", - "log", - "md-5", - "memchr", - "once_cell", - "percent-encoding", - "rand", - "rsa", - "serde", - "sha1", - "sha2", - "smallvec", - "sqlx-core", - "stringprep", - "thiserror", - "time", - "tracing", - "whoami", -] - -[[package]] -name = "sqlx-postgres" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6ac0ac3b7ccd10cc96c7ab29791a7dd236bd94021f31eec7ba3d46a74aa1c24" -dependencies = [ - "atoi", - "base64", - "bitflags 2.4.1", - "byteorder", - "crc", - "dotenvy", - "etcetera", - "futures-channel", - "futures-core", - "futures-io", - "futures-util", - "hex", - "hkdf", - "hmac", - "home", - "itoa", - "log", - "md-5", - "memchr", - "once_cell", - "rand", - "serde", - "serde_json", - "sha1", - "sha2", - "smallvec", - "sqlx-core", - "stringprep", - "thiserror", - "time", - "tracing", - "whoami", -] - -[[package]] -name = "sqlx-sqlite" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "210976b7d948c7ba9fced8ca835b11cbb2d677c59c79de41ac0d397e14547490" -dependencies = [ - "atoi", - "flume", - "futures-channel", - "futures-core", - "futures-executor", - "futures-intrusive", - "futures-util", - "libsqlite3-sys", - "log", - "percent-encoding", - "serde", - "sqlx-core", - "time", - "tracing", - "url", - "urlencoding", -] - [[package]] name = "stability" version = "0.1.1" @@ -3063,49 +2935,19 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" -[[package]] -name = "stringprep" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb41d74e231a107a1b4ee36bd1214b11285b77768d2e3824aedafa988fd36ee6" -dependencies = [ - "finl_unicode", - "unicode-bidi", - "unicode-normalization", -] - [[package]] name = "strsim" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" -[[package]] -name = "strum" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" - [[package]] name = "strum" version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "723b93e8addf9aa965ebe2d11da6d7540fa2283fcea14b3371ff055f7ba13f5f" dependencies = [ - "strum_macros 0.26.1", -] - -[[package]] -name = "strum_macros" -version = "0.24.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "rustversion", - "syn 1.0.109", + "strum_macros", ] [[package]] @@ -3114,7 +2956,7 @@ version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a3417fc93d76740d974a01654a09777cb500428cc874ca9f45edfe0c4d4cd18" dependencies = [ - "heck", + "heck 0.4.1", "proc-macro2", "quote", "rustversion", @@ -3157,9 +2999,9 @@ checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" [[package]] name = "sysinfo" -version = "0.30.5" +version = "0.30.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fb4f3438c8f6389c864e61221cbc97e9bca98b4daf39a5beb7bea660f528bb2" +checksum = "26d7c217777061d5a2d652aea771fb9ba98b6dade657204b08c4b9604d11555b" dependencies = [ "cfg-if", "core-foundation-sys", @@ -3320,9 +3162,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.36.0" +version = "1.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931" +checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" dependencies = [ "backtrace", "bytes", @@ -3354,7 +3196,18 @@ version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" dependencies = [ - "rustls", + "rustls 0.21.10", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" +dependencies = [ + "rustls 0.22.3", + "rustls-pki-types", "tokio", ] @@ -3366,11 +3219,11 @@ checksum = "212d5dcb2a1ce06d81107c3d0ffa3121fe974b73f068c8282cb1c32328113b6c" dependencies = [ "futures-util", "log", - "rustls", + "rustls 0.21.10", "tokio", - "tokio-rustls", + "tokio-rustls 0.24.1", "tungstenite", - "webpki-roots", + "webpki-roots 0.25.3", ] [[package]] @@ -3510,7 +3363,7 @@ dependencies = [ "httparse", "log", "rand", - "rustls", + "rustls 0.21.10", "sha1", "thiserror", "url", @@ -3523,15 +3376,6 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" -[[package]] -name = "unicase" -version = "2.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" -dependencies = [ - "version_check", -] - [[package]] name = "unicode-bidi" version = "0.3.13" @@ -3565,17 +3409,11 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" -[[package]] -name = "unicode_categories" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" - [[package]] name = "unsafe-libyaml" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab4c90930b95a82d00dc9e9ac071b4991924390d46cbd0dfe566148667605e4b" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" [[package]] name = "untrusted" @@ -3614,9 +3452,9 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "uuid" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a" +checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0" dependencies = [ "getrandom", ] @@ -3627,18 +3465,22 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - [[package]] name = "version_check" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "want" version = "0.3.1" @@ -3736,6 +3578,15 @@ version = "0.25.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1778a42e8b3b90bff8d0f5032bf22250792889a5cdc752aa0020c84abe3aaf10" +[[package]] +name = "webpki-roots" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3de34ae270483955a94f4b21bdaaeb83d508bb84a01435f393818edb0012009" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "which" version = "4.4.2" @@ -3748,12 +3599,6 @@ dependencies = [ "rustix", ] -[[package]] -name = "whoami" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22fc3756b8a9133049b26c7f61ab35416c130e8c09b660f5b3958b446f52cc50" - [[package]] name = "winapi" version = "0.3.9" @@ -3770,6 +3615,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +dependencies = [ + "winapi", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -4021,6 +3875,16 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "winreg" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + [[package]] name = "wyz" version = "0.5.1" @@ -4083,3 +3947,31 @@ dependencies = [ "quote", "syn 2.0.52", ] + +[[package]] +name = "zstd" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d789b1514203a1120ad2429eae43a7bd32b90976a7bb8a05f7ec02fa88cc23a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cd99b45c6bc03a018c8b8a86025678c87e55526064e38f9df301989dce7ec0a" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.10+zstd.1.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c253a4914af5bafc8fa8c86ee400827e83cf6ec01195ec1f1ed8441bf00d65aa" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/Cargo.toml b/Cargo.toml index 01247b6..98950f7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dg_fast_farmer" -version = "1.0.2" +version = "1.0.3" edition = "2021" authors = ["James Hoerr"] description = "A lite farmer for the Chia Blockchain." @@ -17,39 +17,49 @@ codegen-units = 1 lto = "fat" [dependencies] -async-trait = "0.1.77" +actix-web = "4.5.1" +async-trait = "0.1.79" bip39 = {version= "2.0.0", features=["rand"] } -blst = "0.3.7" +blst = "0.3.11" bytefmt = "0.1.7" -clap = { version = "4.5.1", features = ["derive"] } -chrono = "0.4.34" +clap = { version = "4.5.4", features = ["derive"] } +chrono = "0.4.37" crossterm = "0.27.0" dialoguer = "0.11.0" -dg_xch_cli = { version= "2.0.2" } -dg_xch_clients = { version= "2.0.2" } -dg_xch_core = { version = "2.0.2", features = ["paperclip"] } -dg_xch_keys = { version= "2.0.2" } -dg_xch_macros = { version= "2.0.2" } -dg_xch_pos = { version= "2.0.2" } -dg_xch_puzzles = { version= "2.0.2" } -dg_xch_serialize = { version= "2.0.2" } -futures-util = "0.3.29" +dg_xch_cli = { path="./dg_xch_utils/cli", version="2.0.3" } +dg_xch_clients = { path="./dg_xch_utils/clients", version="2.0.3", features = ["metrics"] } +dg_xch_core = { path="./dg_xch_utils/core", version = "2.0.3", default-features = false, features = ["metrics"] } +dg_xch_keys = { path="./dg_xch_utils/keys", version="2.0.3" } +dg_xch_macros = { path="./dg_xch_utils/macros", version="2.0.3" } +dg_xch_pos = { path="./dg_xch_utils/proof_of_space", version="2.0.3" } +dg_xch_puzzles = { path="./dg_xch_utils/puzzles", version="2.0.3" } +dg_xch_serialize = { path="./dg_xch_utils/serialize", version="2.0.3" } +dg_xch_servers = { path="./dg_xch_utils/servers", version="2.0.3", features = ["metrics"] } +futures-util = "0.3.30" hex = "0.4.3" home = "0.5.9" -log = "0.4.20" +libc = "0.2.153" +log = "0.4.21" +notify = "6.1.1" +num-bigint = "0.4.4" once_cell = "1.19.0" +prometheus = {version="0.13.3", features=["protobuf"]} +protobuf = "2.27.1" rand = { version = "0.8.5", features = [] } ratatui = "0.26.1" -reqwest = {version="0.11.24", default-features = false, features =["rustls-tls", "json"]} +rayon = "1.10.0" +reqwest = {version="0.12.3", default-features = false, features =["rustls-tls", "json"]} serde = {version="1.0.197", features = ["derive"]} -serde_json = "1.0.114" -serde_yaml = "0.9.32" +serde_json = "1.0.115" +serde_yaml = "0.9.33" simple_logger = "4.3.3" -sysinfo = "0.30.5" -tokio = {version = "1.36.0", features=["rt-multi-thread", "sync", "signal", "macros", "process", "time", "fs", "net"]} +sysinfo = "0.30.9" +tokio = {version = "1.37.0", features=["rt-multi-thread", "sync", "signal", "macros", "process", "time", "fs", "net"]} tokio-tungstenite = {version = "0.20.1", features = ["rustls-tls-webpki-roots", "rustls"] } -tui-logger = {version = "0.11.0"} -uuid = {version="1.7.0", features=["v4"]} +tui-logger = {version = "0.11.0", default-features = false} +uuid = {version="1.8.0", features=["v4"]} +num-integer = "0.1.46" +regex = "1.10.4" [build-dependencies] bindgen = "0.69.4" \ No newline at end of file diff --git a/dg_xch_utils b/dg_xch_utils new file mode 160000 index 0000000..28ec0e2 --- /dev/null +++ b/dg_xch_utils @@ -0,0 +1 @@ +Subproject commit 28ec0e27e7f2b273553deeabfd10d63411a4e1d7 diff --git a/src/cli/commands.rs b/src/cli/commands.rs new file mode 100644 index 0000000..c6c83a1 --- /dev/null +++ b/src/cli/commands.rs @@ -0,0 +1,496 @@ +use crate::cli::prompts::{ + prompt_for_farming_fullnode, prompt_for_farming_port, prompt_for_launcher_id, + prompt_for_mnemonic, prompt_for_payout_address, prompt_for_plot_directories, + prompt_for_rpc_fullnode, prompt_for_rpc_port, prompt_for_ssl_path, +}; +use crate::cli::utils::{init_logger, rpc_client_from_config}; +use crate::farmer::config::{load_keys, Config, DruidGardenHarvesterConfig, FarmingInfo}; +use crate::farmer::{ExtendedFarmerSharedState, Farmer}; +use crate::metrics::Metrics; +use crate::tasks::blockchain_state_updater::update_blockchain; +use crate::tasks::pool_state_updater::pool_updater; +use crate::{gui, metrics, HEADERS}; +use actix_web::web::Data; +use actix_web::{App, HttpServer}; +use dg_xch_cli::wallet_commands::migrate_plot_nft; +use dg_xch_cli::wallets::plotnft_utils::{get_plotnft_by_launcher_id, scrounge_for_plotnfts}; +use dg_xch_clients::api::pool::DefaultPoolClient; +use dg_xch_clients::rpc::full_node::FullnodeClient; +use dg_xch_core::blockchain::sized_bytes::{Bytes32, Bytes48}; +use dg_xch_core::config::PoolWalletConfig; +use dg_xch_core::consensus::constants::{CONSENSUS_CONSTANTS_MAP, MAINNET}; +use dg_xch_core::plots::PlotNft; +use dg_xch_core::protocols::farmer::{FarmerMetrics, FarmerSharedState}; +use dg_xch_core::utils::await_termination; +use dg_xch_keys::{ + key_from_mnemonic, master_sk_to_farmer_sk, master_sk_to_pool_sk, + master_sk_to_pooling_authentication_sk, master_sk_to_singleton_owner_sk, + master_sk_to_wallet_sk, master_sk_to_wallet_sk_unhardened, parse_payout_address, +}; +use dg_xch_puzzles::clvm_puzzles::launcher_id_to_p2_puzzle_hash; +use dg_xch_puzzles::p2_delegated_puzzle_or_hidden_puzzle::puzzle_hash_for_pk; +use dialoguer::Confirm; +use hex::encode; +use log::{info, warn}; +use std::collections::HashMap; +use std::io::{Error, ErrorKind}; +use std::path::{Path, PathBuf}; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; +use tokio::join; +use tokio::sync::RwLock; +use tokio::task::JoinHandle; + +pub(crate) async fn tui_mode(config_path: &Path) -> Result<(), Error> { + let config = Config::try_from(config_path).unwrap_or_default(); + let config_arc = Arc::new(config); + gui::bootstrap(config_arc).await?; + Ok(()) +} + +pub(crate) async fn cli_mode(config_path: &Path) -> Result<(), Error> { + init_logger(); + let config = Config::try_from(config_path)?; + let config_arc = Arc::new(config); + let constants = CONSENSUS_CONSTANTS_MAP + .get(&config_arc.selected_network) + .unwrap_or(&MAINNET); + info!( + "Selected Network: {}, AggSig: {}", + &config_arc.selected_network, + &encode(&constants.agg_sig_me_additional_data) + ); + let (farmer_private_keys, owner_secret_keys, auth_secret_keys, pool_public_keys) = + load_keys(config_arc.clone()).await; + let extended_metrics = Arc::new(Metrics::default()); + let farmer_metrics = FarmerMetrics::new(&*extended_metrics.registry.read().await); + let shared_state = Arc::new(FarmerSharedState { + farmer_private_keys: Arc::new(farmer_private_keys), + owner_secret_keys: Arc::new(owner_secret_keys), + owner_public_keys_to_auth_secret_keys: Arc::new(auth_secret_keys), + pool_public_keys: Arc::new(pool_public_keys), + data: Arc::new(ExtendedFarmerSharedState { + config: config_arc.clone(), + run: Arc::new(AtomicBool::new(true)), + extended_metrics, + ..Default::default() + }), + metrics: Arc::new(RwLock::new(Some(farmer_metrics))), + ..Default::default() + }); + + info!("Using Additional Headers: {:?}", &*HEADERS); + //Pool Updater vars + let pool_state = shared_state.clone(); + let pool_state_handle: JoinHandle<()> = + tokio::spawn(async move { pool_updater(pool_state).await }); + + //Signal Handler to shut down the Async processes + let signal_run = shared_state.data.run.clone(); + let signal_handle = tokio::spawn(async move { + let _ = await_termination().await; + signal_run.store(false, Ordering::Relaxed); + }); + + let pool_client = Arc::new(DefaultPoolClient::new()); + let farmer = Farmer::new(shared_state.clone(), pool_client).await?; + //Client Vars + let client_handle: JoinHandle> = tokio::spawn(async move { + farmer.run().await; + Ok(()) + }); + let fn_shared_state = shared_state.clone(); + let fullnode_thread = tokio::spawn(async move { + update_blockchain(config_arc.clone(), fn_shared_state.clone()).await + }); + let metrics = shared_state.data.config.metrics.clone().unwrap_or_default(); + info!("Metrics: {} on port: {}", metrics.enabled, metrics.port); + let server_handle = if metrics.enabled { + tokio::spawn( + HttpServer::new(move || { + App::new() + .app_data(Data::new(shared_state.clone())) + .configure(metrics::init) + }) + .bind(("0.0.0.0", metrics.port))? + .run(), + ) + } else { + tokio::spawn(async { Ok::<(), Error>(()) }) + }; + let _ = join!( + pool_state_handle, + client_handle, + signal_handle, + server_handle, + fullnode_thread + ); + Ok(()) +} + +pub struct GenerateConfig { + pub output_path: Option, + pub mnemonic_file: Option, + pub fullnode_ws_host: Option, + pub fullnode_ws_port: Option, + pub fullnode_rpc_host: Option, + pub fullnode_rpc_port: Option, + pub fullnode_ssl: Option, + pub network: Option, + pub launcher_id: Option, + pub payout_address: Option, + pub plot_directories: Option>, + pub additional_headers: Option>, +} + +pub async fn generate_config_from_mnemonic(gen_settings: GenerateConfig) -> Result { + if let Some(op) = &gen_settings.output_path { + if op.exists() + && !Confirm::new() + .with_prompt(format!( + "An existing config exists at {:?}, would you like to override it? (Y/N)", + op + )) + .interact() + .map_err(|e| { + Error::new( + ErrorKind::Interrupted, + format!("Dialog Interrupted: {:?}", e), + ) + })? + { + return Err(Error::new(ErrorKind::Interrupted, "User Canceled")); + } + } + let mut config = Config::default(); + let network = gen_settings + .network + .map(|v| { + if CONSENSUS_CONSTANTS_MAP.contains_key(&v) { + v + } else { + "mainnet".to_string() + } + }) + .unwrap_or("mainnet".to_string()); + config.selected_network = network; + let master_key = key_from_mnemonic(&prompt_for_mnemonic(gen_settings.mnemonic_file)?)?; + config.payout_address = prompt_for_payout_address(gen_settings.payout_address)?.to_string(); + config.fullnode_ws_host = + prompt_for_farming_fullnode(gen_settings.fullnode_ws_host)?.to_string(); + config.fullnode_rpc_host = if let Some(host) = gen_settings.fullnode_rpc_host { + host + } else if "chia-proxy.evergreenminer-prod.com" == config.fullnode_ws_host { + "chia-proxy.evergreenminer-prod.com".to_string() + } else { + prompt_for_rpc_fullnode(None)? + }; + config.fullnode_ws_port = if let Some(port) = gen_settings.fullnode_ws_port { + port + } else if "chia-proxy.evergreenminer-prod.com" == config.fullnode_ws_host { + 443 + } else { + 8444 + }; + config.fullnode_rpc_port = if let Some(port) = gen_settings.fullnode_rpc_port { + port + } else if "chia-proxy.evergreenminer-prod.com" == config.fullnode_rpc_host { + 443 + } else { + 8555 + }; + config.ssl_root_path = if "chia-proxy.evergreenminer-prod.com" == config.fullnode_ws_host { + None + } else { + prompt_for_ssl_path(gen_settings.fullnode_ssl)? + }; + config.harvester_configs.druid_garden = Some(DruidGardenHarvesterConfig { + plot_directories: if let Some(dirs) = gen_settings.plot_directories { + dirs + } else { + prompt_for_plot_directories()? + }, + }); + let client = rpc_client_from_config(&config, &gen_settings.additional_headers); + let mut page = 0; + let mut plotnfts = vec![]; + if let Some(launcher_id) = prompt_for_launcher_id(gen_settings.launcher_id)? { + info!("Searching for NFT with LauncherID: {launcher_id}"); + if let Some(plotnft) = + get_plotnft_by_launcher_id(client.clone(), &launcher_id, None).await? + { + plotnfts.push(plotnft); + } else { + return Err(Error::new( + ErrorKind::NotFound, + "Failed to find a plotNFT with LauncherID: {launcher_id}", + )); + } + } else { + info!("No LauncherID Specified, Searching for PlotNFTs..."); + while page < 50 && plotnfts.is_empty() { + let mut puzzle_hashes = vec![]; + for index in page * 50..(page + 1) * 50 { + let wallet_sk = + master_sk_to_wallet_sk_unhardened(&master_key, index).map_err(|e| { + Error::new( + ErrorKind::InvalidInput, + format!("Failed to parse Wallet SK: {:?}", e), + ) + })?; + let pub_key: Bytes48 = wallet_sk.sk_to_pk().to_bytes().into(); + puzzle_hashes.push(puzzle_hash_for_pk(&pub_key)?); + let hardened_wallet_sk = + master_sk_to_wallet_sk(&master_key, index).map_err(|e| { + Error::new( + ErrorKind::InvalidInput, + format!("Failed to parse Wallet SK: {:?}", e), + ) + })?; + let pub_key: Bytes48 = hardened_wallet_sk.sk_to_pk().to_bytes().into(); + puzzle_hashes.push(puzzle_hash_for_pk(&pub_key)?); + } + plotnfts.extend(scrounge_for_plotnfts(client.clone(), &puzzle_hashes).await?); + page += 1; + } + } + for plot_nft in plotnfts { + config.pool_info.push(PoolWalletConfig { + launcher_id: plot_nft.launcher_id, + pool_url: plot_nft.pool_state.pool_url.unwrap_or_default(), + target_puzzle_hash: plot_nft.pool_state.target_puzzle_hash, + payout_instructions: config.payout_address.clone(), + p2_singleton_puzzle_hash: launcher_id_to_p2_puzzle_hash( + &plot_nft.launcher_id, + plot_nft.delay_time as u64, + &plot_nft.delay_puzzle_hash, + )?, + owner_public_key: plot_nft.pool_state.owner_pubkey, + difficulty: None, + }); + let mut owner_key = None; + let mut auth_key = None; + for i in 0..150 { + let key = master_sk_to_singleton_owner_sk(&master_key, i).unwrap(); + let pub_key: Bytes48 = key.sk_to_pk().to_bytes().into(); + if pub_key == plot_nft.pool_state.owner_pubkey { + let a_key = master_sk_to_pooling_authentication_sk(&master_key, i, 0).unwrap(); + owner_key = Some(key.into()); + auth_key = Some(a_key.into()); + break; + } + } + if let Some(info) = config.farmer_info.iter_mut().find(|f| { + if let Some(l) = &f.launcher_id { + l == &plot_nft.launcher_id + } else { + false + } + }) { + info.farmer_secret_key = master_sk_to_farmer_sk(&master_key)?.into(); + info.launcher_id = Some(plot_nft.launcher_id); + info.pool_secret_key = Some(master_sk_to_pool_sk(&master_key)?.into()); + info.owner_secret_key = owner_key; + info.auth_secret_key = auth_key; + } else { + config.farmer_info.push(FarmingInfo { + farmer_secret_key: master_sk_to_farmer_sk(&master_key)?.into(), + launcher_id: Some(plot_nft.launcher_id), + pool_secret_key: Some(master_sk_to_pool_sk(&master_key)?.into()), + owner_secret_key: owner_key, + auth_secret_key: auth_key, + }); + } + } + if config.farmer_info.is_empty() { + warn!("No PlotNFT Found"); + config.farmer_info.push(FarmingInfo { + farmer_secret_key: master_sk_to_farmer_sk(&master_key)?.into(), + launcher_id: None, + pool_secret_key: Some(master_sk_to_pool_sk(&master_key)?.into()), + owner_secret_key: None, + auth_secret_key: None, + }); + } + if let Some(op) = &gen_settings.output_path { + config.save_as_yaml(op)?; + } + Ok(config) +} + +pub async fn update_pool_info( + config: Config, + launcher_id: Option, +) -> Result { + let client = rpc_client_from_config(&config, &None); + #[inline] + async fn handle_launcher_id( + plot_nfts: &mut Vec, + client: Arc, + launcher_id: Bytes32, + ) -> Result<(), Error> { + info!( + "Fetching current PlotNFT state for launcher id {} ...", + launcher_id.to_string() + ); + plot_nfts.extend(get_plotnft_by_launcher_id(client.clone(), &launcher_id, None).await?); + Ok(()) + } + let mut plot_nfts = vec![]; + for farmer_info in &config.farmer_info { + if let Some(farmer_launcher_id) = farmer_info.launcher_id { + if let Some(input_launcher_id) = &launcher_id { + if Bytes32::from(input_launcher_id) == farmer_launcher_id { + handle_launcher_id(&mut plot_nfts, client.clone(), farmer_launcher_id).await?; + } + } else { + handle_launcher_id(&mut plot_nfts, client.clone(), farmer_launcher_id).await?; + } + } + } + if plot_nfts.is_empty() && launcher_id.is_some() { + return Err(Error::new( + ErrorKind::InvalidInput, + format!( + "Failed to Find a PlotNFT with Launcher ID: {}", + launcher_id.expect("Checked Some Above") + ), + )); + } + let mut updated_config = config.clone(); + for plot_nft in plot_nfts { + if let Some(pool_wallet) = updated_config + .pool_info + .iter_mut() + .find(|pw| pw.launcher_id == plot_nft.launcher_id) + { + let old_pool_wallet = pool_wallet.clone(); + pool_wallet.pool_url = plot_nft.pool_state.pool_url.unwrap_or_default(); + if pool_wallet.target_puzzle_hash != plot_nft.pool_state.target_puzzle_hash { + // Reset diff on pool change + pool_wallet.difficulty = None; + } + pool_wallet.target_puzzle_hash = plot_nft.pool_state.target_puzzle_hash; + pool_wallet.owner_public_key = plot_nft.pool_state.owner_pubkey; + + let mut change_messages: Vec = vec![]; + if old_pool_wallet.pool_url != pool_wallet.pool_url { + change_messages.push(format!( + "from {} to {}", + old_pool_wallet.pool_url, pool_wallet.pool_url + )); + } + if old_pool_wallet.target_puzzle_hash != pool_wallet.target_puzzle_hash { + change_messages.push(format!( + "from PH {} to PH {}", + old_pool_wallet.target_puzzle_hash, pool_wallet.target_puzzle_hash + )); + } + if change_messages.is_empty() { + info!( + "PlotNFT state for launcher id {} did not change", + plot_nft.launcher_id.to_string(), + ); + } else { + info!( + "PlotNFT state for launcher id {} did change {}", + plot_nft.launcher_id.to_string(), + change_messages.join(" and "), + ); + } + } + } + Ok(updated_config) +} + +pub async fn join_pool( + config: Config, + pool_url: String, + mnemonic_file: Option, + launcher_id: Option, + fee: Option, +) -> Result { + let client = rpc_client_from_config(&config, &None); + let mnemonic = prompt_for_mnemonic(mnemonic_file)?; + let mut found = false; + let owner_ph = Bytes32::from(parse_payout_address(&config.payout_address)?); + for farmer_info in &config.farmer_info { + if let Some(farmer_launcher_id) = farmer_info.launcher_id { + if let Some(selected_launcher_id) = &launcher_id { + if Bytes32::from(selected_launcher_id) == farmer_launcher_id { + migrate_plot_nft( + client.clone(), + &pool_url, + &farmer_launcher_id, + &owner_ph, + &mnemonic.to_string(), + CONSENSUS_CONSTANTS_MAP + .get(&config.selected_network) + .cloned() + .unwrap_or(MAINNET.clone()), + fee.unwrap_or_default(), + ) + .await?; + found = true; + } + } else { + migrate_plot_nft( + client.clone(), + &pool_url, + &farmer_launcher_id, + &owner_ph, + &mnemonic.to_string(), + CONSENSUS_CONSTANTS_MAP + .get(&config.selected_network) + .cloned() + .unwrap_or(MAINNET.clone()), + fee.unwrap_or_default(), + ) + .await?; + found = true; + } + } + } + if !found && launcher_id.is_some() { + return Err(Error::new( + ErrorKind::InvalidInput, + format!( + "Failed to Find a PlotNFT with Launcher ID: {}", + launcher_id.expect("Checked Some Above") + ), + )); + } + update_pool_info(config, launcher_id).await +} +pub async fn update(config: Config) -> Result { + let mut config = config; + config.payout_address = + prompt_for_payout_address(Some(config.payout_address.clone()))?.to_string(); + config.fullnode_ws_host = + prompt_for_farming_fullnode(Some(config.fullnode_ws_host.clone()))?.to_string(); + config.fullnode_rpc_host = + prompt_for_rpc_fullnode(Some(config.fullnode_ws_host.clone()))?.to_string(); + config.fullnode_ws_port = prompt_for_farming_port( + if "chia-proxy.evergreenminer-prod.com" == config.fullnode_ws_host { + Some(443) + } else { + Some(config.fullnode_ws_port) + }, + )?; + config.fullnode_rpc_port = prompt_for_rpc_port( + if "chia-proxy.evergreenminer-prod.com" == config.fullnode_rpc_host { + Some(443) + } else { + Some(config.fullnode_rpc_port) + }, + )?; + config.ssl_root_path = prompt_for_ssl_path(config.ssl_root_path)?; + config.harvester_configs.druid_garden = Some(DruidGardenHarvesterConfig { + plot_directories: if let Some(gh) = config.harvester_configs.druid_garden { + gh.plot_directories + } else { + prompt_for_plot_directories()? + }, + }); + update_pool_info(config, None).await +} diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 63c8599..b765b7b 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -1,32 +1,10 @@ -use crate::farmer::config::{Config, DruidGardenHarvesterConfig, FarmingInfo}; -use crate::farmer::ExtendedFarmerSharedState; -use bip39::Mnemonic; +pub(crate) mod commands; +mod prompts; +pub(crate) mod utils; + use clap::{Parser, Subcommand}; -use dg_xch_cli::wallet_commands::migrate_plot_nft; -use dg_xch_cli::wallets::plotnft_utils::{get_plotnft_by_launcher_id, scrounge_for_plotnfts}; -use dg_xch_clients::rpc::full_node::FullnodeClient; -use dg_xch_clients::ClientSSLConfig; -use dg_xch_core::blockchain::sized_bytes::{Bytes32, Bytes48}; -use dg_xch_core::config::PoolWalletConfig; -use dg_xch_core::consensus::constants::CONSENSUS_CONSTANTS_MAP; -use dg_xch_core::protocols::farmer::FarmerSharedState; -use dg_xch_keys::{ - key_from_mnemonic, master_sk_to_farmer_sk, master_sk_to_pool_sk, - master_sk_to_pooling_authentication_sk, master_sk_to_singleton_owner_sk, - master_sk_to_wallet_sk, master_sk_to_wallet_sk_unhardened, -}; -use dg_xch_puzzles::clvm_puzzles::launcher_id_to_p2_puzzle_hash; -use dg_xch_puzzles::p2_delegated_puzzle_or_hidden_puzzle::puzzle_hash_for_pk; -use dialoguer::theme::ColorfulTheme; -use dialoguer::{Confirm, Input}; -use home::home_dir; -use log::{info, warn}; -use std::collections::HashMap; -use std::fs; -use std::io::{Error, ErrorKind}; -use std::path::{Path, PathBuf}; +use std::io::Error; use std::str::FromStr; -use std::sync::Arc; #[derive(Parser, Debug)] #[command(author, version, about, long_about = None)] @@ -37,10 +15,29 @@ pub struct Cli { pub config: Option, } +#[derive(Default, Debug, Copy, Clone, Subcommand)] +pub enum RunMode { + Cli, + #[default] + Tui, +} +impl FromStr for RunMode { + type Err = Error; + fn from_str(s: &str) -> Result { + Ok(match s.to_ascii_lowercase().as_str() { + "cli" => RunMode::Cli, + "tui" => RunMode::Tui, + _ => RunMode::default(), + }) + } +} + #[derive(Debug, Subcommand)] pub enum Action { - Gui {}, - Run {}, + Run { + #[arg(short = 'm', long)] + mode: Option, + }, Init { #[arg(short = 'f', long)] fullnode_ws_host: Option, @@ -63,12 +60,20 @@ pub enum Action { #[arg(short = 'l', long)] launcher_id: Option, }, - UpdatePoolInfo {}, + Update {}, + UpdatePoolInfo { + #[arg(short = 'l', long)] + launcher_id: Option, + }, + UpdatePayoutAddress { + #[arg(short = 'a', long)] + address: String, + }, JoinPool { #[arg(short = 'u', long)] pool_url: String, #[arg(short = 'm', long)] - mnemonic: String, + mnemonic_file: Option, #[arg(short = 'i', long)] launcher_id: Option, #[arg(short = 'f', long)] @@ -77,379 +82,8 @@ pub enum Action { } impl Default for Action { fn default() -> Self { - Action::Gui {} - } -} - -pub struct GenerateConfig { - pub output_path: Option, - pub mnemonic: Mnemonic, - pub fullnode_ws_host: Option, - pub fullnode_ws_port: Option, - pub fullnode_rpc_host: Option, - pub fullnode_rpc_port: Option, - pub fullnode_ssl: Option, - pub network: Option, - pub launcher_id: Option, - pub payout_address: Option, - pub plot_directories: Option>, - pub additional_headers: Option>, -} - -fn get_root_path() -> PathBuf { - let prefix = home_dir().unwrap_or(Path::new("/").to_path_buf()); - prefix.as_path().join(Path::new(".config/fast_farmer/")) -} - -pub fn get_config_path() -> PathBuf { - get_root_path() - .as_path() - .join(Path::new("fast_farmer.yaml")) -} - -pub fn get_ssl_root_path(shared_state: &FarmerSharedState) -> PathBuf { - if let Some(ssl_root_path) = &shared_state.data.config.ssl_root_path { - PathBuf::from(ssl_root_path) - } else { - get_root_path().as_path().join(Path::new("ssl/")) - } -} - -pub async fn generate_config_from_mnemonic(gen_settings: GenerateConfig) -> Result { - if let Some(op) = &gen_settings.output_path { - if op.exists() - && !Confirm::new() - .with_prompt(format!( - "An existing config exists at {:?}, would you like to override it? (Y/N)", - op - )) - .interact() - .map_err(|e| { - Error::new( - ErrorKind::Interrupted, - format!("Dialog Interrupted: {:?}", e), - ) - })? - { - return Err(Error::new(ErrorKind::Interrupted, "User Canceled")); + Action::Run { + mode: Some(RunMode::default()), } } - let mut config = Config::default(); - let network = gen_settings - .network - .map(|v| { - if CONSENSUS_CONSTANTS_MAP.contains_key(&v) { - v - } else { - "mainnet".to_string() - } - }) - .unwrap_or("mainnet".to_string()); - config.selected_network = network; - config.payout_address = gen_settings.payout_address.unwrap_or_default(); - config.harvester_configs.druid_garden = Some(DruidGardenHarvesterConfig { - plot_directories: gen_settings.plot_directories.unwrap_or_default(), - }); - let master_key = key_from_mnemonic(&gen_settings.mnemonic)?; - config.fullnode_ws_host = gen_settings - .fullnode_ws_host - .clone() - .unwrap_or(String::from("localhost")); - config.fullnode_rpc_host = gen_settings.fullnode_rpc_host.unwrap_or( - gen_settings - .fullnode_ws_host - .unwrap_or(String::from("localhost")), - ); - config.fullnode_ws_port = gen_settings.fullnode_ws_port.unwrap_or(8444); - config.fullnode_rpc_port = gen_settings.fullnode_rpc_port.unwrap_or(8555); - config.ssl_root_path = gen_settings.fullnode_ssl.clone(); - let client = Arc::new(FullnodeClient::new( - &config.fullnode_rpc_host, - config.fullnode_rpc_port, - 60, - gen_settings.fullnode_ssl.map(|s| ClientSSLConfig { - ssl_crt_path: Path::new(&s) - .join("daemon/private_daemon.crt") - .to_string_lossy() - .to_string(), - ssl_key_path: Path::new(&s) - .join("daemon/private_daemon.key") - .to_string_lossy() - .to_string(), - ssl_ca_crt_path: Path::new(&s) - .join("ca/private_ca.crt") - .to_string_lossy() - .to_string(), - }), - &gen_settings.additional_headers, - )); - let mut page = 0; - let mut plotnfts = vec![]; - if let Some(launcher_id) = gen_settings.launcher_id { - info!("Searching for NFT with LauncherID: {launcher_id}"); - if let Some(plotnft) = get_plotnft_by_launcher_id(client.clone(), &launcher_id).await? { - plotnfts.push(plotnft); - } else { - return Err(Error::new( - ErrorKind::NotFound, - "Failed to find a plotNFT with LauncherID: {launcher_id}", - )); - } - } else { - info!("No LauncherID Specified, Searching for PlotNFTs..."); - while page < 50 && plotnfts.is_empty() { - let mut puzzle_hashes = vec![]; - for index in page * 50..(page + 1) * 50 { - let wallet_sk = - master_sk_to_wallet_sk_unhardened(&master_key, index).map_err(|e| { - Error::new( - ErrorKind::InvalidInput, - format!("Failed to parse Wallet SK: {:?}", e), - ) - })?; - let pub_key: Bytes48 = wallet_sk.sk_to_pk().to_bytes().into(); - puzzle_hashes.push(puzzle_hash_for_pk(&pub_key)?); - let hardened_wallet_sk = - master_sk_to_wallet_sk(&master_key, index).map_err(|e| { - Error::new( - ErrorKind::InvalidInput, - format!("Failed to parse Wallet SK: {:?}", e), - ) - })?; - let pub_key: Bytes48 = hardened_wallet_sk.sk_to_pk().to_bytes().into(); - puzzle_hashes.push(puzzle_hash_for_pk(&pub_key)?); - } - plotnfts.extend(scrounge_for_plotnfts(client.clone(), &puzzle_hashes).await?); - page += 1; - } - } - for plot_nft in plotnfts { - config.pool_info.push(PoolWalletConfig { - launcher_id: plot_nft.launcher_id, - pool_url: plot_nft.pool_state.pool_url.unwrap_or_default(), - target_puzzle_hash: plot_nft.pool_state.target_puzzle_hash, - payout_instructions: config.payout_address.clone(), - p2_singleton_puzzle_hash: launcher_id_to_p2_puzzle_hash( - &plot_nft.launcher_id, - plot_nft.delay_time as u64, - &plot_nft.delay_puzzle_hash, - )?, - owner_public_key: plot_nft.pool_state.owner_pubkey, - difficulty: None, - }); - let mut owner_key = None; - let mut auth_key = None; - for i in 0..150 { - let key = master_sk_to_singleton_owner_sk(&master_key, i).unwrap(); - let pub_key: Bytes48 = key.sk_to_pk().to_bytes().into(); - if pub_key == plot_nft.pool_state.owner_pubkey { - let a_key = master_sk_to_pooling_authentication_sk(&master_key, i, 0).unwrap(); - owner_key = Some(key.into()); - auth_key = Some(a_key.into()); - break; - } - } - if let Some(info) = config.farmer_info.iter_mut().find(|f| { - if let Some(l) = &f.launcher_id { - l == &plot_nft.launcher_id - } else { - false - } - }) { - info.farmer_secret_key = master_sk_to_farmer_sk(&master_key)?.into(); - info.launcher_id = Some(plot_nft.launcher_id); - info.pool_secret_key = Some(master_sk_to_pool_sk(&master_key)?.into()); - info.owner_secret_key = owner_key; - info.auth_secret_key = auth_key; - } else { - config.farmer_info.push(FarmingInfo { - farmer_secret_key: master_sk_to_farmer_sk(&master_key)?.into(), - launcher_id: Some(plot_nft.launcher_id), - pool_secret_key: Some(master_sk_to_pool_sk(&master_key)?.into()), - owner_secret_key: owner_key, - auth_secret_key: auth_key, - }); - } - } - if config.farmer_info.is_empty() { - warn!("No PlotNFT Found"); - config.farmer_info.push(FarmingInfo { - farmer_secret_key: master_sk_to_farmer_sk(&master_key)?.into(), - launcher_id: None, - pool_secret_key: Some(master_sk_to_pool_sk(&master_key)?.into()), - owner_secret_key: None, - auth_secret_key: None, - }); - } - if let Some(op) = &gen_settings.output_path { - config.save_as_yaml(op)?; - } - Ok(config) -} - -pub async fn update_pool_info(config: Config) -> Result { - let client = Arc::new(FullnodeClient::new( - &config.fullnode_rpc_host, - config.fullnode_rpc_port, - 60, - config.ssl_root_path.clone().map(|s| ClientSSLConfig { - ssl_crt_path: Path::new(&s) - .join("daemon/private_daemon.crt") - .to_string_lossy() - .to_string(), - ssl_key_path: Path::new(&s) - .join("daemon/private_daemon.key") - .to_string_lossy() - .to_string(), - ssl_ca_crt_path: Path::new(&s) - .join("ca/private_ca.crt") - .to_string_lossy() - .to_string(), - }), - &None, - )); - let mut plot_nfts = vec![]; - for farmer_info in &config.farmer_info { - if let Some(launcher_id) = farmer_info.launcher_id { - info!( - "Fetching current PlotNFT state for launcher id {} ...", - launcher_id.to_string() - ); - plot_nfts.extend(get_plotnft_by_launcher_id(client.clone(), &launcher_id).await?) - } - } - - let mut updated_config = config.clone(); - for plot_nft in plot_nfts { - if let Some(pool_wallet) = updated_config - .pool_info - .iter_mut() - .find(|pw| pw.launcher_id == plot_nft.launcher_id) - { - let old_pool_wallet = pool_wallet.clone(); - pool_wallet.pool_url = plot_nft.pool_state.pool_url.unwrap_or_default(); - if pool_wallet.target_puzzle_hash != plot_nft.pool_state.target_puzzle_hash { - // Reset diff on pool change - pool_wallet.difficulty = None; - } - pool_wallet.target_puzzle_hash = plot_nft.pool_state.target_puzzle_hash; - pool_wallet.owner_public_key = plot_nft.pool_state.owner_pubkey; - - let mut change_messages: Vec = vec![]; - if old_pool_wallet.pool_url != pool_wallet.pool_url { - change_messages.push(format!( - "from {} to {}", - old_pool_wallet.pool_url, pool_wallet.pool_url - )); - } - if old_pool_wallet.target_puzzle_hash != pool_wallet.target_puzzle_hash { - change_messages.push(format!( - "from PH {} to PH {}", - old_pool_wallet.target_puzzle_hash, pool_wallet.target_puzzle_hash - )); - } - if change_messages.is_empty() { - info!( - "PlotNFT state for launcher id {} did not change", - plot_nft.launcher_id.to_string(), - ); - } else { - info!( - "PlotNFT state for launcher id {} did change {}", - plot_nft.launcher_id.to_string(), - change_messages.join(" and "), - ); - } - } - } - - Ok(updated_config) -} - -pub fn load_mnemonic_from_file>(path: P) -> Result { - Mnemonic::from_str( - &fs::read_to_string(path) - .map_err(|e| Error::new(e.kind(), format!("Failed to Mnemonic File: {e:?}")))?, - ) - .map_err(|e| { - Error::new( - ErrorKind::InvalidInput, - format!("Failed to parse Mnemonic: {e:?}"), - ) - }) -} - -pub fn prompt_for_mnemonic() -> Result { - Mnemonic::from_str( - &Input::::with_theme(&ColorfulTheme::default()) - .with_prompt("Please Input Your Mnemonic: ") - .validate_with(|input: &String| -> Result<(), &str> { - if Mnemonic::from_str(input).is_ok() { - Ok(()) - } else { - Err("You did not input a valid Mnemonic, Please try again.") - } - }) - .interact_text() - .map_err(|e| { - Error::new( - ErrorKind::InvalidInput, - format!("Failed to read user Input for Mnemonic: {e:?}"), - ) - })?, - ) - .map_err(|e| { - Error::new( - ErrorKind::InvalidInput, - format!("Failed to parse Mnemonic: {e:?}"), - ) - }) -} - -pub async fn join_pool( - config: Config, - pool_url: String, - mnemonic: String, - launcher_id: Option, - fee: Option, -) -> Result { - let launcher_id_bytes: Option = launcher_id.map(|l| l.into()); - let client = Arc::new(FullnodeClient::new( - &config.fullnode_rpc_host, - config.fullnode_rpc_port, - 60, - config.ssl_root_path.clone().map(|s| ClientSSLConfig { - ssl_crt_path: Path::new(&s) - .join("daemon/private_daemon.crt") - .to_string_lossy() - .to_string(), - ssl_key_path: Path::new(&s) - .join("daemon/private_daemon.key") - .to_string_lossy() - .to_string(), - ssl_ca_crt_path: Path::new(&s) - .join("ca/private_ca.crt") - .to_string_lossy() - .to_string(), - }), - &None, - )); - for farmer_info in &config.farmer_info { - let launcher_id = farmer_info.launcher_id.unwrap(); - if let Some(selected_launcher_id) = launcher_id_bytes { - if selected_launcher_id != launcher_id { - continue; - } - } - migrate_plot_nft( - client.clone(), - &pool_url, - &launcher_id, - &mnemonic, - fee.unwrap_or_default(), - ) - .await?; - } - - update_pool_info(config).await } diff --git a/src/cli/prompts.rs b/src/cli/prompts.rs new file mode 100644 index 0000000..3ab2832 --- /dev/null +++ b/src/cli/prompts.rs @@ -0,0 +1,373 @@ +use bip39::Mnemonic; +use dg_xch_core::blockchain::sized_bytes::{prep_hex_str, Bytes32}; +use dg_xch_keys::parse_payout_address; +use dialoguer::theme::ColorfulTheme; +use dialoguer::Input; +use hex::decode; +use once_cell::sync::Lazy; +use regex::Regex; +use std::collections::HashSet; +use std::fs; +use std::io::{Error, ErrorKind}; +use std::net::IpAddr; +use std::path::Path; +use std::str::FromStr; + +pub fn prompt_for_plot_directories() -> Result, Error> { + let mut dirs = HashSet::new(); + let mut first = true; + while let Some(dir) = Input::::with_theme(&ColorfulTheme::default()) + .with_prompt(if first { + first = false; + "Enter a Root Plot Directory or leave blank to continue: " + } else { + "Enter Another Plot Directory or leave blank to continue: " + }) + .allow_empty(true) + .interact_text() + .map(|input| { + if input.trim().is_empty() { + None + } else { + Some(input) + } + }) + .map_err(|e| { + Error::new( + ErrorKind::InvalidInput, + format!("Failed to read user Input for Plot Directory: {e:?}"), + ) + })? + { + let tripmmed = dir.trim(); + let stripped = tripmmed.strip_suffix('/').unwrap_or(tripmmed); + dirs.insert(stripped.to_string()); + } + Ok(dirs.into_iter().collect()) +} + +pub fn prompt_for_payout_address(current: Option) -> Result { + let prompt = if let Some(current) = ¤t { + format!("Please Input XCH Payout Address, or leave blank to use {current}: ") + } else { + String::from("Please Input Your XCH Payout Address: ") + }; + Input::::with_theme(&ColorfulTheme::default()) + .with_prompt(prompt) + .allow_empty(true) + .validate_with(|input: &String| -> Result<(), &str> { + if (input.trim().is_empty() && current.is_some()) || parse_payout_address(input).is_ok() + { + Ok(()) + } else { + Err("You did not input a valid XCH Address, Please try again.") + } + }) + .interact_text() + .map(|input| { + if input.trim().is_empty() && current.is_some() { + current + .map(|v| { + Bytes32::from( + parse_payout_address(&v) + .unwrap_or_else(|_| panic!("{input} Not a valid Payout Address")), + ) + }) + .expect("Just Checked Is Some") + } else { + Bytes32::from(parse_payout_address(&input).expect("Checked In Validator")) + } + }) + .map_err(|e| { + Error::new( + ErrorKind::InvalidInput, + format!("Failed to read user Input for XCH Address: {e:?}"), + ) + }) +} + +pub fn prompt_for_launcher_id(current: Option) -> Result, Error> { + let prompt = if let Some(current) = ¤t { + format!("Please Input PlotNft LauncherId, leave blank to scan, current {current}: ") + } else { + String::from("Please Input PlotNft LauncherId, leave blank to scan: ") + }; + Input::::with_theme(&ColorfulTheme::default()) + .with_prompt(prompt) + .allow_empty(true) + .validate_with(|input: &String| -> Result<(), &str> { + if input.trim().is_empty() + || (input.len() == 66 || input.len() == 64 && decode(prep_hex_str(input)).is_ok()) + { + Ok(()) + } else { + Err("You did not input a valid LauncherId, Please try again.") + } + }) + .interact_text() + .map(|input| { + if input.trim().is_empty() { + current + } else { + Some(Bytes32::from(input)) + } + }) + .map_err(|e| { + Error::new( + ErrorKind::InvalidInput, + format!("Failed to read user Input for LauncherId: {e:?}"), + ) + }) +} + +pub fn prompt_for_farming_fullnode(current: Option) -> Result { + let prompt = if let Some(current) = ¤t { + format!("Please select Node for Farming, \"community\" or a custom IP/Domain, leave blank to continue Using {current}: ") + } else { + String::from("Please select Node for Farming, \"community\" or a custom IP/Domain:") + }; + Input::::with_theme(&ColorfulTheme::default()) + .with_prompt(prompt) + .allow_empty(true) + .validate_with(|input: &String| -> Result<(), &str> { + let input = input.to_ascii_lowercase(); + let trimmed = input.trim(); + if trimmed.is_empty() + || ["community", "c"].contains(&trimmed) //Community Node + || is_domain(trimmed) + || IpAddr::from_str(trimmed).is_ok() + || trimmed == "localhost" || trimmed == "l" + { + Ok(()) + } else { + Err("Please Enter community (c) or local (l)") + } + }) + .interact_text() + .map(|input| { + if input.trim().is_empty() { + if let Some(current) = current { + current + } else { + String::from("localhost") + } + } else if ["l", "localhost"].contains(&input.as_str()) { + String::from("localhost") + } else if ["c", "community"].contains(&input.as_str()) { + String::from("chia-proxy.evergreenminer-prod.com") + } else { + input.trim().to_string() + } + }) + .map_err(|e| { + Error::new( + ErrorKind::InvalidInput, + format!("Failed to read user Input for Farming FullNode URL: {e:?}"), + ) + }) +} + +pub fn prompt_for_rpc_fullnode(current: Option) -> Result { + let prompt = if let Some(current) = ¤t { + format!("Please Select Your Node for RPC Calls (community/local), leave blank to continue Using {current}: ") + } else { + String::from("Please Select Your Node for RPC Calls (community/local): ") + }; + Input::::with_theme(&ColorfulTheme::default()) + .with_prompt(prompt) + .allow_empty(true) + .validate_with(|input: &String| -> Result<(), &str> { + let input = input.to_ascii_lowercase(); + let trimmed = input.trim(); + if trimmed.is_empty() + || ["community", "c"].contains(&trimmed) //Community Node + || is_domain(trimmed) + || IpAddr::from_str(trimmed).is_ok() + || trimmed == "localhost" || trimmed == "l" + { + Ok(()) + } else { + Err("Please Enter community (c) or local (l)") + } + }) + .interact_text() + .map(|input| { + if input.trim().is_empty() { + if let Some(current) = current { + current + } else { + String::from("localhost") + } + } else if ["l", "localhost"].contains(&input.as_str()) { + String::from("localhost") + } else if ["c", "community"].contains(&input.as_str()) { + String::from("chia-proxy.evergreenminer-prod.com") + } else { + input.trim().to_string() + } + }) + .map_err(|e| { + Error::new( + ErrorKind::InvalidInput, + format!("Failed to read user Input for FullNode URL: {e:?}"), + ) + }) +} + +pub fn prompt_for_farming_port(current: Option) -> Result { + let prompt = if let Some(current) = current { + format!("Please Input Your Node Farming Port, leave blank to continue Using {current}: ") + } else { + String::from("Please Input Your Node Farming Port (Usually 8444 or 443): ") + }; + Input::::with_theme(&ColorfulTheme::default()) + .with_prompt(prompt) + .allow_empty(true) + .validate_with(|input: &String| -> Result<(), &str> { + if input.trim().is_empty() && current.is_some() { + Ok(()) + } else { + u16::from_str(input) + .map(|_| ()) + .map_err(|_| "Input is not a valid u16") + } + }) + .interact_text() + .map(|input| { + if !input.trim().is_empty() { + u16::from_str(&input).expect("Was Validated in the validate_with call") + } else if let Some(current) = current { + current + } else { + 0 //Should Never Hit This + } + }) + .map_err(|e| { + Error::new( + ErrorKind::InvalidInput, + format!("Failed to read user Input for FullNode Farming Port: {e:?}"), + ) + }) +} + +pub fn prompt_for_rpc_port(current: Option) -> Result { + let prompt = if let Some(current) = current { + format!("Please Input Your Node RPC Port, leave blank to continue Using {current}: ") + } else { + String::from("Please Input Your Node RPC Port (Usually 8555 or 443): ") + }; + Input::::with_theme(&ColorfulTheme::default()) + .with_prompt(prompt) + .allow_empty(true) + .validate_with(|input: &String| -> Result<(), &str> { + if input.trim().is_empty() && current.is_some() { + Ok(()) + } else { + u16::from_str(input) + .map(|_| ()) + .map_err(|_| "Input is not a valid u16") + } + }) + .interact_text() + .map(|input| { + if !input.trim().is_empty() { + u16::from_str(&input).expect("Was Validated in the validate_with call") + } else if let Some(current) = current { + current + } else { + 0 //Should Never Hit This + } + }) + .map_err(|e| { + Error::new( + ErrorKind::InvalidInput, + format!("Failed to read user Input for FullNode RPC Port: {e:?}"), + ) + }) +} + +pub fn prompt_for_ssl_path(current: Option) -> Result, Error> { + let prompt = if let Some(current) = ¤t { + format!("Input Node SSL Path for custom certs or hit enter, currently using {current}: ") + } else { + String::from("Input Node SSL Path for custom certs or hit enter: ") + }; + Input::::with_theme(&ColorfulTheme::default()) + .with_prompt(prompt) + .allow_empty(true) + .validate_with(|input: &String| -> Result<(), &str> { + if input.is_empty() { + Ok(()) + } else { + let path = Path::new(input); + if path.exists() { + Ok(()) + } else { + Err("Input is not a valid directory") + } + } + }) + .interact_text() + .map(|input| { + if input.is_empty() { + current + } else { + Some(input) + } + }) + .map_err(|e| { + Error::new( + ErrorKind::InvalidInput, + format!("Failed to read user Input for SSL Path: {e:?}"), + ) + }) +} + +pub fn prompt_for_mnemonic>(path: Option

) -> Result { + if let Some(mnemonic_file) = path { + Mnemonic::from_str( + &fs::read_to_string(mnemonic_file) + .map_err(|e| Error::new(e.kind(), format!("Failed to Mnemonic File: {e:?}")))?, + ) + .map_err(|e| { + Error::new( + ErrorKind::InvalidInput, + format!("Failed to parse Mnemonic: {e:?}"), + ) + }) + } else { + Mnemonic::from_str( + &Input::::with_theme(&ColorfulTheme::default()) + .with_prompt("Please Input Your Mnemonic: ") + .validate_with(|input: &String| -> Result<(), &str> { + if Mnemonic::from_str(input).is_ok() { + Ok(()) + } else { + Err("You did not input a valid Mnemonic, Please try again.") + } + }) + .interact_text() + .map_err(|e| { + Error::new( + ErrorKind::InvalidInput, + format!("Failed to read user Input for Mnemonic: {e:?}"), + ) + })?, + ) + .map_err(|e| { + Error::new( + ErrorKind::InvalidInput, + format!("Failed to parse Mnemonic: {e:?}"), + ) + }) + } +} + +static DOMAIN_REGEX: Lazy = Lazy::new(|| { + Regex::new(r"^(?:[a-zA-Z0-9-]{0,63}\.)*[a-zA-Z0-9-]{1,63}\.[a-zA-Z]{2,63}$") + .expect("Invalid regular expression") +}); + +fn is_domain(domain: &str) -> bool { + DOMAIN_REGEX.is_match(domain) +} diff --git a/src/cli/utils.rs b/src/cli/utils.rs new file mode 100644 index 0000000..b6d8488 --- /dev/null +++ b/src/cli/utils.rs @@ -0,0 +1,85 @@ +use crate::farmer::config::Config; +use crate::farmer::{ExtendedFarmerSharedState, CA_PRIVATE_CRT, PRIVATE_CRT, PRIVATE_KEY}; +use dg_xch_clients::rpc::full_node::FullnodeClient as RpcClient; +use dg_xch_clients::ClientSSLConfig; +use dg_xch_core::protocols::farmer::FarmerSharedState; +use home::home_dir; +use log::LevelFilter; +use simple_logger::SimpleLogger; +use std::collections::HashMap; +use std::io::{Error, ErrorKind}; +use std::path::{Path, PathBuf}; +use std::sync::Arc; + +pub(crate) fn get_root_path() -> PathBuf { + let prefix = home_dir().unwrap_or(Path::new("/").to_path_buf()); + prefix.as_path().join(Path::new(".config/fast_farmer/")) +} + +pub(crate) fn get_config_path() -> PathBuf { + get_root_path() + .as_path() + .join(Path::new("fast_farmer.yaml")) +} + +pub(crate) fn get_device_id_path() -> PathBuf { + get_root_path().as_path().join(Path::new("device_id.bin")) +} + +pub(crate) fn get_ssl_root_path( + shared_state: &FarmerSharedState, +) -> PathBuf { + if let Some(ssl_root_path) = &shared_state.data.config.ssl_root_path { + PathBuf::from(ssl_root_path) + } else { + get_root_path().as_path().join(Path::new("ssl/")) + } +} + +pub(crate) fn rpc_client_from_config( + config: &Config, + headers: &Option>, +) -> Arc { + Arc::new(RpcClient::new( + &config.fullnode_rpc_host, + config.fullnode_rpc_port, + 600, + config.ssl_root_path.clone().map(|s| ClientSSLConfig { + ssl_crt_path: Path::new(&s) + .join(PRIVATE_CRT) + .to_string_lossy() + .to_string(), + ssl_key_path: Path::new(&s) + .join(PRIVATE_KEY) + .to_string_lossy() + .to_string(), + ssl_ca_crt_path: Path::new(&s) + .join(CA_PRIVATE_CRT) + .to_string_lossy() + .to_string(), + }), + headers, + )) +} + +pub(crate) fn init_logger() { + SimpleLogger::new() + .with_colors(true) + .with_level(LevelFilter::Info) + .env() + .init() + .unwrap_or_default(); +} + +pub(crate) fn check_config(config_path: &Path) -> Result<(), Error> { + if !config_path.exists() { + let error_msg = format!( + "Failed to find config at {:?}, please run init", + config_path + ); + eprintln!("{error_msg}"); + Err(Error::new(ErrorKind::NotFound, error_msg)) + } else { + Ok(()) + } +} diff --git a/src/farmer/config.rs b/src/farmer/config.rs index 4c51c26..650c20c 100644 --- a/src/farmer/config.rs +++ b/src/farmer/config.rs @@ -9,6 +9,16 @@ use std::io::{Error, ErrorKind}; use std::path::{Path, PathBuf}; use std::sync::Arc; +const fn default_true() -> bool { + true +} +const fn default_metrics_port() -> u16 { + 8080 +} +const fn default_none() -> Option { + None +} + #[derive(Default, Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub struct FarmingInfo { pub farmer_secret_key: Bytes32, @@ -18,13 +28,31 @@ pub struct FarmingInfo { pub auth_secret_key: Option, } +#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub struct MetricsConfig { + #[serde(default = "default_true")] + pub enabled: bool, + #[serde(default = "default_metrics_port")] + pub port: u16, +} +impl Default for MetricsConfig { + fn default() -> Self { + Self { + enabled: true, + port: 8080, + } + } +} + #[derive(Default, Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub struct DruidGardenHarvesterConfig { + #[serde(default = "Vec::new")] pub plot_directories: Vec, } #[derive(Default, Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub struct HarvesterConfig { + #[serde(default = "default_none")] pub druid_garden: Option, } @@ -40,6 +68,7 @@ pub struct Config { pub pool_info: Vec, pub payout_address: String, pub harvester_configs: HarvesterConfig, + pub metrics: Option, } impl Config { pub fn save_as_yaml>(&self, path: P) -> Result<(), Error> { @@ -80,10 +109,12 @@ impl Default for Config { pool_info: vec![], payout_address: "".to_string(), harvester_configs: HarvesterConfig { - druid_garden: Some(DruidGardenHarvesterConfig { - plot_directories: vec![], - }), + druid_garden: Some(DruidGardenHarvesterConfig::default()), }, + metrics: Some(MetricsConfig { + enabled: true, + port: 8080, + }), } } } diff --git a/src/farmer/mod.rs b/src/farmer/mod.rs index 4f1c27c..d4795c5 100644 --- a/src/farmer/mod.rs +++ b/src/farmer/mod.rs @@ -1,8 +1,11 @@ -use crate::cli::get_ssl_root_path; +use crate::cli::utils::get_ssl_root_path; use crate::farmer::config::Config; use crate::farmer::protocols::fullnode::new_signage_point::NewSignagePointHandle; use crate::farmer::protocols::fullnode::request_signed_values::RequestSignedValuesHandle; +use crate::gui::FullNodeState; use crate::harvesters::{load_harvesters, Harvesters}; +use crate::metrics::Metrics; +use crate::PROTOCOL_VERSION; use dg_xch_clients::api::pool::PoolClient; use dg_xch_clients::websocket::farmer::FarmerClient; use dg_xch_clients::websocket::WsClientConfig; @@ -25,15 +28,18 @@ use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use std::time::{Duration, Instant}; use tokio::fs::File; -use tokio::sync::Mutex; +use tokio::sync::RwLock; use uuid::Uuid; pub mod config; pub mod protocols; -static PUBLIC_CRT: &str = "farmer/public_farmer.crt"; -static PUBLIC_KEY: &str = "farmer/public_farmer.key"; -static CA_PUBLIC_CRT: &str = "ca/chia_ca.crt"; +pub static PUBLIC_CRT: &str = "farmer/public_farmer.crt"; +pub static PUBLIC_KEY: &str = "farmer/public_farmer.key"; +pub static CA_PUBLIC_CRT: &str = "ca/chia_ca.crt"; +pub static PRIVATE_CRT: &str = "farmer/private_farmer.crt"; +pub static PRIVATE_KEY: &str = "farmer/private_farmer.key"; +pub static CA_PRIVATE_CRT: &str = "ca/private_ca.crt"; #[derive(Clone, Default)] pub struct GuiStats { @@ -49,9 +55,11 @@ pub struct ExtendedFarmerSharedState { pub(crate) config: Arc, pub(crate) run: Arc, pub(crate) force_pool_update: Arc, - pub(crate) full_node_client: Arc>>>, - pub(crate) gui_stats: Arc>, - pub(crate) last_sp_timestamp: Arc>, + pub(crate) full_node_client: Arc>>>, + pub(crate) gui_stats: Arc>, + pub(crate) last_sp_timestamp: Arc>, + pub(crate) extended_metrics: Arc, + pub(crate) fullnode_state: Arc>>, } impl Default for ExtendedFarmerSharedState { fn default() -> Self { @@ -61,7 +69,9 @@ impl Default for ExtendedFarmerSharedState { force_pool_update: Arc::new(Default::default()), full_node_client: Arc::new(Default::default()), gui_stats: Arc::new(Default::default()), - last_sp_timestamp: Arc::new(Mutex::new(Instant::now())), + last_sp_timestamp: Arc::new(RwLock::new(Instant::now())), + extended_metrics: Arc::new(Default::default()), + fullnode_state: Arc::new(Default::default()), } } } @@ -137,12 +147,12 @@ impl Farmer { if !s.shared_state.data.run.load(Ordering::Relaxed) { break; } - if let Some(client) = s.shared_state.data.full_node_client.lock().await.as_ref() { + if let Some(client) = s.shared_state.data.full_node_client.read().await.as_ref() { client_run.store(false, Ordering::Relaxed); client .client .connection - .lock() + .write() .await .shutdown() .await @@ -159,7 +169,7 @@ impl Farmer { continue; } else { info!("Farmer Client Initialized"); - *s.shared_state.data.full_node_client.lock().await = Some(c); + *s.shared_state.data.full_node_client.write().await = Some(c); break; } } @@ -175,7 +185,7 @@ impl Farmer { } let mut last_clear = Instant::now(); loop { - if let Some(client) = s.shared_state.data.full_node_client.lock().await.as_ref() { + if let Some(client) = s.shared_state.data.full_node_client.read().await.as_ref() { if client.is_closed() { if !s.shared_state.data.run.load(Ordering::Relaxed) { info!("Farmer Stopped"); @@ -187,14 +197,14 @@ impl Farmer { } } let dur = Instant::now() - .duration_since(*s.shared_state.data.last_sp_timestamp.lock().await) + .duration_since(*s.shared_state.data.last_sp_timestamp.read().await) .as_secs(); - if dur >= 180 { + if dur >= 60 { info!( "Failed to get Signage Point after {dur} seconds, restarting farmer client" ); - *s.shared_state.data.last_sp_timestamp.lock().await = Instant::now(); - if let Some(c) = &*s.shared_state.data.full_node_client.lock().await { + *s.shared_state.data.last_sp_timestamp.write().await = Instant::now(); + if let Some(c) = &*s.shared_state.data.full_node_client.read().await { info!( "Shutting Down old Farmer Client: {}:{}", s.shared_state.data.config.fullnode_ws_host, @@ -203,7 +213,7 @@ impl Farmer { client_run.store(false, Ordering::Relaxed); c.client .connection - .lock() + .write() .await .shutdown() .await @@ -215,7 +225,7 @@ impl Farmer { let expired: Vec = s .shared_state .cache_time - .lock() + .write() .await .iter() .filter_map(|(k, v)| { @@ -228,22 +238,22 @@ impl Farmer { .collect(); s.shared_state .cache_time - .lock() + .write() .await .retain(|k, _| !expired.contains(k)); s.shared_state .signage_points - .lock() + .write() .await .retain(|k, _| !expired.contains(k)); s.shared_state .quality_to_identifiers - .lock() + .write() .await .retain(|k, _| !expired.contains(k)); s.shared_state .proofs_of_space - .lock() + .write() .await .retain(|k, _| !expired.contains(k)); last_clear = Instant::now(); @@ -276,6 +286,7 @@ impl Farmer { ssl_ca_crt_path: ssl_path.join(CA_PUBLIC_CRT).to_string_lossy().to_string(), }), software_version: None, + protocol_version: PROTOCOL_VERSION, additional_headers: None, }), shared_state.clone(), @@ -289,13 +300,13 @@ impl Farmer { shared_state: &FarmerSharedState, client: &mut FarmerClient, ) -> Result<(), Error> { - client.client.connection.lock().await.clear().await; + client.client.connection.write().await.clear().await; let signage_handle_id = Uuid::new_v4(); let harvester_id = load_client_id(shared_state).await?; client .client .connection - .lock() + .write() .await .subscribe( signage_handle_id, @@ -321,7 +332,7 @@ impl Farmer { client .client .connection - .lock() + .write() .await .subscribe( request_signed_values_id, diff --git a/src/farmer/protocols/fullnode/new_signage_point.rs b/src/farmer/protocols/fullnode/new_signage_point.rs index 4ed3e26..640ca79 100644 --- a/src/farmer/protocols/fullnode/new_signage_point.rs +++ b/src/farmer/protocols/fullnode/new_signage_point.rs @@ -1,6 +1,7 @@ use crate::farmer::protocols::harvester::new_proof_of_space::NewProofOfSpaceHandle; use crate::farmer::{ExtendedFarmerSharedState, FarmerSharedState}; use crate::harvesters::{Harvester, Harvesters}; +use crate::PROTOCOL_VERSION; use async_trait::async_trait; use dg_xch_clients::api::pool::PoolClient; use dg_xch_core::blockchain::proof_of_space::calculate_prefix_bits; @@ -17,16 +18,16 @@ use std::collections::HashMap; use std::io::{Cursor, Error}; use std::sync::Arc; use std::time::Instant; -use tokio::sync::Mutex; +use tokio::sync::RwLock; use uuid::Uuid; pub struct NewSignagePointHandle { pub id: Uuid, pub harvester_id: Bytes32, - pub pool_state: Arc>>, + pub pool_state: Arc>>, pub pool_client: Arc, - pub signage_points: Arc>>>, - pub cache_time: Arc>>, + pub signage_points: Arc>>>, + pub cache_time: Arc>>, pub shared_state: Arc>, pub harvesters: Arc>>, pub constants: &'static ConsensusConstants, @@ -41,14 +42,14 @@ impl MessageHandler for NewSignag ) -> Result<(), Error> { debug!("NewSignagePoint Message. Starting Deserialization."); let mut cursor = Cursor::new(&msg.data); - let sp = NewSignagePoint::from_bytes(&mut cursor)?; + let sp = NewSignagePoint::from_bytes(&mut cursor, PROTOCOL_VERSION)?; debug!("NewSignagePoint Message. Finished Deserialization."); if sp.sp_source_data.is_none() { error!("No SignagePoint Source Data Included for Farmer which Requires it"); } let mut pool_difficulties = vec![]; debug!("Generating Pool Difficulties"); - for (p2_singleton_puzzle_hash, pool_dict) in self.pool_state.lock().await.iter() { + for (p2_singleton_puzzle_hash, pool_dict) in self.pool_state.read().await.iter() { if let Some(config) = &pool_dict.pool_config { if config.pool_url.is_empty() { debug!("Self Pooling Detected for {p2_singleton_puzzle_hash}"); @@ -70,7 +71,16 @@ impl MessageHandler for NewSignag "New Signage Point({}): {:?}", sp.signage_point_index, sp.challenge_hash ); - *self.shared_state.data.last_sp_timestamp.lock().await = Instant::now(); + let now = Instant::now(); + let time_since_last_sp = now + .duration_since(*self.shared_state.data.last_sp_timestamp.read().await) + .as_millis(); + self.shared_state + .data + .extended_metrics + .signage_point_interval + .observe(time_since_last_sp as f64 / 1000f64); + *self.shared_state.data.last_sp_timestamp.write().await = now; let filter_prefix_bits = calculate_prefix_bits(self.constants, sp.peak_height); let sp_hash = sp.challenge_chain_sp; let harvester_point = Arc::new(NewSignagePointHarvester { @@ -82,10 +92,17 @@ impl MessageHandler for NewSignag pool_difficulties, filter_prefix_bits, }); - self.cache_time.lock().await.insert(sp_hash, Instant::now()); - self.shared_state.data.gui_stats.lock().await.most_recent_sp = - (sp.challenge_hash, sp.signage_point_index); - match self.signage_points.lock().await.entry(sp_hash) { + self.cache_time + .write() + .await + .insert(sp_hash, Instant::now()); + self.shared_state + .data + .gui_stats + .write() + .await + .most_recent_sp = (sp.challenge_hash, sp.signage_point_index); + match self.signage_points.write().await.entry(sp_hash) { Entry::Occupied(mut e) => { e.get_mut().push(sp); } diff --git a/src/farmer/protocols/fullnode/request_signed_values.rs b/src/farmer/protocols/fullnode/request_signed_values.rs index c4cdb15..c7fc7b3 100644 --- a/src/farmer/protocols/fullnode/request_signed_values.rs +++ b/src/farmer/protocols/fullnode/request_signed_values.rs @@ -1,6 +1,7 @@ use crate::farmer::protocols::harvester::respond_signatures::RespondSignaturesHandler; use crate::farmer::{ExtendedFarmerSharedState, FarmerSharedState}; use crate::harvesters::{Harvester, Harvesters}; +use crate::PROTOCOL_VERSION; use async_trait::async_trait; use dg_xch_clients::api::pool::PoolClient; use dg_xch_core::blockchain::sized_bytes::Bytes32; @@ -36,12 +37,12 @@ impl MessageHandler ) -> Result<(), Error> { debug!("RequestSignedValues Message. Starting Deserialization."); let mut cursor = Cursor::new(&msg.data); - let request = RequestSignedValues::from_bytes(&mut cursor)?; + let request = RequestSignedValues::from_bytes(&mut cursor, PROTOCOL_VERSION)?; debug!("RequestSignedValues Message. Finished Deserialization."); if let Some(identifier) = self .shared_state .quality_to_identifiers - .lock() + .read() .await .get(&request.quality_string) { @@ -51,13 +52,13 @@ impl MessageHandler if let Some(data) = request.foliage_block_data { foliage_block_data = Some(SignatureRequestSourceData { kind: SigningDataKind::FoliageBlockData, - data: data.to_bytes(), + data: data.to_bytes(PROTOCOL_VERSION), }); } if let Some(data) = request.foliage_transaction_block_data { foliage_transaction_block = Some(SignatureRequestSourceData { kind: SigningDataKind::FoliageTransactionBlock, - data: data.to_bytes(), + data: data.to_bytes(PROTOCOL_VERSION), }); } let request = RequestSignatures { diff --git a/src/farmer/protocols/harvester/new_proof_of_space.rs b/src/farmer/protocols/harvester/new_proof_of_space.rs index f8c5f3d..f348fe9 100644 --- a/src/farmer/protocols/harvester/new_proof_of_space.rs +++ b/src/farmer/protocols/harvester/new_proof_of_space.rs @@ -1,7 +1,7 @@ use crate::farmer::protocols::harvester::respond_signatures::RespondSignaturesHandler; use crate::farmer::{load_client_id, ExtendedFarmerSharedState, FarmerSharedState}; use crate::harvesters::{Harvester, Harvesters, ProofHandler, SignatureHandler}; -use crate::HEADERS; +use crate::{HEADERS, PROTOCOL_VERSION}; use async_trait::async_trait; use blst::min_pk::{AggregateSignature, PublicKey, Signature}; use blst::BLST_ERROR; @@ -24,6 +24,7 @@ use dg_xch_core::protocols::pool::{ use dg_xch_pos::verify_and_get_quality_string; use dg_xch_serialize::{hash_256, ChiaSerialize}; use log::{debug, error, info, warn}; +use std::collections::hash_map::Entry; use std::collections::HashMap; use std::io::{Error, ErrorKind}; use std::sync::atomic::Ordering; @@ -45,7 +46,7 @@ impl ProofHandler for NewProofOfS if let Some(sps) = self .shared_state .signage_points - .lock() + .read() .await .get(&new_pos.sp_hash) { @@ -141,24 +142,32 @@ fn format_big_number(number: u32) -> String { impl NewProofOfSpaceHandle { async fn _handle_proof(&self, sp: &NewSignagePoint, qs: &Bytes32, new_pos: &NewProofOfSpace) { + match self + .shared_state + .proofs_of_space + .write() + .await + .entry(new_pos.sp_hash) { - let mut farmer_pos = self.shared_state.proofs_of_space.lock().await; - if farmer_pos.get(&new_pos.sp_hash).is_none() { - farmer_pos.insert(new_pos.sp_hash, vec![]); + Entry::Occupied(mut e) => { + e.get_mut() + .push((new_pos.plot_identifier.clone(), new_pos.proof.clone())); + } + Entry::Vacant(e) => { + e.insert(vec![( + new_pos.plot_identifier.clone(), + new_pos.proof.clone(), + )]); } - farmer_pos - .get_mut(&new_pos.sp_hash) - .expect("Should not happen, item created above") - .push((new_pos.plot_identifier.clone(), new_pos.proof.clone())); } self.shared_state .cache_time - .lock() + .write() .await .insert(new_pos.sp_hash, Instant::now()); self.shared_state .quality_to_identifiers - .lock() + .write() .await .insert( *qs, @@ -171,7 +180,7 @@ impl NewProofOfSpaceHandle { ); self.shared_state .cache_time - .lock() + .write() .await .insert(*qs, Instant::now()); let sig_handle = RespondSignaturesHandler { @@ -190,22 +199,22 @@ impl NewProofOfSpaceHandle { ( SignatureRequestSourceData { kind: SigningDataKind::ChallengeChainVdf, - data: vdf.cc_vdf.to_bytes(), + data: vdf.cc_vdf.to_bytes(PROTOCOL_VERSION), }, SignatureRequestSourceData { kind: SigningDataKind::RewardChainVdf, - data: vdf.rc_vdf.to_bytes(), + data: vdf.rc_vdf.to_bytes(PROTOCOL_VERSION), }, ) } else if let Some(sub_slot_data) = sp_data.sub_slot_data.as_ref() { ( SignatureRequestSourceData { kind: SigningDataKind::ChallengeChainSubSlot, - data: sub_slot_data.cc_sub_slot.to_bytes(), + data: sub_slot_data.cc_sub_slot.to_bytes(PROTOCOL_VERSION), }, SignatureRequestSourceData { kind: SigningDataKind::RewardChainSubSlot, - data: sub_slot_data.rc_sub_slot.to_bytes(), + data: sub_slot_data.rc_sub_slot.to_bytes(PROTOCOL_VERSION), }, ) } else { @@ -249,103 +258,131 @@ impl NewProofOfSpaceHandle { qs: &Bytes32, new_pos: NewProofOfSpace, ) -> Result<(), Error> { - if let Some(pool_state) = self + if self .shared_state .pool_states - .lock() + .read() .await - .get_mut(p2_singleton_puzzle_hash) + .get(p2_singleton_puzzle_hash) + .is_none() { - if let Some(pool_config) = &pool_state.pool_config { - if pool_config.pool_url.is_empty() - || new_pos.proof.pool_contract_puzzle_hash.is_none() - { - } else if let Some(pool_dif) = pool_state.current_difficulty { - let required_iters = calculate_iterations_quality( - self.constants.difficulty_constant_factor, - qs, - new_pos.proof.size, - pool_dif, - &new_pos.sp_hash, - ); - let pool_required_iters = calculate_sp_interval_iters( - self.constants, - self.constants.pool_sub_slot_iters, - )?; - if required_iters > pool_required_iters { - warn!( - "Proof of space not good enough for pool {}: {:?} {qs:?}", - pool_config.pool_url, pool_state.current_difficulty - ); - } else if let Some(auth_token_timeout) = pool_state.authentication_token_timeout - { - let shared_state = self.shared_state.clone(); - let payload = PostPartialPayload { - launcher_id: pool_config.launcher_id, - authentication_token: get_current_authentication_token( - auth_token_timeout, - ), - proof_of_space: new_pos.proof.clone(), - sp_hash: new_pos.sp_hash, - end_of_sub_slot: new_pos.signage_point_index == 0, - harvester_id: load_client_id(shared_state.as_ref()).await?, - }; - let payload_bytes = hash_256(payload.to_bytes()); - let sp_src_data = { - if new_pos.include_source_signature_data - || new_pos.farmer_reward_address_override.is_some() - { - Some(vec![Some(SignatureRequestSourceData { - kind: SigningDataKind::Partial, - data: payload.to_bytes(), - })]) - } else { - None - } - }; - let request = RequestSignatures { - plot_identifier: new_pos.plot_identifier.clone(), - challenge_hash: new_pos.challenge_hash, - sp_hash: new_pos.sp_hash, - messages: vec![Bytes32::new(&payload_bytes)], - message_data: sp_src_data, - rc_block_unfinished: None, - }; - let handler = PartialHandler { - pool_client: self.pool_client.clone(), - shared_state: self.shared_state.clone(), - p2_singleton_puzzle_hash: *p2_singleton_puzzle_hash, - new_pos, - auth_token_timeout, - payload, - payload_bytes, - }; - if let Some(h) = self.harvesters.get(&self.harvester_id) { - let harvester = h.clone(); - tokio::spawn(async move { - match harvester.as_ref() { - Harvesters::DruidGarden(h) => { - if let Err(e) = h.request_signatures(request, handler).await - { - error!("Error Requesting Signature: {}", e); - } - } - } - }); - } else { - error!("Failed to find harvester with ID {}", &self.harvester_id); + warn!("Did not find pool info for {p2_singleton_puzzle_hash}"); + return Ok(()); + } + let (pool_url, launcher_id) = if let Some(Some(config)) = self + .shared_state + .pool_states + .read() + .await + .get(p2_singleton_puzzle_hash) + .map(|v| v.pool_config.as_ref()) + { + (config.pool_url.clone(), config.launcher_id) + } else { + warn!("No Pool Config for {p2_singleton_puzzle_hash}"); + return Ok(()); + }; + if pool_url.is_empty() || new_pos.proof.pool_contract_puzzle_hash.is_none() { + return Ok(()); + } + + let (required_iters, pool_dif) = if let Some(Some(pool_dif)) = self + .shared_state + .pool_states + .read() + .await + .get(p2_singleton_puzzle_hash) + .map(|v| v.current_difficulty) + { + ( + calculate_iterations_quality( + self.constants.difficulty_constant_factor, + qs, + new_pos.proof.size, + pool_dif, + &new_pos.sp_hash, + ), + pool_dif, + ) + } else { + warn!("No pool specific difficulty has been set for {p2_singleton_puzzle_hash}, check communication with the pool, skipping this partial to {}.", pool_url); + return Ok(()); + }; + let pool_required_iters = + calculate_sp_interval_iters(self.constants, self.constants.pool_sub_slot_iters)?; + if required_iters > pool_required_iters { + warn!( + "Proof of space not good enough for pool {}: {:?} {qs:?}", + pool_url, pool_dif + ); + return Ok(()); + } + let auth_token_timeout = if let Some(Some(auth_token_timeout)) = self + .shared_state + .pool_states + .read() + .await + .get(p2_singleton_puzzle_hash) + .map(|v| v.authentication_token_timeout) + { + auth_token_timeout + } else { + warn!("No pool specific authentication_token_timeout has been set for {p2_singleton_puzzle_hash}, check communication with the pool."); + return Ok(()); + }; + let shared_state = self.shared_state.clone(); + let payload = PostPartialPayload { + launcher_id, + authentication_token: get_current_authentication_token(auth_token_timeout), + proof_of_space: new_pos.proof.clone(), + sp_hash: new_pos.sp_hash, + end_of_sub_slot: new_pos.signage_point_index == 0, + harvester_id: load_client_id(shared_state.as_ref()).await?, + }; + let payload_bytes = hash_256(payload.to_bytes(PROTOCOL_VERSION)); + let sp_src_data = { + if new_pos.include_source_signature_data + || new_pos.farmer_reward_address_override.is_some() + { + Some(vec![Some(SignatureRequestSourceData { + kind: SigningDataKind::Partial, + data: payload.to_bytes(PROTOCOL_VERSION), + })]) + } else { + None + } + }; + let request = RequestSignatures { + plot_identifier: new_pos.plot_identifier.clone(), + challenge_hash: new_pos.challenge_hash, + sp_hash: new_pos.sp_hash, + messages: vec![Bytes32::new(&payload_bytes)], + message_data: sp_src_data, + rc_block_unfinished: None, + }; + let handler = PartialHandler { + pool_client: self.pool_client.clone(), + shared_state: self.shared_state.clone(), + p2_singleton_puzzle_hash: *p2_singleton_puzzle_hash, + new_pos, + auth_token_timeout, + payload, + payload_bytes, + pool_dif, + }; + if let Some(h) = self.harvesters.get(&self.harvester_id) { + let harvester = h.clone(); + tokio::spawn(async move { + match harvester.as_ref() { + Harvesters::DruidGarden(h) => { + if let Err(e) = h.request_signatures(request, handler).await { + error!("Error Requesting Signature: {}", e); } - } else { - warn!("No pool specific authentication_token_timeout has been set for {p2_singleton_puzzle_hash}, check communication with the pool."); } - } else { - warn!("No pool specific difficulty has been set for {p2_singleton_puzzle_hash}, check communication with the pool, skipping this partial to {}.", pool_config.pool_url); } - } else { - warn!("No Pool Config for {p2_singleton_puzzle_hash}"); - } + }); } else { - warn!("Did not find pool info for {p2_singleton_puzzle_hash}"); + error!("Failed to find harvester with ID {}", &self.harvester_id); } Ok(()) } @@ -369,6 +406,7 @@ pub struct PartialHandler { pub new_pos: NewProofOfSpace, pub payload: PostPartialPayload, pub payload_bytes: Vec, + pub pool_dif: u64, } #[async_trait] impl SignatureHandler for PartialHandler { @@ -418,86 +456,137 @@ impl SignatureHandler for Partial break; } } - - if let Some(pool_state) = self + if self + .shared_state + .pool_states + .read() + .await + .get(&self.p2_singleton_puzzle_hash) + .is_none() + { + warn!( + "Did not find pool info for {}", + &self.p2_singleton_puzzle_hash + ); + return Ok(()); + } + let (pool_url, owner_public_key) = if let Some(Some(config)) = self .shared_state .pool_states - .lock() + .read() .await - .get_mut(&self.p2_singleton_puzzle_hash) + .get(&self.p2_singleton_puzzle_hash) + .map(|v| v.pool_config.as_ref()) + { + (config.pool_url.clone(), config.owner_public_key) + } else { + warn!("No Pool Config for {}", self.p2_singleton_puzzle_hash); + return Ok(()); + }; + if let Some(auth_key) = self + .shared_state + .owner_public_keys_to_auth_secret_keys + .get(&owner_public_key) { - if let Some(pool_config) = &pool_state.pool_config { - if let Some(auth_key) = self + let auth_sig = sign(auth_key, &self.payload_bytes); + if let Some(plot_sig) = plot_sig { + let agg_sig = + AggregateSignature::aggregate(&[&plot_sig.to_signature(), &auth_sig], true) + .map_err(|e| Error::new(ErrorKind::InvalidInput, format!("{:?}", e)))?; + let post_request = PostPartialRequest { + payload: self.payload.clone(), + aggregate_signature: agg_sig.to_signature().to_bytes().into(), + }; + debug!( + "Submitting partial for {} to {}", + post_request.payload.launcher_id.to_string(), + &pool_url + ); + if let Some(v) = self .shared_state - .auth_secret_keys - .get(&pool_config.owner_public_key) + .pool_states + .write() + .await + .get_mut(&self.p2_singleton_puzzle_hash) { - let auth_sig = sign(auth_key, &self.payload_bytes); - if let Some(plot_sig) = plot_sig { - let agg_sig = AggregateSignature::aggregate( - &[&plot_sig.to_signature(), &auth_sig], - true, - ) - .map_err(|e| Error::new(ErrorKind::InvalidInput, format!("{:?}", e)))?; - let post_request = PostPartialRequest { - payload: self.payload.clone(), - aggregate_signature: agg_sig.to_signature().to_bytes().into(), - }; - debug!( - "Submitting partial for {} to {}", - post_request.payload.launcher_id.to_string(), - &pool_config.pool_url - ); - match self - .pool_client - .post_partial( - &pool_config.pool_url, - post_request, - &Some(HEADERS.clone()), - ) + v.points_found_since_start += self.pool_dif; + v.points_found_24h.push((Instant::now(), self.pool_dif)); + } + if let Some(r) = self.shared_state.metrics.read().await.as_ref() { + use std::time::Duration; + let now = Instant::now(); + if let Some(c) = &r.points_found_24h { + if let Some(v) = self + .shared_state + .pool_states + .write() + .await + .get_mut(&self.p2_singleton_puzzle_hash) + { + c.with_label_values(&[&&self.p2_singleton_puzzle_hash.to_string()]) + .set( + v.points_found_24h + .iter() + .filter(|v| { + now.duration_since(v.0) + < Duration::from_secs(60 * 60 * 24) + }) + .map(|v| v.1) + .sum(), + ) + } + } + } + match self + .pool_client + .post_partial(&pool_url, post_request, &Some(HEADERS.clone())) + .await + { + Ok(resp) => { + if let Some(v) = self + .shared_state + .pool_states + .write() .await + .get_mut(&self.p2_singleton_puzzle_hash) { - Ok(resp) => { - pool_state.current_points += resp.new_difficulty; - if pool_state.current_difficulty != Some(resp.new_difficulty) { - info!( - "New Pool Difficulty: {:?} ", - pool_state.current_difficulty - ); - pool_state.current_difficulty = Some(resp.new_difficulty); - } - info!("Current Points: {:?} ", pool_state.current_points); + v.current_points += resp.new_difficulty; + if v.current_difficulty != Some(resp.new_difficulty) { + info!("New Pool Difficulty: {:?} ", v.current_difficulty); + v.current_difficulty = Some(resp.new_difficulty); } - Err(e) => { - error!("Error in pooling: {:?}", e); - if e.error_code == PoolErrorCode::ProofNotGoodEnough as u8 { - error!("Partial not good enough, forcing pool farmer update to get our current difficulty."); - self.shared_state - .data - .force_pool_update - .store(true, Ordering::Relaxed); - } - if e.error_code == PoolErrorCode::InvalidSignature as u8 { - error!("Invalid Signature, Forcing Pool Update"); - pool_state.next_farmer_update = Instant::now(); - } + info!("Current Points: {:?} ", v.current_points); + } + } + Err(e) => { + error!("Error in pooling: {:?}", e); + if e.error_code == PoolErrorCode::ProofNotGoodEnough as u8 { + error!("Partial not good enough, forcing pool farmer update to get our current difficulty."); + self.shared_state + .data + .force_pool_update + .store(true, Ordering::Relaxed); + } + if e.error_code == PoolErrorCode::InvalidSignature as u8 { + error!("Invalid Signature, Forcing Pool Update"); + if let Some(v) = self + .shared_state + .pool_states + .write() + .await + .get_mut(&self.p2_singleton_puzzle_hash) + { + v.next_farmer_update = Instant::now(); } } - } else { - warn!("invalid plot Sig"); } - } else { - warn!( - "No authentication sk for {}", - &self.p2_singleton_puzzle_hash - ); } } else { - warn!("No Pool Config for {}", &self.p2_singleton_puzzle_hash); + warn!("invalid plot Sig"); } } else { warn!( - "Did not find pool info for {}", + "No authentication sk for {}", &self.p2_singleton_puzzle_hash ); } diff --git a/src/farmer/protocols/harvester/respond_signatures.rs b/src/farmer/protocols/harvester/respond_signatures.rs index b5b1593..a157034 100644 --- a/src/farmer/protocols/harvester/respond_signatures.rs +++ b/src/farmer/protocols/harvester/respond_signatures.rs @@ -1,5 +1,6 @@ use crate::farmer::{ExtendedFarmerSharedState, FarmerSharedState}; use crate::harvesters::{Harvesters, SignatureHandler}; +use crate::PROTOCOL_VERSION; use async_trait::async_trait; use blst::min_pk::AggregateSignature; use blst::BLST_ERROR; @@ -12,7 +13,7 @@ use dg_xch_core::consensus::constants::ConsensusConstants; use dg_xch_core::protocols::farmer::{DeclareProofOfSpace, SignedValues}; use dg_xch_core::protocols::harvester::RespondSignatures; use dg_xch_core::protocols::{ChiaMessage, ProtocolMessageTypes}; -use dg_xch_keys::decode_puzzle_hash; +use dg_xch_keys::{decode_puzzle_hash, parse_payout_address}; use dg_xch_pos::verify_and_get_quality_string; use dg_xch_serialize::ChiaSerialize; use log::{debug, error, info, warn}; @@ -36,7 +37,7 @@ impl SignatureHandler if let Some(sps) = self .shared_state .signage_points - .lock() + .read() .await .get(&response.sp_hash) { @@ -67,20 +68,22 @@ impl SignatureHandler )); } let mut pospace = None; + if let Some(proofs) = self + .shared_state + .proofs_of_space + .read() + .await + .get(&response.sp_hash) { - let locked = self.shared_state.proofs_of_space.lock().await; - let proofs = locked.get(&response.sp_hash); - if let Some(proofs) = proofs { - for (plot_identifier, candidate_pospace) in proofs { - if *plot_identifier == response.plot_identifier { - pospace = Some(candidate_pospace.clone()); - break; - } + for (plot_identifier, candidate_pospace) in proofs { + if *plot_identifier == response.plot_identifier { + pospace = Some(candidate_pospace.clone()); + break; } - } else { - debug!("Failed to load farmer proofs for {}", &response.sp_hash); - return Ok(()); } + } else { + debug!("Failed to load farmer proofs for {}", &response.sp_hash); + return Ok(()); } if let Some(pospace) = pospace { let include_taproot = pospace.pool_contract_puzzle_hash.is_some(); @@ -218,7 +221,7 @@ impl SignatureHandler )?, }; let pool_target_signature = - sign(sk, &pool_target.to_bytes()); + sign(sk, &pool_target.to_bytes(PROTOCOL_VERSION)); (Some(pool_target), Some(pool_target_signature)) } else { error!("Don't have the private key for the pool key used by harvester: {pool_public_key}"); @@ -248,9 +251,9 @@ impl SignatureHandler { farmer_reward_address_override } else { - decode_puzzle_hash( + Bytes32::from(parse_payout_address( &self.shared_state.data.config.payout_address, - )? + )?) }, pool_target, pool_signature: pool_target_signature @@ -263,22 +266,23 @@ impl SignatureHandler .shared_state .data .full_node_client - .lock() + .read() .await - .as_mut() + .as_ref() { let _ = client .client .connection - .lock() + .write() .await .send(Message::Binary( ChiaMessage::new( ProtocolMessageTypes::DeclareProofOfSpace, + PROTOCOL_VERSION, &request, None, ) - .to_bytes(), + .to_bytes(PROTOCOL_VERSION), )) .await; debug!("Declaring Proof of Space: {:?}", request); @@ -288,7 +292,17 @@ impl SignatureHandler " Declaring New Proof of Space ", 56 ); let bottom = format!("{:🌱<1$}", "", 43); - info!("\n{top}\n{mid}\n{bottom}") + info!("\n{top}\n{mid}\n{bottom}"); + if let Some(Some(declared)) = self + .shared_state + .metrics + .read() + .await + .as_ref() + .map(|v| &v.proofs_declared) + { + declared.inc(); + } } else { error!( "Failed to declare Proof of Space: {:?} No Client", @@ -424,22 +438,23 @@ impl SignatureHandler .shared_state .data .full_node_client - .lock() + .read() .await - .as_mut() + .as_ref() { let _ = client .client .connection - .lock() + .write() .await .send(Message::Binary( ChiaMessage::new( ProtocolMessageTypes::SignedValues, + PROTOCOL_VERSION, &request, None, ) - .to_bytes(), + .to_bytes(PROTOCOL_VERSION), )) .await; debug!("Sending Signed Values: {:?}", request); diff --git a/src/gui/mod.rs b/src/gui/mod.rs index 72dd2a0..a65dfeb 100644 --- a/src/gui/mod.rs +++ b/src/gui/mod.rs @@ -1,13 +1,14 @@ +use actix_web::web::Data; +use actix_web::{App, HttpServer}; use crossterm::{ event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyModifiers}, execute, terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, }; use std::io::Error; -use std::path::Path; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; -use std::time::{Duration, Instant}; +use std::time::Duration; use ratatui::style::Stylize; use ratatui::{prelude::*, widgets::*}; @@ -16,20 +17,19 @@ use tui_logger::*; use crate::farmer::config::{load_keys, Config}; use crate::farmer::{ExtendedFarmerSharedState, Farmer, GuiStats}; +use crate::metrics; +use crate::tasks::blockchain_state_updater::update_blockchain; use crate::tasks::pool_state_updater::pool_updater; use chrono::prelude::*; -use dg_xch_clients::api::full_node::FullnodeAPI; use dg_xch_clients::api::pool::DefaultPoolClient; -use dg_xch_clients::rpc::full_node::FullnodeClient; -use dg_xch_clients::ClientSSLConfig; use dg_xch_core::blockchain::blockchain_state::BlockchainState; use dg_xch_core::protocols::farmer::FarmerSharedState; use log::{error, LevelFilter}; use sysinfo::System; -use tokio::join; use tokio::sync::Mutex; use tokio::task::{spawn_blocking, JoinHandle}; use tokio::time::sleep; +use tokio::{join, select}; #[derive(Debug, Clone, Copy, Eq, PartialEq)] pub enum Action { @@ -43,14 +43,15 @@ pub enum Action { #[derive(Copy, Clone, Default, Debug, PartialEq)] struct SysInfo { cpu_usage: u16, - ram_usage: u16, - swap_usage: u16, + ram_used: u64, + ram_total: u64, + swap_used: u64, + swap_total: u64, } struct GuiState { system_info: Arc>, farmer_state: Arc>, - fullnode_state: Arc>>, } impl GuiState { @@ -58,7 +59,6 @@ impl GuiState { GuiState { system_info: Default::default(), farmer_state: shared_state, - fullnode_state: Arc::new(Mutex::new(None)), } } } @@ -77,7 +77,7 @@ pub async fn bootstrap(config: Arc) -> Result<(), Error> { let shared_state = Arc::new(FarmerSharedState { farmer_private_keys: Arc::new(farmer_private_keys), owner_secret_keys: Arc::new(owner_secret_keys), - auth_secret_keys: Arc::new(auth_secret_keys), + owner_public_keys_to_auth_secret_keys: Arc::new(auth_secret_keys), pool_public_keys: Arc::new(pool_public_keys), data: Arc::new(ExtendedFarmerSharedState { config: config.clone(), @@ -108,47 +108,7 @@ pub async fn bootstrap(config: Arc) -> Result<(), Error> { }); let fullnode_state = gui_state.clone(); let fullnode_thread = tokio::spawn(async move { - let full_node_rpc = FullnodeClient::new( - &config.fullnode_rpc_host, - config.fullnode_rpc_port, - 60, - config.ssl_root_path.clone().map(|s| ClientSSLConfig { - ssl_crt_path: Path::new(&s) - .join("farmer/private_daemon.crt") - .to_string_lossy() - .to_string(), - ssl_key_path: Path::new(&s) - .join("farmer/private_daemon.key") - .to_string_lossy() - .to_string(), - ssl_ca_crt_path: Path::new(&s) - .join("ca/private_ca.crt") - .to_string_lossy() - .to_string(), - }), - &None, - ); - let mut last_update = Instant::now(); - loop { - if last_update.elapsed().as_secs() > 5 { - last_update = Instant::now(); - let bc_state = full_node_rpc.get_blockchain_state().await; - match bc_state { - Ok(bc_state) => { - *fullnode_state.fullnode_state.lock().await = Some(FullNodeState { - blockchain_state: bc_state, - }); - } - Err(e) => { - error!("{:?}", e); - } - } - } - if !fullnode_state.farmer_state.data.run.load(Ordering::Relaxed) { - break; - } - tokio::time::sleep(Duration::from_millis(25)).await; - } + update_blockchain(config.clone(), fullnode_state.farmer_state.clone()).await }); let sys_info_gui_state = gui_state.clone(); let sys_info_thread = tokio::spawn(async move { @@ -159,20 +119,20 @@ pub async fn bootstrap(config: Arc) -> Result<(), Error> { system.refresh_cpu(); system.refresh_memory(); loop { - let sys_info_res = spawn_blocking(move || { + let results = spawn_blocking(move || { system.refresh_cpu(); system.refresh_memory(); let si = SysInfo { cpu_usage: system.global_cpu_info().cpu_usage() as u16, - ram_usage: ((system.used_memory() as f32 / system.total_memory() as f32) - * 100.0) as u16, - swap_usage: ((system.used_swap() as f32 / system.total_swap() as f32) * 100.0) - as u16, + ram_used: system.used_memory(), + ram_total: system.total_memory(), + swap_used: system.used_swap(), + swap_total: system.total_swap(), }; (system, si) }) .await; - let (sys, sys_info) = sys_info_res.unwrap_or_else(|e| { + let (sys, sys_info) = results.unwrap_or_else(|e| { error!("Error Joining System Loading Thread: {:?}", e); (System::new(), Default::default()) }); @@ -189,11 +149,42 @@ pub async fn bootstrap(config: Arc) -> Result<(), Error> { tokio::time::sleep(Duration::from_millis(25)).await; } }); - let (fn_res, sys_res, gui_res, farmer_res) = join!( + let metrics = shared_state.data.config.metrics.clone().unwrap_or_default(); + let metrics_state = shared_state.clone(); + let server_handle = if metrics.enabled { + tokio::spawn(async move { + let server_state = metrics_state.clone(); + select! { + _ = HttpServer::new(move || { + App::new() + .app_data(Data::new(server_state.clone())) + .configure(metrics::init) + }) + .bind(("0.0.0.0", metrics.port))? + .run() + => { + Ok(()) + } + _ = async { + loop { + if !metrics_state.data.run.load(Ordering::Relaxed) { + break; + } + } + } => { + Ok(()) + } + } + }) + } else { + tokio::spawn(async move { Ok::<(), Error>(()) }) + }; + let (fn_res, sys_res, gui_res, farmer_res, server_res) = join!( fullnode_thread, sys_info_thread, run_gui(&mut terminal, gui_state), - farmer_thread + farmer_thread, + server_handle ); disable_raw_mode()?; execute!( @@ -214,6 +205,9 @@ pub async fn bootstrap(config: Arc) -> Result<(), Error> { if let Err(err) = farmer_res { eprintln!("Error Joining Farmer Thread {err:?}"); } + if let Err(err) = server_res { + eprintln!("Error Joining Server Thread {err:?}"); + } Ok(()) } @@ -223,9 +217,15 @@ async fn run_gui( ) -> std::io::Result<()> { loop { { - let farmer_state = gui_state.farmer_state.data.gui_stats.lock().await.clone(); + let farmer_state = gui_state.farmer_state.data.gui_stats.read().await.clone(); let sys_info = *gui_state.system_info.lock().await; - let fullnode_state = gui_state.fullnode_state.lock().await.clone(); + let fullnode_state = gui_state + .farmer_state + .data + .fullnode_state + .read() + .await + .clone(); terminal.draw(|f| ui(f, farmer_state, fullnode_state, sys_info))?; } if event::poll(Duration::from_millis(25))? { @@ -327,9 +327,7 @@ fn ui( mempool_size = full_node.blockchain_state.mempool_size; } let formatted_timestamp: String = if timestamp != 0 { - let naive = NaiveDateTime::from_timestamp_opt(timestamp as i64, 0); - let datetime: DateTime = - DateTime::from_naive_utc_and_offset(naive.unwrap_or_default(), Utc); + let datetime = DateTime::from_timestamp(timestamp as i64, 0).unwrap_or_default(); datetime.format("%d-%m-%Y %H:%M:%S").to_string() } else { "N/A".to_string() @@ -368,11 +366,27 @@ fn ui( .borders(Borders::ALL), ); f.render_widget(fullnode_content, overview_chunks[2]); - let cpu_usage_widget = draw_gauge("CPU Usage", sys_info.cpu_usage); + let cpu_usage_widget = draw_gauge("System CPU Usage", sys_info.cpu_usage); f.render_widget(cpu_usage_widget, overview_chunks[3]); - let ram_usage_widget = draw_gauge("RAM Usage", sys_info.ram_usage); + let ram_title = format!( + "System RAM Usage: {:.2}Gb/{:.2}Gb", + sys_info.ram_used as f32 / 1024f32 / 1024f32 / 1024f32, + sys_info.ram_total as f32 / 1024f32 / 1024f32 / 1024f32 + ); + let ram_usage_widget = draw_gauge( + &ram_title, + ((sys_info.ram_used as f32 / sys_info.ram_total as f32) * 100f32) as u16, + ); f.render_widget(ram_usage_widget, overview_chunks[4]); - let swap_usage_widget = draw_gauge("Swap Usage", sys_info.swap_usage); + let swap_title = format!( + "System Swap Usage: {:.2}Gb/{:.2}Gb", + sys_info.swap_used as f32 / 1024f32 / 1024f32 / 1024f32, + sys_info.swap_total as f32 / 1024f32 / 1024f32 / 1024f32 + ); + let swap_usage_widget = draw_gauge( + &swap_title, + ((sys_info.swap_used as f32 / sys_info.swap_total as f32) * 100f32) as u16, + ); f.render_widget(swap_usage_widget, overview_chunks[5]); let logs_widget = draw_logs(); diff --git a/src/harvesters/druid_garden.rs b/src/harvesters/druid_garden.rs index 363473b..3d45061 100644 --- a/src/harvesters/druid_garden.rs +++ b/src/harvesters/druid_garden.rs @@ -1,5 +1,7 @@ -use crate::farmer::{PathInfo, PlotInfo}; +use crate::farmer::{ExtendedFarmerSharedState, PathInfo, PlotInfo}; use crate::harvesters::{FarmingKeys, Harvester, ProofHandler, SignatureHandler}; +use crate::metrics::Metrics; +use crate::PROTOCOL_VERSION; use async_trait::async_trait; use blst::min_pk::{PublicKey, SecretKey}; use dg_xch_core::blockchain::proof_of_space::{ @@ -12,6 +14,7 @@ use dg_xch_core::consensus::pot_iterations::{ calculate_iterations_quality, calculate_sp_interval_iters, }; use dg_xch_core::plots::PlotHeader; +use dg_xch_core::protocols::farmer::FarmerSharedState; use dg_xch_core::protocols::harvester::{ NewProofOfSpace, NewSignagePointHarvester, RequestSignatures, RespondSignatures, }; @@ -29,7 +32,7 @@ use rand::random; use std::collections::{HashMap, HashSet}; use std::io::{Error, ErrorKind}; use std::path::{Path, PathBuf}; -use std::sync::atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering}; +use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; use std::sync::Arc; use std::thread::available_parallelism; use std::time::{Duration, Instant, SystemTime}; @@ -38,12 +41,12 @@ use tokio::time::timeout; #[derive(Default)] pub struct PlotCounts { - pub og_passed: Arc, - pub og_total: Arc, - pub pool_total: Arc, - pub pool_passed: Arc, - pub compressed_passed: Arc, - pub compressed_total: Arc, + pub og_passed: Arc, + pub og_total: Arc, + pub pool_total: Arc, + pub pool_passed: Arc, + pub compressed_passed: Arc, + pub compressed_total: Arc, } pub struct DruidGardenHarvester { @@ -55,6 +58,7 @@ pub struct DruidGardenHarvester { pub selected_network: String, pub uuid: Bytes32, pub client_id: Bytes32, + pub shared_state: Arc>, } #[async_trait] impl Harvester for DruidGardenHarvester { @@ -66,6 +70,12 @@ impl Harvester for DruidGardenHarvester { where T: ProofHandler + Sync + Send, { + let start = self + .shared_state + .data + .extended_metrics + .signage_point_processing_latency + .start_timer(); let plot_counts = Arc::new(PlotCounts::default()); let harvester_point = Arc::new(signage_point); let constants = Arc::new( @@ -81,6 +91,7 @@ impl Harvester for DruidGardenHarvester { let data_arc = harvester_point.clone(); let constants_arc = constants.clone(); let plot_counts = plot_counts.clone(); + let metrics = self.shared_state.data.extended_metrics.clone(); let mut responses = vec![]; let plot_handle = timeout(Duration::from_secs(20), tokio::spawn(async move { let (plot_id, k, memo, c_level) = match plot_info.reader.header() { @@ -89,7 +100,7 @@ impl Harvester for DruidGardenHarvester { PlotHeader::GHv2_5(_) => { return Err(Error::new( ErrorKind::InvalidInput, - "Use the Gigahorse FastFarmer to Farm Gigahorse Plots", + "To Farm Gigahorse Plots you need to enable the Gigahorse Harvester", )); } }; @@ -150,6 +161,7 @@ impl Harvester for DruidGardenHarvester { } } for (index, quality) in qualities.into_iter() { + let start = metrics.qualities_latency.start_timer(); let required_iters = calculate_iterations_quality( constants_arc.difficulty_constant_factor, &quality, @@ -157,10 +169,12 @@ impl Harvester for DruidGardenHarvester { dif, &data_arc.sp_hash, ); + start.stop_and_record(); if let Ok(sp_interval_iters) = calculate_sp_interval_iters(&constants_arc, sub_slot_iters) { if required_iters < sp_interval_iters { + let start = metrics.proof_latency.start_timer(); match plot_info.reader.fetch_ordered_proof(index).await { Ok(proof) => { let proof_bytes = proof_to_bytes(&proof); @@ -168,7 +182,7 @@ impl Harvester for DruidGardenHarvester { "File: {:?} Plot ID: {}, challenge: {sp_challenge_hash}, Quality Str: {}, proof_xs: {}", path, &plot_id, - encode(quality.to_bytes()), + encode(quality.to_bytes(PROTOCOL_VERSION)), encode(&proof_bytes) ); responses.push(( @@ -191,6 +205,7 @@ impl Harvester for DruidGardenHarvester { error!("Failed to read Proof: {:?}", e); } } + start.stop_and_record(); } else { debug!( "Not Enough Iterations: {} > {}", @@ -218,7 +233,7 @@ impl Harvester for DruidGardenHarvester { .handle_proof(NewProofOfSpace { challenge_hash: harvester_point.challenge_hash, sp_hash: harvester_point.sp_hash, - plot_identifier: encode(quality.to_bytes()) + plot_identifier: encode(quality.to_bytes(PROTOCOL_VERSION)) + path.file_name.as_str(), proof, signage_point_index: harvester_point.signage_point_index, @@ -253,6 +268,11 @@ impl Harvester for DruidGardenHarvester { } } } + let finished = start.stop_and_record(); + info!( + "Finished Processing SP({}) in {:.3} seconds", + harvester_point.sp_hash, finished + ); info!( "Passed Filter - OG: {}/{}. NFT: {}/{}. Compressed: {}/{}. Proofs Found: {}. Partials Found: NFT({}), Compressed({})", plot_counts.og_passed.load(Ordering::Relaxed), @@ -265,6 +285,41 @@ impl Harvester for DruidGardenHarvester { nft_partials.load(Ordering::Relaxed), compressed_partials.load(Ordering::Relaxed), ); + self.shared_state + .data + .extended_metrics + .total_proofs_found + .inc_by(proofs.load(Ordering::Relaxed)); + self.shared_state + .data + .extended_metrics + .last_proofs_found + .set(proofs.load(Ordering::Relaxed)); + let total_partials = + nft_partials.load(Ordering::Relaxed) + compressed_partials.load(Ordering::Relaxed); + self.shared_state + .data + .extended_metrics + .total_partials_found + .inc_by(total_partials); + self.shared_state + .data + .extended_metrics + .last_partials_found + .set(total_partials); + let total_passed = plot_counts.og_passed.load(Ordering::Relaxed) + + plot_counts.pool_passed.load(Ordering::Relaxed) + + plot_counts.compressed_passed.load(Ordering::Relaxed); + self.shared_state + .data + .extended_metrics + .total_passed_filter + .inc_by(total_passed); + self.shared_state + .data + .extended_metrics + .last_passed_filter + .set(total_passed); Ok(()) } @@ -294,10 +349,7 @@ impl Harvester for DruidGardenHarvester { PlotHeader::GHv2_5(_) => { return Err(Error::new( ErrorKind::InvalidInput, - format!( - "Use the Gigahorse FastFarmer to Farm Gigahorse Plots: {}", - file_name - ), + format!("To Farm Gigahorse Plots you need to enable the Gigahorse Harvester: {}", file_name), )); } }, @@ -343,6 +395,7 @@ impl DruidGardenHarvester { shutdown_signal: Arc, selected_network: &str, client_id: Bytes32, + shared_state: Arc>, ) -> Result { let decompressor_pool = Arc::new(DecompressorPool::new( 1, @@ -357,6 +410,7 @@ impl DruidGardenHarvester { &farming_keys.pool_contract_hashes, vec![], decompressor_pool.clone(), + shared_state.data.extended_metrics.clone(), ) .await?, )); @@ -364,6 +418,7 @@ impl DruidGardenHarvester { let plot_sync_dirs = plot_dirs.clone(); let plot_sync_farming_keys = farming_keys.clone(); let plot_sync_decompressor_pool = decompressor_pool.clone(); + let plot_sync_shared_state = shared_state.clone(); let _plot_sync = tokio::spawn(async move { let mut last_sync = Instant::now(); loop { @@ -386,6 +441,7 @@ impl DruidGardenHarvester { &plot_sync_farming_keys.pool_contract_hashes, existing_plot_paths.as_ref().clone(), plot_sync_decompressor_pool.clone(), + plot_sync_shared_state.data.extended_metrics.clone(), ) .await { @@ -410,6 +466,7 @@ impl DruidGardenHarvester { selected_network: selected_network.to_string(), client_id, uuid: Bytes32::from(random::<[u8; 32]>()), + shared_state, }) } } @@ -421,6 +478,7 @@ async fn load_plots( pool_contract_hashes: &[Bytes32], existing_plot_paths: Vec, decompressor_pool: Arc, + metrics: Arc, ) -> Result>, Error> { debug!("Started Loading Plots"); if farmer_public_keys.is_empty() { @@ -444,6 +502,7 @@ async fn load_plots( let pool_contract_hashes = pool_contract_hashes.clone(); let existing_paths = existing_paths.clone(); let decompressor_pool = decompressor_pool.clone(); + let metrics = metrics.clone(); let dir = dir.clone(); debug!("Validating Plot Directory: {:?}", &dir); futures.push(timeout( @@ -488,6 +547,7 @@ async fn load_plots( continue; } }; + let plot_load = metrics.plot_load_latency.start_timer(); let plot_file = match DiskPlot::new(&path).await { Ok(plot_file) => plot_file, Err(e) => { @@ -496,6 +556,7 @@ async fn load_plots( continue; } }; + plot_load.stop_and_record(); match PlotReader::new( plot_file, Some(decompressor_pool.clone()), @@ -636,7 +697,7 @@ async fn load_headers( PlotHeader::GHv2_5(_) => { return Err(Error::new( ErrorKind::InvalidInput, - "Use the Gigahorse FastFarmer to Farm Gigahorse Plots", + "Use the Gigahorse Harvester to Farm Gigahorse Plots", )) } }; diff --git a/src/harvesters/mod.rs b/src/harvesters/mod.rs index 21d81b1..969ef8e 100644 --- a/src/harvesters/mod.rs +++ b/src/harvesters/mod.rs @@ -1,6 +1,6 @@ pub mod druid_garden; -use crate::cli::get_ssl_root_path; +use crate::cli::utils::get_ssl_root_path; use crate::farmer::ExtendedFarmerSharedState; use crate::harvesters::druid_garden::DruidGardenHarvester; use async_trait::async_trait; @@ -72,7 +72,7 @@ pub async fn load_harvesters( pool_public_keys.push(p_sk.sk_to_pk().to_bytes().into()); } } - shared_state.data.gui_stats.lock().await.keys = farmer_public_keys.clone(); + shared_state.data.gui_stats.write().await.keys = farmer_public_keys.clone(); let pool_contract_hashes = shared_state .data .config @@ -103,6 +103,7 @@ pub async fn load_harvesters( shared_state.data.run.clone(), &shared_state.data.config.selected_network, client_id, + shared_state.clone(), ) .await?; harvesters.insert( @@ -110,8 +111,8 @@ pub async fn load_harvesters( Arc::new(Harvesters::DruidGarden(harvester)), ); } - shared_state.data.gui_stats.lock().await.total_plot_count = sum; - shared_state.data.gui_stats.lock().await.total_plot_space = total_size; + shared_state.data.gui_stats.write().await.total_plot_count = sum; + shared_state.data.gui_stats.write().await.total_plot_space = total_size; Ok(Arc::new(harvesters)) } diff --git a/src/main.rs b/src/main.rs index e19acc9..d22a16b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,30 +1,23 @@ -use crate::cli::{ - generate_config_from_mnemonic, get_config_path, join_pool, load_mnemonic_from_file, - prompt_for_mnemonic, update_pool_info, Action, Cli, GenerateConfig, +use crate::cli::commands::{ + cli_mode, generate_config_from_mnemonic, join_pool, tui_mode, update, update_pool_info, + GenerateConfig, }; -use crate::farmer::config::{load_keys, Config}; -use crate::farmer::{ExtendedFarmerSharedState, Farmer}; -use crate::tasks::pool_state_updater::pool_updater; +use crate::cli::utils::{check_config, get_config_path, init_logger}; +use crate::cli::{Action, Cli, RunMode}; +use crate::farmer::config::Config; use clap::Parser; -use dg_xch_clients::api::pool::DefaultPoolClient; use dg_xch_core::blockchain::sized_bytes::Bytes32; -use dg_xch_core::consensus::constants::{CONSENSUS_CONSTANTS_MAP, MAINNET}; -use dg_xch_core::protocols::farmer::FarmerSharedState; -use dg_xch_core::utils::await_termination; -use hex::encode; -use log::{error, info, LevelFilter}; +use dg_xch_keys::{encode_puzzle_hash, parse_payout_address}; +use dg_xch_serialize::ChiaProtocolVersion; use once_cell::sync::Lazy; use reqwest::header::USER_AGENT; -use simple_logger::SimpleLogger; use std::collections::HashMap; use std::env; use std::io::Error; use std::path::PathBuf; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::Arc; use tokio::fs::create_dir_all; -use tokio::join; -use tokio::task::JoinHandle; + +const PROTOCOL_VERSION: ChiaProtocolVersion = ChiaProtocolVersion::Chia0_0_36; fn _version() -> &'static str { env!("CARGO_PKG_VERSION") @@ -32,10 +25,18 @@ fn _version() -> &'static str { fn _pkg_name() -> &'static str { env!("CARGO_PKG_NAME") } - +fn gh_version() -> &'static str { + "3.0" +} +fn chia_version() -> &'static str { + "2.2.1" +} pub fn version() -> String { format!("{}: {}", _pkg_name(), _version()) } +pub fn header_version() -> String { + format!("{}={}", _pkg_name(), _version()) +} #[test] fn version_test() { @@ -48,8 +49,14 @@ pub static HEADERS: Lazy> = Lazy::new(|| { String::from("X-fast-farmer-version"), _version().to_string(), ); - headers.insert(USER_AGENT.to_string(), version()); + headers.insert(USER_AGENT.to_string(), header_version()); headers.insert(String::from("X-dg-xch-pos-version"), dg_xch_pos::version()); + headers.insert(String::from("X-gh-version"), gh_version().to_string()); + headers.insert(String::from("X-chia-version"), chia_version().to_string()); + headers.insert( + String::from("X-chia-protocol-version"), + PROTOCOL_VERSION.to_string(), + ); headers }); @@ -57,6 +64,7 @@ pub mod cli; pub mod farmer; pub mod gui; pub mod harvesters; +mod metrics; pub mod tasks; #[tokio::main] @@ -75,81 +83,12 @@ async fn main() -> Result<(), Error> { }; let action = cli.action.unwrap_or_default(); match action { - Action::Gui {} => { - if !config_path.exists() { - eprintln!( - "Failed to find config at {:?}, please run init", - config_path - ); - return Ok(()); + Action::Run { mode } => { + check_config(&config_path)?; + match mode.unwrap_or_default() { + RunMode::Cli => cli_mode(config_path.as_path()).await, + RunMode::Tui => tui_mode(config_path.as_path()).await, } - let config = Config::try_from(&config_path).unwrap_or_default(); - let config_arc = Arc::new(config); - gui::bootstrap(config_arc).await?; - Ok(()) - } - Action::Run {} => { - if !config_path.exists() { - eprintln!( - "Failed to find config at {:?}, please run init", - config_path - ); - return Ok(()); - } - SimpleLogger::new() - .with_colors(true) - .with_level(LevelFilter::Info) - .env() - .init() - .unwrap_or_default(); - let config = Config::try_from(&config_path).unwrap(); - let config_arc = Arc::new(config); - let constants = CONSENSUS_CONSTANTS_MAP - .get(&config_arc.selected_network) - .unwrap_or(&MAINNET); - info!( - "Selected Network: {}, AggSig: {}", - &config_arc.selected_network, - &encode(&constants.agg_sig_me_additional_data) - ); - let (farmer_private_keys, owner_secret_keys, auth_secret_keys, pool_public_keys) = - load_keys(config_arc.clone()).await; - let shared_state = Arc::new(FarmerSharedState { - farmer_private_keys: Arc::new(farmer_private_keys), - owner_secret_keys: Arc::new(owner_secret_keys), - auth_secret_keys: Arc::new(auth_secret_keys), - pool_public_keys: Arc::new(pool_public_keys), - data: Arc::new(ExtendedFarmerSharedState { - config: config_arc.clone(), - run: Arc::new(AtomicBool::new(true)), - ..Default::default() - }), - ..Default::default() - }); - - info!("Using Additional Headers: {:?}", &*HEADERS); - //Pool Updater vars - let pool_state = shared_state.clone(); - let pool_state_handle: JoinHandle<()> = - tokio::spawn(async move { pool_updater(pool_state).await }); - - //Signal Handler to Shutdown the Async processes - let signal_run = shared_state.data.run.clone(); - let signal_handle = tokio::spawn(async move { - let _ = await_termination().await; - signal_run.store(false, Ordering::Relaxed); - }); - - let pool_client = Arc::new(DefaultPoolClient::new()); - let farmer = Farmer::new(shared_state, pool_client).await?; - - //Client Vars - let client_handle: JoinHandle> = tokio::spawn(async move { - farmer.run().await; - Ok(()) - }); - let _ = join!(pool_state_handle, client_handle, signal_handle); - Ok(()) } Action::Init { fullnode_ws_host, @@ -163,20 +102,10 @@ async fn main() -> Result<(), Error> { mnemonic_file, launcher_id, } => { - SimpleLogger::new() - .with_colors(true) - .with_level(LevelFilter::Info) - .env() - .init() - .unwrap_or_default(); - let mnemonic = if let Some(mnemonic_file) = mnemonic_file { - load_mnemonic_from_file(mnemonic_file)? - } else { - prompt_for_mnemonic()? - }; + init_logger(); generate_config_from_mnemonic(GenerateConfig { output_path: Some(config_path), - mnemonic, + mnemonic_file, fullnode_ws_host, fullnode_ws_port, fullnode_rpc_host, @@ -191,48 +120,47 @@ async fn main() -> Result<(), Error> { .await?; Ok(()) } - Action::UpdatePoolInfo {} => { - SimpleLogger::new() - .with_colors(true) - .with_level(LevelFilter::Info) - .env() - .init() - .unwrap_or_default(); - if !config_path.exists() { - error!( - "Failed to find config at {:?}, please run init", - config_path - ); - return Ok(()); - } - let config = Config::try_from(&config_path).unwrap_or_default(); - let updated_config = update_pool_info(config).await?; + Action::Update {} => { + check_config(&config_path)?; + init_logger(); + let config = Config::try_from(&config_path)?; + let updated_config = update(config).await?; updated_config.save_as_yaml(config_path)?; Ok(()) } + Action::UpdatePoolInfo { launcher_id } => { + check_config(&config_path)?; + init_logger(); + let config = Config::try_from(&config_path)?; + let updated_config = update_pool_info(config, launcher_id).await?; + updated_config.save_as_yaml(config_path)?; + Ok(()) + } + Action::UpdatePayoutAddress { address } => { + check_config(&config_path)?; + init_logger(); + let mut config = Config::try_from(&config_path)?; + let payout_address = parse_payout_address(&address)?; + let xch_address = encode_puzzle_hash(&Bytes32::from(payout_address), "xch")?; + for pool_info in &mut config.pool_info { + pool_info.payout_instructions = xch_address.clone(); + } + config.payout_address = xch_address; + config.save_as_yaml(config_path)?; + Ok(()) + } Action::JoinPool { pool_url, - mnemonic, + mnemonic_file, launcher_id, fee, } => { - if !config_path.exists() { - eprintln!( - "Failed to find config at {:?}, please run init", - config_path - ); - return Ok(()); - } - SimpleLogger::new() - .with_colors(true) - .with_level(LevelFilter::Info) - .env() - .init() - .unwrap_or_default(); - let config = Config::try_from(&config_path).unwrap_or_default(); - let updated_config = join_pool(config, pool_url, mnemonic, launcher_id, fee).await?; + check_config(&config_path)?; + init_logger(); + let config = Config::try_from(&config_path).unwrap(); + let updated_config = + join_pool(config, pool_url, mnemonic_file, launcher_id, fee).await?; updated_config.save_as_yaml(config_path)?; - Ok(()) } } diff --git a/src/metrics/mod.rs b/src/metrics/mod.rs new file mode 100644 index 0000000..9b01b3e --- /dev/null +++ b/src/metrics/mod.rs @@ -0,0 +1,348 @@ +use crate::_version; +use crate::cli::utils::get_device_id_path; +use crate::farmer::ExtendedFarmerSharedState; +use actix_web::web::{Data, Json, ServiceConfig}; +use actix_web::{get, Error, HttpResponse}; +use dg_xch_core::protocols::farmer::FarmerSharedState; +use log::{debug, info}; +use prometheus::core::{ + AtomicU64, GenericCounter, GenericCounterVec, GenericGauge, GenericGaugeVec, +}; +use prometheus::{Histogram, HistogramOpts, Opts, Registry, TextEncoder}; +use protobuf::Message; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::fs; +use std::io::ErrorKind; +use std::sync::Arc; +use std::time::Instant; +use tokio::sync::RwLock; +use uuid::Uuid; + +const PLOT_LOAD_LATENCY_BUCKETS: [f64; 11] = [ + 0.01, + 0.05, + 0.1, + 0.25, + 0.5, + 1.0, + 2.5, + 5.0, + 10.0, + 30.0, + f64::INFINITY, +]; +const LATENCY_BUCKETS: [f64; 11] = [ + 0.01, + 0.05, + 0.1, + 0.25, + 0.5, + 1.0, + 2.5, + 5.0, + 10.0, + 30.0, + f64::INFINITY, +]; +const SP_INTERVAL_BUCKETS: [f64; 11] = [ + 0f64, + 4f64, + 8f64, + 12f64, + 16f64, + 20f64, + 25f64, + 30f64, + 45f64, + 60f64, + f64::INFINITY, +]; + +pub struct Metrics { + pub id: Uuid, + pub registry: Arc>, + pub qualities_latency: Arc, + pub proof_latency: Arc, + pub signage_point_interval: Arc, + pub signage_point_processing_latency: Arc, + pub plot_load_latency: Arc, + pub blockchain_synced: Arc>, + pub blockchain_height: Arc>, + pub blockchain_netspace: Arc>, + pub total_proofs_found: Arc>, + pub last_proofs_found: Arc>, + pub total_partials_found: Arc>, + pub last_partials_found: Arc>, + pub total_passed_filter: Arc>, + pub last_passed_filter: Arc>, + pub partials_submitted: Arc>, + pub plot_file_size: Arc>, + pub plot_counts: Arc>, +} +impl Default for Metrics { + fn default() -> Self { + let id = get_uuid().unwrap_or_else(|_| Uuid::new_v4()); + let metrics_registry = Registry::new_custom( + Some(String::from("fast_farmer")), + Some(HashMap::from([ + ("device_id".to_string(), id.to_string()), + ("fast_farmer_version".to_string(), _version().to_string()), + ])), + ) + .expect("Expected To Create Default Metrics Registry"); + Self { + id, + blockchain_synced: Arc::new( + GenericGauge::new("blockchain_synced", "Is Upstream Node Synced") + .map(|g: GenericGauge| { + metrics_registry.register(Box::new(g.clone())).unwrap_or(()); + g + }) + .expect("Expected To Create Static Metrics"), + ), + blockchain_height: Arc::new( + GenericGauge::new("blockchain_height", "Blockchain Height") + .map(|g: GenericGauge| { + metrics_registry.register(Box::new(g.clone())).unwrap_or(()); + g + }) + .expect("Expected To Create Static Metrics"), + ), + blockchain_netspace: Arc::new( + GenericGauge::new("blockchain_netspace", "Current Netspace") + .map(|g: GenericGauge| { + metrics_registry.register(Box::new(g.clone())).unwrap_or(()); + g + }) + .expect("Expected To Create Static Metrics"), + ), + qualities_latency: Arc::new({ + let opts = HistogramOpts::new( + "qualities_latency", + "Time in seconds to load plot data from Disk", + ) + .buckets(LATENCY_BUCKETS.to_vec()); + Histogram::with_opts(opts) + .map(|h: Histogram| { + metrics_registry.register(Box::new(h.clone())).unwrap_or(()); + h + }) + .expect("Expected To Create Static Metrics") + }), + proof_latency: Arc::new({ + let opts = HistogramOpts::new( + "proof_latency", + "Time in seconds to compute a proof/partial", + ) + .buckets(LATENCY_BUCKETS.to_vec()); + Histogram::with_opts(opts) + .map(|h: Histogram| { + metrics_registry.register(Box::new(h.clone())).unwrap_or(()); + h + }) + .expect("Expected To Create Static Metrics") + }), + signage_point_interval: Arc::new({ + let opts = HistogramOpts::new( + "signage_point_interval", + "Time in seconds in between signage points", + ) + .buckets(SP_INTERVAL_BUCKETS.to_vec()); + Histogram::with_opts(opts) + .map(|h: Histogram| { + metrics_registry.register(Box::new(h.clone())).unwrap_or(()); + h + }) + .expect("Expected To Create Static Metrics") + }), + signage_point_processing_latency: Arc::new({ + let opts = HistogramOpts::new( + "signage_point_processing_latency", + "Time in seconds to process signage points", + ) + .buckets(LATENCY_BUCKETS.to_vec()); + Histogram::with_opts(opts) + .map(|h: Histogram| { + metrics_registry.register(Box::new(h.clone())).unwrap_or(()); + h + }) + .expect("Expected To Create Static Metrics") + }), + plot_load_latency: Arc::new({ + let opts = HistogramOpts::new( + "plot_load_latency", + "Time in seconds to compute a plot file", + ) + .buckets(PLOT_LOAD_LATENCY_BUCKETS.to_vec()); + Histogram::with_opts(opts) + .map(|h: Histogram| { + metrics_registry.register(Box::new(h.clone())).unwrap_or(()); + h + }) + .expect("Expected To Create Static Metrics") + }), + partials_submitted: Arc::new( + GenericCounterVec::new( + Opts::new("partials_submitted", "Total Partials Submitted"), + &["launcher_id"], + ) + .map(|g: GenericCounterVec| { + metrics_registry.register(Box::new(g.clone())).unwrap_or(()); + g + }) + .expect("Expected To Create Static Metrics"), + ), + total_proofs_found: Arc::new( + GenericCounter::new("total_proofs_found", "Total Proofs Found") + .map(|g: GenericCounter| { + metrics_registry.register(Box::new(g.clone())).unwrap_or(()); + g + }) + .expect("Expected To Create Static Metrics"), + ), + last_proofs_found: Arc::new( + GenericGauge::new("last_proofs_found", "Last Value of Proofs Found") + .map(|g: GenericGauge| { + metrics_registry.register(Box::new(g.clone())).unwrap_or(()); + g + }) + .expect("Expected To Create Static Metrics"), + ), + total_partials_found: Arc::new( + GenericCounter::new("total_partials_found", "Total Partials Found") + .map(|g: GenericCounter| { + metrics_registry.register(Box::new(g.clone())).unwrap_or(()); + g + }) + .expect("Expected To Create Static Metrics"), + ), + last_partials_found: Arc::new( + GenericGauge::new("last_partials_found", "Last Value of Partials Found") + .map(|g: GenericGauge| { + metrics_registry.register(Box::new(g.clone())).unwrap_or(()); + g + }) + .expect("Expected To Create Static Metrics"), + ), + total_passed_filter: Arc::new( + GenericCounter::new("total_passed_filter", "Total Plots Passed Filter") + .map(|g: GenericCounter| { + metrics_registry.register(Box::new(g.clone())).unwrap_or(()); + g + }) + .expect("Expected To Create Static Metrics"), + ), + last_passed_filter: Arc::new( + GenericGauge::new("last_passed_filter", "Last Value of Plots Passed Filter") + .map(|g: GenericGauge| { + metrics_registry.register(Box::new(g.clone())).unwrap_or(()); + g + }) + .expect("Expected To Create Static Metrics"), + ), + plot_file_size: Arc::new( + GenericGaugeVec::new( + Opts::new("plot_file_size", "Plots Loaded on Server"), + &["c_level", "k_size", "type"], + ) + .map(|g: GenericGaugeVec| { + metrics_registry.register(Box::new(g.clone())).unwrap_or(()); + g + }) + .expect("Expected To Create Static Metrics"), + ), + plot_counts: Arc::new( + GenericGaugeVec::new( + Opts::new("plot_counts", "Plots Loaded on Server"), + &["c_level", "k_size", "type"], + ) + .map(|g: GenericGaugeVec| { + metrics_registry.register(Box::new(g.clone())).unwrap_or(()); + g + }) + .expect("Expected To Create Static Metrics"), + ), + registry: Arc::new(RwLock::new(metrics_registry)), + } + } +} + +pub fn init(cfg: &mut ServiceConfig) { + cfg.service(metrics); + cfg.service(bin_metrics); +} + +#[get("/metrics")] +pub async fn metrics( + state: Data>>, +) -> Result { + if let Some(farmer_metrics) = state.metrics.read().await.as_ref() { + if let Some(uptime) = &farmer_metrics.uptime { + uptime.set( + Instant::now() + .duration_since(*farmer_metrics.start_time) + .as_secs(), + ) + } + } + let encoder = TextEncoder::new(); + match encoder.encode_to_string(&state.data.extended_metrics.registry.read().await.gather()) { + Ok(enc) => Ok(HttpResponse::Ok().body(enc)), + Err(err) => { + debug!("Error in metrics: {:?}", err); + Ok(HttpResponse::InternalServerError().finish()) + } + } +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct MetricsResp { + pub metrics: Vec>, +} + +#[get("/metrics/bin")] +pub async fn bin_metrics( + state: Data>>, +) -> Result, Error> { + if let Some(farmer_metrics) = state.metrics.read().await.as_ref() { + if let Some(uptime) = &farmer_metrics.uptime { + uptime.set( + Instant::now() + .duration_since(*farmer_metrics.start_time) + .as_secs(), + ) + } + } + Ok(Json(MetricsResp { + metrics: state + .data + .extended_metrics + .registry + .read() + .await + .gather() + .into_iter() + .map(|m| m.write_to_bytes().unwrap_or_default()) + .collect(), + })) +} + +pub fn get_uuid() -> Result { + let uuid_path = get_device_id_path(); + if uuid_path.exists() { + Uuid::parse_str(fs::read_to_string(uuid_path)?.as_str()) + .map_err(|e| std::io::Error::new(ErrorKind::InvalidInput, e)) + } else { + info!("Creating UUID: {:?}", uuid_path); + if let Some(p) = &uuid_path.parent() { + fs::create_dir_all(p)?; + } + let uuid = Uuid::new_v4(); + match fs::write(&uuid_path, uuid.to_string().as_bytes()) { + Ok(_) => Uuid::parse_str(fs::read_to_string(uuid_path)?.as_str()) + .map_err(|e| std::io::Error::new(ErrorKind::InvalidInput, e)), + Err(e) => Err(std::io::Error::new(ErrorKind::InvalidInput, e)), + } + } +} diff --git a/src/tasks/blockchain_state_updater.rs b/src/tasks/blockchain_state_updater.rs new file mode 100644 index 0000000..c1097e6 --- /dev/null +++ b/src/tasks/blockchain_state_updater.rs @@ -0,0 +1,53 @@ +use crate::cli::utils::rpc_client_from_config; +use crate::farmer::config::Config; +use crate::farmer::ExtendedFarmerSharedState; +use crate::gui::FullNodeState; +use dg_xch_clients::api::full_node::FullnodeAPI; +use dg_xch_core::protocols::farmer::FarmerSharedState; +use log::error; +use std::sync::atomic::Ordering; +use std::sync::Arc; +use std::time::{Duration, Instant}; + +pub async fn update_blockchain( + config: Arc, + farmer_state: Arc>, +) { + let full_node_rpc = rpc_client_from_config(config.as_ref(), &None); + let mut last_update = Instant::now(); + loop { + if last_update.elapsed().as_secs() > 5 { + last_update = Instant::now(); + let bc_state = full_node_rpc.get_blockchain_state().await; + match bc_state { + Ok(bc_state) => { + farmer_state + .data + .extended_metrics + .blockchain_height + .set(bc_state.peak.as_ref().map(|p| p.height).unwrap_or_default() as u64); + farmer_state + .data + .extended_metrics + .blockchain_synced + .set(bc_state.sync.synced as u64); + farmer_state + .data + .extended_metrics + .blockchain_netspace + .set((bc_state.space / 1024u128 / 1024u128 / 1024u128) as u64); //Convert to GiB for better fitting into u64 + *farmer_state.data.fullnode_state.write().await = Some(FullNodeState { + blockchain_state: bc_state, + }); + } + Err(e) => { + error!("{:?}", e); + } + } + } + if !farmer_state.data.run.load(Ordering::Relaxed) { + break; + } + tokio::time::sleep(Duration::from_millis(25)).await; + } +} diff --git a/src/tasks/mod.rs b/src/tasks/mod.rs index 4446c3c..52a66cb 100644 --- a/src/tasks/mod.rs +++ b/src/tasks/mod.rs @@ -1 +1,2 @@ +pub mod blockchain_state_updater; pub mod pool_state_updater; diff --git a/src/tasks/pool_state_updater.rs b/src/tasks/pool_state_updater.rs index 8ca614f..6e1b1e9 100644 --- a/src/tasks/pool_state_updater.rs +++ b/src/tasks/pool_state_updater.rs @@ -1,6 +1,6 @@ use crate::farmer::config::Config; use crate::farmer::ExtendedFarmerSharedState; -use crate::HEADERS; +use crate::{HEADERS, PROTOCOL_VERSION}; use blst::min_pk::SecretKey; use dg_xch_clients::api::pool::{DefaultPoolClient, PoolClient}; use dg_xch_core::blockchain::sized_bytes::{Bytes32, Bytes48}; @@ -12,7 +12,7 @@ use dg_xch_core::protocols::pool::{ PoolError, PoolErrorCode, PostFarmerPayload, PostFarmerRequest, PostFarmerResponse, PutFarmerPayload, PutFarmerRequest, PutFarmerResponse, }; -use dg_xch_keys::parse_payout_address; +use dg_xch_keys::{encode_puzzle_hash, parse_payout_address}; use dg_xch_serialize::{hash_256, ChiaSerialize}; use log::{debug, error, info, warn}; use std::collections::hash_map::Entry; @@ -20,7 +20,7 @@ use std::collections::HashMap; use std::sync::atomic::Ordering; use std::sync::Arc; use std::time::{Duration, Instant, SystemTime}; -use tokio::sync::Mutex; +use tokio::sync::RwLock; const UPDATE_POOL_INFO_INTERVAL: u64 = 600; const UPDATE_POOL_INFO_FAILURE_RETRY_INTERVAL: u64 = 120; @@ -39,7 +39,7 @@ pub async fn pool_updater(shared_state: Arc( target_puzzle_hash: pool_config.target_puzzle_hash, authentication_token, } - .to_bytes(); + .to_bytes(PROTOCOL_VERSION); let to_sign = hash_256(&msg); let signature = sign(authentication_sk, &to_sign); if !verify_signature(&authentication_sk.sk_to_pk(), &to_sign, &signature) { @@ -140,7 +140,7 @@ pub async fn post_farmer( })?, suggested_difficulty, }; - let to_sign = hash_256(payload.to_bytes()); + let to_sign = hash_256(payload.to_bytes(PROTOCOL_VERSION)); let signature = sign(owner_sk, &to_sign); if !verify_signature(&owner_sk.sk_to_pk(), &to_sign, &signature) { error!("Farmer POST Failed to Validate Signature"); @@ -178,7 +178,7 @@ pub async fn put_farmer( payout_instructions: parse_payout_address(payout_instructions).ok(), suggested_difficulty, }; - let to_sign = hash_256(payload.to_bytes()); + let to_sign = hash_256(payload.to_bytes(PROTOCOL_VERSION)); let signature = sign(owner_sk, &to_sign); if !verify_signature(&owner_sk.sk_to_pk(), &to_sign, &signature) { error!("Local Failed to Validate Signature"); @@ -197,7 +197,7 @@ pub async fn put_farmer( } pub async fn update_pool_farmer_info( - pool_states: Arc>>, + pool_states: Arc>>, pool_config: &PoolWalletConfig, authentication_token_timeout: u8, authentication_sk: &SecretKey, @@ -211,7 +211,7 @@ pub async fn update_pool_farmer_info( ) .await?; pool_states - .lock() + .write() .await .get_mut(&pool_config.p2_singleton_puzzle_hash) .unwrap_or_else(|| { @@ -222,7 +222,7 @@ pub async fn update_pool_farmer_info( }) .current_difficulty = Some(response.current_difficulty); pool_states - .lock() + .write() .await .get_mut(&pool_config.p2_singleton_puzzle_hash) .unwrap_or_else(|| { @@ -235,7 +235,7 @@ pub async fn update_pool_farmer_info( info!( "Updating Pool Difficulty: {:?} ", pool_states - .lock() + .read() .await .get(&pool_config.p2_singleton_puzzle_hash) .unwrap_or_else(|| panic!( @@ -247,7 +247,7 @@ pub async fn update_pool_farmer_info( info!( "Updating Current Points: {:?} ", pool_states - .lock() + .read() .await .get(&pool_config.p2_singleton_puzzle_hash) .unwrap_or_else(|| panic!( @@ -262,7 +262,7 @@ pub async fn update_pool_farmer_info( pub async fn update_pool_state<'a, T: 'a + PoolClient + Sized + Sync + Send>( auth_keys: &HashMap, owner_keys: &HashMap, - pool_states: Arc>>, + pool_states: Arc>>, client: Arc, config: Arc, ) { @@ -276,7 +276,7 @@ pub async fn update_pool_state<'a, T: 'a + PoolClient + Sized + Sync + Send>( pool_config.p2_singleton_puzzle_hash ); if let Entry::Vacant(s) = pool_states - .lock() + .write() .await .entry(pool_config.p2_singleton_puzzle_hash) { @@ -295,7 +295,7 @@ pub async fn update_pool_state<'a, T: 'a + PoolClient + Sized + Sync + Send>( }); } pool_states - .lock() + .write() .await .get_mut(&pool_config.p2_singleton_puzzle_hash) .unwrap_or_else(|| { @@ -316,9 +316,9 @@ pub async fn update_pool_state<'a, T: 'a + PoolClient + Sized + Sync + Send>( continue; } let next_pool_info_update = pool_states - .lock() + .read() .await - .get_mut(&pool_config.p2_singleton_puzzle_hash) + .get(&pool_config.p2_singleton_puzzle_hash) .unwrap_or_else(|| { panic!( "Item Added to Map Above, Expected {} to exist", @@ -335,7 +335,7 @@ pub async fn update_pool_state<'a, T: 'a + PoolClient + Sized + Sync + Send>( match client.get_pool_info(&pool_config.pool_url).await { Ok(pool_info) => { pool_states - .lock() + .write() .await .get_mut(&pool_config.p2_singleton_puzzle_hash) .unwrap_or_else(|| { @@ -348,9 +348,9 @@ pub async fn update_pool_state<'a, T: 'a + PoolClient + Sized + Sync + Send>( Some(pool_info.authentication_token_timeout); // Only update the first time from GET /pool_info, gets updated from GET /farmer later let is_first = pool_states - .lock() + .read() .await - .get_mut(&pool_config.p2_singleton_puzzle_hash) + .get(&pool_config.p2_singleton_puzzle_hash) .unwrap_or_else(|| { panic!( "Item Added to Map Above, Expected {} to exist", @@ -361,7 +361,7 @@ pub async fn update_pool_state<'a, T: 'a + PoolClient + Sized + Sync + Send>( .is_none(); if is_first { pool_states - .lock() + .write() .await .get_mut(&pool_config.p2_singleton_puzzle_hash) .unwrap_or_else(|| { @@ -373,7 +373,7 @@ pub async fn update_pool_state<'a, T: 'a + PoolClient + Sized + Sync + Send>( .current_difficulty = Some(pool_info.minimum_difficulty); } pool_states - .lock() + .write() .await .get_mut(&pool_config.p2_singleton_puzzle_hash) .unwrap_or_else(|| { @@ -387,7 +387,7 @@ pub async fn update_pool_state<'a, T: 'a + PoolClient + Sized + Sync + Send>( } Err(e) => { pool_states - .lock() + .write() .await .get_mut(&pool_config.p2_singleton_puzzle_hash) .unwrap_or_else(|| { @@ -405,7 +405,7 @@ pub async fn update_pool_state<'a, T: 'a + PoolClient + Sized + Sync + Send>( debug!("Not Ready for Update"); } let next_farmer_update = pool_states - .lock() + .read() .await .get(&pool_config.p2_singleton_puzzle_hash) .unwrap_or_else(|| { @@ -421,7 +421,7 @@ pub async fn update_pool_state<'a, T: 'a + PoolClient + Sized + Sync + Send>( ); if Instant::now() >= next_farmer_update { pool_states - .lock() + .write() .await .get_mut(&pool_config.p2_singleton_puzzle_hash) .unwrap_or_else(|| { @@ -433,7 +433,7 @@ pub async fn update_pool_state<'a, T: 'a + PoolClient + Sized + Sync + Send>( .next_farmer_update = Instant::now() + Duration::from_secs(UPDATE_POOL_FARMER_INFO_INTERVAL); let authentication_token_timeout = pool_states - .lock() + .read() .await .get(&pool_config.p2_singleton_puzzle_hash) .unwrap_or_else(|| { @@ -551,7 +551,7 @@ pub async fn update_pool_state<'a, T: 'a + PoolClient + Sized + Sync + Send>( false }; let current_difficulty = pool_states - .lock() + .read() .await .get(&pool_config.p2_singleton_puzzle_hash) .unwrap_or_else(|| { @@ -563,6 +563,26 @@ pub async fn update_pool_state<'a, T: 'a + PoolClient + Sized + Sync + Send>( .current_difficulty; let difficulty_update_required = pool_config.difficulty.unwrap_or_default() > 0 && current_difficulty < pool_config.difficulty; + debug!( + "Current Pool Payout Address: {}", + encode_puzzle_hash( + &Bytes32::from( + parse_payout_address(&old_instructions).unwrap_or_default() + ), + "xch" + ) + .unwrap_or_default() + ); + debug!( + "Desired Pool Payout Address: {}", + encode_puzzle_hash( + &Bytes32::from( + parse_payout_address(&config.payout_address).unwrap_or_default() + ), + "xch" + ) + .unwrap_or_default() + ); if payout_instructions_update_required || difficulty_update_required { if payout_instructions_update_required { info!( @@ -602,11 +622,11 @@ pub async fn update_pool_state<'a, T: 'a + PoolClient + Sized + Sync + Send>( Ok(res) => { if res.suggested_difficulty.is_some() { pool_states - .lock() + .write() .await .get_mut(&pool_config.p2_singleton_puzzle_hash) .unwrap_or_else(|| panic!("Item Added to Map Above, Expected {} to exist", - &pool_config.p2_singleton_puzzle_hash)) + &pool_config.p2_singleton_puzzle_hash)) .current_difficulty = pool_config.difficulty } info!("Farmer Update Response: {:?}", res);