feat: beta release 0.1.6

This commit is contained in:
Cyrix126 2024-04-02 17:08:14 +02:00
parent 55cf2bc820
commit a4d09c2a3a
23 changed files with 2648 additions and 2091 deletions

View file

@ -1,13 +1,59 @@
# v0.1.6
Fix release for beta version.
This version is only made for testing purposes and have feedbacks.
## Changes
### User interface
Indicator with countdown for algorithm.
Hero mode button active on next decision of algorithm without restart.
Add info if algorithm decision is made with hero mode selected.
Text on hover improvements for token input.
Better displaying info about HR relative to algorithm on console output
Add info if algorithm is waiting for XMRig average HR.
### Internal
Use HTTP client default retry
Bump deps versions
#### XvB process
Immediately start algorithm when possible without delay.
Will retrieve public and private stats just before algorithm rerun, so decision is based on last data.
Algorithm takes longest average HR of XMRig depending on what's available (instead of depending of the number of run of the algorithm).
#### Manage lost connection of XvB nodes
Continue XvB partially if XvB nodes fails instead of stopping.
Make XMRig go back to P2Pool if needed after XvB nodes fail.
Check continuously if XvB nodes came online after fail.
Auto reload XvB process if XvB nodes came online.
#### P2pool
Retrieve current shares as soon as p2pool process is synced.
### Code Organization
Separate XvB process into submodules.
Simplify code for XvB process.
Put tests into own file.
Update test to take into account margin on XvB side.
## Fixes
Winner was not recognized.
Did not take into account scale of sent sidechain P2Pool HR.
Last hour average HR sent kept only one sample.
Multiple instance of algorithm ran in parallel under some conditions.
XMRig config was updated when not needed, even for 0 seconds.
Calculation of time needed to send minimum HR for round type was sending all spared HR less outside XvB HR instead of just minimum HR for round type less oHR.
Calculation of current round type was only looking if value was more than minimum required when it should look if value is more or equal (very few chances to have exactly equal HR but was noticed with the units test).
## Bundled Versions
* [`P2Pool v3.10`](https://github.com/SChernykh/p2pool/releases/tag/v3.10)
* [`XMRig v6.21.1`](https://github.com/xmrig/xmrig/releases/tag/v6.21.1)
# v0.1.5 # v0.1.5
Fix release for beta version. Fix release for beta version.
This version is only made for testing purposes and have feedbacks. This version is only made for testing purposes and have feedbacks.
## Changes ## Changes
update dependencies of UI update dependencies of UI
replace old http client replace old HTTP client
## Fixes ## Fixes
fix formatting hr algorithm fix formatting HR algorithm
fix private round calculation fix private round calculation
## Bundled Versions ## Bundled Versions

194
Cargo.lock generated
View file

@ -280,7 +280,7 @@ checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.55", "syn 2.0.57",
] ]
[[package]] [[package]]
@ -345,7 +345,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "258b52a1aa741b9f09783b2d86cf0aeeb617bbf847f6933340a39644227acbdb" checksum = "258b52a1aa741b9f09783b2d86cf0aeeb617bbf847f6933340a39644227acbdb"
dependencies = [ dependencies = [
"event-listener 5.2.0", "event-listener 5.2.0",
"event-listener-strategy 0.5.0", "event-listener-strategy 0.5.1",
"futures-core", "futures-core",
"pin-project-lite", "pin-project-lite",
] ]
@ -358,16 +358,16 @@ checksum = "f28243a43d821d11341ab73c80bed182dc015c514b951616cf79bd4af39af0c3"
dependencies = [ dependencies = [
"concurrent-queue", "concurrent-queue",
"event-listener 5.2.0", "event-listener 5.2.0",
"event-listener-strategy 0.5.0", "event-listener-strategy 0.5.1",
"futures-core", "futures-core",
"pin-project-lite", "pin-project-lite",
] ]
[[package]] [[package]]
name = "async-executor" name = "async-executor"
version = "1.8.0" version = "1.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17ae5ebefcc48e7452b4987947920dac9450be1110cadf34d1b8c116bdbaf97c" checksum = "10b3e585719c2358d2660232671ca8ca4ddb4be4ce8a1842d6c2dc8685303316"
dependencies = [ dependencies = [
"async-lock 3.3.0", "async-lock 3.3.0",
"async-task", "async-task",
@ -495,19 +495,21 @@ dependencies = [
[[package]] [[package]]
name = "async-process" name = "async-process"
version = "2.1.0" version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "451e3cf68011bd56771c79db04a9e333095ab6349f7e47592b788e9b98720cc8" checksum = "d999d925640d51b662b7b4e404224dd81de70f4aa4a199383c2c5e5b86885fa3"
dependencies = [ dependencies = [
"async-channel", "async-channel",
"async-io 2.3.2", "async-io 2.3.2",
"async-lock 3.3.0", "async-lock 3.3.0",
"async-signal", "async-signal",
"async-task",
"blocking", "blocking",
"cfg-if", "cfg-if",
"event-listener 5.2.0", "event-listener 5.2.0",
"futures-lite 2.3.0", "futures-lite 2.3.0",
"rustix 0.38.32", "rustix 0.38.32",
"tracing",
"windows-sys 0.52.0", "windows-sys 0.52.0",
] ]
@ -519,7 +521,7 @@ checksum = "30c5ef0ede93efbf733c1a727f3b6b5a1060bbedd5600183e66f6e4be4af0ec5"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.55", "syn 2.0.57",
] ]
[[package]] [[package]]
@ -554,7 +556,7 @@ checksum = "a507401cad91ec6a857ed5513a2073c82a9b9048762b885bb98655b306964681"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.55", "syn 2.0.57",
] ]
[[package]] [[package]]
@ -809,6 +811,12 @@ dependencies = [
"tracing", "tracing",
] ]
[[package]]
name = "bounded-vec-deque"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2225b558afc76c596898f5f1b3fc35cfce0eb1b13635cbd7d1b2a7177dc10ccd"
[[package]] [[package]]
name = "built" name = "built"
version = "0.7.1" version = "0.7.1"
@ -838,7 +846,7 @@ checksum = "4da9a32f3fed317401fa3c862968128267c3106685286e15d5aaa3d7389c2f60"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.55", "syn 2.0.57",
] ]
[[package]] [[package]]
@ -1320,9 +1328,9 @@ checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650"
[[package]] [[package]]
name = "ecolor" name = "ecolor"
version = "0.27.0" version = "0.27.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c416b34b29e8f6b2492b5c80319cf9792d339550f0d75a54b53ed070045bbdeb" checksum = "fb152797942f72b84496eb2ebeff0060240e0bf55096c4525ffa22dd54722d86"
dependencies = [ dependencies = [
"bytemuck", "bytemuck",
"serde", "serde",
@ -1330,9 +1338,9 @@ dependencies = [
[[package]] [[package]]
name = "eframe" name = "eframe"
version = "0.27.0" version = "0.27.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86811b20df847cfa73637020ee78de780407110f49785df2dea6741146ea5aac" checksum = "3bcc8e06df6f0a6cf09a3247ff7e85fdfffc28dda4fe5561e05314bf7618a918"
dependencies = [ dependencies = [
"bytemuck", "bytemuck",
"cocoa", "cocoa",
@ -1366,9 +1374,9 @@ dependencies = [
[[package]] [[package]]
name = "egui" name = "egui"
version = "0.27.0" version = "0.27.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8e20541e5d3da08cf817eb5c8a2bc4024f21aad7acf98de201442d6d76e0ecc" checksum = "6d1b8cc14b0b260aa6bd124ef12c8a94f57ffe8e40aa970f3db710c21bb945f3"
dependencies = [ dependencies = [
"accesskit", "accesskit",
"ahash", "ahash",
@ -1381,9 +1389,9 @@ dependencies = [
[[package]] [[package]]
name = "egui-wgpu" name = "egui-wgpu"
version = "0.27.0" version = "0.27.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25d1c9a6f17f48a75148e7a3db7cc0221b7261db577281267301f83d79d0834a" checksum = "04ee072f7cbd9e03ae4028db1c4a8677fbb4efc4b62feee6563763a6f041c88d"
dependencies = [ dependencies = [
"bytemuck", "bytemuck",
"document-features", "document-features",
@ -1399,9 +1407,9 @@ dependencies = [
[[package]] [[package]]
name = "egui-winit" name = "egui-winit"
version = "0.27.0" version = "0.27.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff4fc01bc3617dba9280475e9ab2382015e298f401d2318c2a1d78c419b6dffe" checksum = "3733435d6788c760bb98ce4cb1b8b7a2d953a3a7b421656ba8b3e014019be3d0"
dependencies = [ dependencies = [
"accesskit_winit", "accesskit_winit",
"arboard", "arboard",
@ -1416,9 +1424,9 @@ dependencies = [
[[package]] [[package]]
name = "egui_extras" name = "egui_extras"
version = "0.27.0" version = "0.27.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f097daa92d09cc4561f817e2f9733c55af3b157cf437ee3a401cdf038e75fcdc" checksum = "70edf79855c42e55c357f7f97cd3be9be59cee2585cc39045ce8ff187aa8d4b0"
dependencies = [ dependencies = [
"egui", "egui",
"enum-map", "enum-map",
@ -1430,9 +1438,9 @@ dependencies = [
[[package]] [[package]]
name = "egui_glow" name = "egui_glow"
version = "0.27.0" version = "0.27.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "743380a1c0f1dbb7bfe29663ce62b10785adda51105c9bb4e544e2f9955b4958" checksum = "f933e9e64c4d074c78ce71785a5778f648453c2b2a3efd28eea189dac3f19c28"
dependencies = [ dependencies = [
"bytemuck", "bytemuck",
"egui", "egui",
@ -1452,14 +1460,20 @@ checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a"
[[package]] [[package]]
name = "emath" name = "emath"
version = "0.27.0" version = "0.27.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e87799f56edee11422267e1764dd0bf3fe1734888e8d2d0924a67b85f4998fbe" checksum = "555a7cbfcc52c81eb5f8f898190c840fa1c435f67f30b7ef77ce7cf6b7dcd987"
dependencies = [ dependencies = [
"bytemuck", "bytemuck",
"serde", "serde",
] ]
[[package]]
name = "enclose"
version = "1.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1056f553da426e9c025a662efa48b52e62e0a3a7648aa2d15aeaaf7f0d329357"
[[package]] [[package]]
name = "endi" name = "endi"
version = "1.1.0" version = "1.1.0"
@ -1484,7 +1498,7 @@ checksum = "f282cfdfe92516eb26c2af8589c274c7c17681f5ecc03c18255fe741c6aa64eb"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.55", "syn 2.0.57",
] ]
[[package]] [[package]]
@ -1505,7 +1519,7 @@ checksum = "5c785274071b1b420972453b306eeca06acf4633829db4223b58a2a8c5953bc4"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.55", "syn 2.0.57",
] ]
[[package]] [[package]]
@ -1516,7 +1530,7 @@ checksum = "6fd000fd6988e73bbe993ea3db9b1aa64906ab88766d654973924340c8cddb42"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.55", "syn 2.0.57",
] ]
[[package]] [[package]]
@ -1544,9 +1558,9 @@ dependencies = [
[[package]] [[package]]
name = "epaint" name = "epaint"
version = "0.27.0" version = "0.27.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "392eccd6d6b13fadccc268f4f61d9363775ec5711ffdece2a79006d676920bcf" checksum = "bd63c37156e949bda80f7e39cc11508bc34840aecf52180567e67cdb2bf1a5fe"
dependencies = [ dependencies = [
"ab_glyph", "ab_glyph",
"ahash", "ahash",
@ -1632,9 +1646,9 @@ dependencies = [
[[package]] [[package]]
name = "event-listener-strategy" name = "event-listener-strategy"
version = "0.5.0" version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "feedafcaa9b749175d5ac357452a9d41ea2911da598fde46ce1fe02c37751291" checksum = "332f51cb23d20b0de8458b86580878211da09bcd4503cb579c225b3d124cabb3"
dependencies = [ dependencies = [
"event-listener 5.2.0", "event-listener 5.2.0",
"pin-project-lite", "pin-project-lite",
@ -1768,7 +1782,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.55", "syn 2.0.57",
] ]
[[package]] [[package]]
@ -1849,7 +1863,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.55", "syn 2.0.57",
] ]
[[package]] [[package]]
@ -2070,10 +2084,11 @@ dependencies = [
[[package]] [[package]]
name = "gupaxx" name = "gupaxx"
version = "0.1.5" version = "0.1.6"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"benri", "benri",
"bounded-vec-deque",
"bytes", "bytes",
"chrono", "chrono",
"derive_more", "derive_more",
@ -2081,10 +2096,11 @@ dependencies = [
"eframe", "eframe",
"egui", "egui",
"egui_extras", "egui_extras",
"enclose",
"env_logger", "env_logger",
"figment", "figment",
"flate2", "flate2",
"image 0.25.0", "image 0.25.1",
"is_elevated", "is_elevated",
"log", "log",
"lzma-sys", "lzma-sys",
@ -2113,6 +2129,25 @@ dependencies = [
"zip", "zip",
] ]
[[package]]
name = "h2"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51ee2dd2e4f378392eeff5d51618cd9a63166a2513846bbc55f21cfacd9199d4"
dependencies = [
"bytes",
"fnv",
"futures-core",
"futures-sink",
"futures-util",
"http",
"indexmap",
"slab",
"tokio",
"tokio-util",
"tracing",
]
[[package]] [[package]]
name = "half" name = "half"
version = "2.4.0" version = "2.4.0"
@ -2245,6 +2280,7 @@ dependencies = [
"bytes", "bytes",
"futures-channel", "futures-channel",
"futures-util", "futures-util",
"h2",
"http", "http",
"http-body", "http-body",
"httparse", "httparse",
@ -2350,9 +2386,9 @@ dependencies = [
[[package]] [[package]]
name = "image" name = "image"
version = "0.25.0" version = "0.25.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9b4f005360d32e9325029b38ba47ebd7a56f3316df09249368939562d518645" checksum = "fd54d660e773627692c524beaad361aca785a4f9f5730ce91f42aabe5bce3d11"
dependencies = [ dependencies = [
"bytemuck", "bytemuck",
"byteorder", "byteorder",
@ -2423,7 +2459,7 @@ checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.55", "syn 2.0.57",
] ]
[[package]] [[package]]
@ -2590,9 +2626,9 @@ dependencies = [
[[package]] [[package]]
name = "libredox" name = "libredox"
version = "0.0.1" version = "0.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" checksum = "3af92c55d7d839293953fcd0fda5ecfe93297cfde6ffbdec13b41d99c0ba6607"
dependencies = [ dependencies = [
"bitflags 2.5.0", "bitflags 2.5.0",
"libc", "libc",
@ -2601,13 +2637,12 @@ dependencies = [
[[package]] [[package]]
name = "libredox" name = "libredox"
version = "0.0.2" version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3af92c55d7d839293953fcd0fda5ecfe93297cfde6ffbdec13b41d99c0ba6607" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
dependencies = [ dependencies = [
"bitflags 2.5.0", "bitflags 2.5.0",
"libc", "libc",
"redox_syscall 0.4.1",
] ]
[[package]] [[package]]
@ -2966,7 +3001,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.55", "syn 2.0.57",
] ]
[[package]] [[package]]
@ -3037,7 +3072,7 @@ dependencies = [
"proc-macro-crate 3.1.0", "proc-macro-crate 3.1.0",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.55", "syn 2.0.57",
] ]
[[package]] [[package]]
@ -3165,7 +3200,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.55", "syn 2.0.57",
] ]
[[package]] [[package]]
@ -3301,14 +3336,14 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.55", "syn 2.0.57",
] ]
[[package]] [[package]]
name = "pin-project-lite" name = "pin-project-lite"
version = "0.2.13" version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02"
[[package]] [[package]]
name = "pin-utils" name = "pin-utils"
@ -3466,7 +3501,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8021cf59c8ec9c432cfc2526ac6b8aa508ecaf29cd415f271b8406c1b851c3fd" checksum = "8021cf59c8ec9c432cfc2526ac6b8aa508ecaf29cd415f271b8406c1b851c3fd"
dependencies = [ dependencies = [
"quote", "quote",
"syn 2.0.55", "syn 2.0.57",
] ]
[[package]] [[package]]
@ -3651,12 +3686,12 @@ dependencies = [
[[package]] [[package]]
name = "redox_users" name = "redox_users"
version = "0.4.4" version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891"
dependencies = [ dependencies = [
"getrandom", "getrandom",
"libredox 0.0.1", "libredox 0.1.3",
"thiserror", "thiserror",
] ]
@ -3705,6 +3740,7 @@ dependencies = [
"bytes", "bytes",
"futures-core", "futures-core",
"futures-util", "futures-util",
"h2",
"http", "http",
"http-body", "http-body",
"http-body-util", "http-body-util",
@ -3880,9 +3916,9 @@ dependencies = [
[[package]] [[package]]
name = "security-framework" name = "security-framework"
version = "2.9.2" version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" checksum = "770452e37cad93e0a50d5abc3990d2bc351c36d0328f86cefec2f2fb206eaef6"
dependencies = [ dependencies = [
"bitflags 1.3.2", "bitflags 1.3.2",
"core-foundation", "core-foundation",
@ -3893,9 +3929,9 @@ dependencies = [
[[package]] [[package]]
name = "security-framework-sys" name = "security-framework-sys"
version = "2.9.1" version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" checksum = "41f3cc463c0ef97e11c3461a9d3787412d30e8e7eb907c79180c4a57bf7c04ef"
dependencies = [ dependencies = [
"core-foundation-sys", "core-foundation-sys",
"libc", "libc",
@ -3939,7 +3975,7 @@ checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.55", "syn 2.0.57",
] ]
[[package]] [[package]]
@ -3961,7 +3997,7 @@ checksum = "0b2e6b945e9d3df726b65d6ee24060aff8e3533d431f677a9695db04eff9dfdb"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.55", "syn 2.0.57",
] ]
[[package]] [[package]]
@ -4258,9 +4294,9 @@ dependencies = [
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.55" version = "2.0.57"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "002a1b3dbf967edfafc32655d0f377ab0bb7b994aa1d32c8cc7e9b8bf3ebb8f0" checksum = "11a6ae1e52eb25aab8f3fb9fca13be982a373b8f1157ca14b897a825ba4a2d35"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -4364,7 +4400,7 @@ checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.55", "syn 2.0.57",
] ]
[[package]] [[package]]
@ -4463,7 +4499,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.55", "syn 2.0.57",
] ]
[[package]] [[package]]
@ -4476,6 +4512,20 @@ dependencies = [
"tokio", "tokio",
] ]
[[package]]
name = "tokio-util"
version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15"
dependencies = [
"bytes",
"futures-core",
"futures-sink",
"pin-project-lite",
"tokio",
"tracing",
]
[[package]] [[package]]
name = "toml" name = "toml"
version = "0.5.11" version = "0.5.11"
@ -4590,7 +4640,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.55", "syn 2.0.57",
] ]
[[package]] [[package]]
@ -4828,7 +4878,7 @@ dependencies = [
"once_cell", "once_cell",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.55", "syn 2.0.57",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
@ -4862,7 +4912,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.55", "syn 2.0.57",
"wasm-bindgen-backend", "wasm-bindgen-backend",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
@ -5620,9 +5670,9 @@ checksum = "054a8e68b76250b253f671d1268cb7f1ae089ec35e195b2efb2a4e9a836d0621"
[[package]] [[package]]
name = "xml-rs" name = "xml-rs"
version = "0.8.19" version = "0.8.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fcb9cbac069e033553e8bb871be2fbdffcab578eb25bd0f7c508cedc6dcd75a" checksum = "791978798f0597cfc70478424c2b4fdc2b7a8024aaff78497ef00f24ef674193"
[[package]] [[package]]
name = "zbus" name = "zbus"
@ -5676,7 +5726,7 @@ dependencies = [
"async-fs 2.1.1", "async-fs 2.1.1",
"async-io 2.3.2", "async-io 2.3.2",
"async-lock 3.3.0", "async-lock 3.3.0",
"async-process 2.1.0", "async-process 2.2.0",
"async-recursion", "async-recursion",
"async-task", "async-task",
"async-trait", "async-trait",
@ -5771,7 +5821,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.55", "syn 2.0.57",
] ]
[[package]] [[package]]

View file

@ -1,7 +1,7 @@
cargo-features = ["profile-rustflags"] cargo-features = ["profile-rustflags"]
[package] [package]
name = "gupaxx" name = "gupaxx"
version = "0.1.5" version = "0.1.6"
authors = ["cyrix126 <gupaxx@baermail.fr>"] authors = ["cyrix126 <gupaxx@baermail.fr>"]
description = "Fork of Gupax integrating the XMRvsBeast Raffle " description = "Fork of Gupax integrating the XMRvsBeast Raffle "
documentation = "https://github.com/cyrix126/gupaxx" documentation = "https://github.com/cyrix126/gupaxx"
@ -33,8 +33,8 @@ benri = "0.1.12"
bytes = "1.6.0" bytes = "1.6.0"
dirs = "5.0.1" dirs = "5.0.1"
#-------------------------------------------------------------------------------- #--------------------------------------------------------------------------------
egui = "0.27.0" egui = "0.27.1"
egui_extras = { version = "0.27.0", features = ["image"] } egui_extras = { version = "0.27.1", features = ["image"] }
## 2023-12-28: https://github.com/hinto-janai/gupax/issues/68 ## 2023-12-28: https://github.com/hinto-janai/gupax/issues/68
## ##
## 2024-03-18: Both `glow` and `wgpu` seem to crash: ## 2024-03-18: Both `glow` and `wgpu` seem to crash:
@ -57,8 +57,8 @@ egui_extras = { version = "0.27.0", features = ["image"] }
#-------------------------------------------------------------------------------- #--------------------------------------------------------------------------------
env_logger = "0.11.3" env_logger = "0.11.3"
figment = { version = "0.10.15", features = ["toml"] } figment = { version = "0.10.15", features = ["toml"] }
reqwest = {version = "0.12.2", default-features=false, features=["json", "default-tls"]} reqwest = {version = "0.12.2", default-features=false, features=["json", "default-tls", "http2"]}
image = { version = "0.25.0", features = ["png"] } image = { version = "0.25.1", features = ["png"] }
log = "0.4.21" log = "0.4.21"
num-format = { version = "0.4.4", default-features = false } num-format = { version = "0.4.4", default-features = false }
once_cell = "1.19.0" once_cell = "1.19.0"
@ -80,15 +80,17 @@ derive_more = {version="0.99.17", default-features=false, features=["display"]}
serde-this-or-that = "0.4.2" serde-this-or-that = "0.4.2"
readable = "0.16" readable = "0.16"
chrono = {version="0.4.37", default-features=false, features=["clock", "std"]} chrono = {version="0.4.37", default-features=false, features=["clock", "std"]}
enclose = "1.1.8"
bounded-vec-deque = {version="0.1.1", default-features=false}
# Unix dependencies # Unix dependencies
[target.'cfg(unix)'.dependencies] [target.'cfg(unix)'.dependencies]
eframe = { version = "0.27.0", features = ["wgpu"] } eframe = { version = "0.27.1", features = ["wgpu"] }
tar = "0.4.40" tar = "0.4.40"
flate2 = "1.0" flate2 = "1.0"
sudo = "0.6.0" sudo = "0.6.0"
# macOS # macOS
[target.'cfg(target_os = "macos")'.dependencies] [target.'cfg(target_os = "macos")'.dependencies]
eframe = { version = "0.27.0", features = ["wgpu"] } eframe = { version = "0.27.1", features = ["wgpu"] }
# On apple-darwin targets there is an issue with the native and rustls # On apple-darwin targets there is an issue with the native and rustls
# tls implementation so this makes it fall back to the openssl variant. # tls implementation so this makes it fall back to the openssl variant.
# #
@ -102,7 +104,7 @@ eframe = { version = "0.27.0", features = ["wgpu"] }
# linked as well which causes problems, so statically link it. # linked as well which causes problems, so statically link it.
lzma-sys = { version = "0.1.20", features = ["static"] } lzma-sys = { version = "0.1.20", features = ["static"] }
[dev-dependencies] [dev-dependencies]
egui = {version = "0.27.0", features=["callstack"]} egui = {version = "0.27.1", features=["callstack"]}
# [target.'cfg(not(target_os = "macos"))'.dependencies] # [target.'cfg(not(target_os = "macos"))'.dependencies]
# tls-api-native-tls = "0.9.0" # tls-api-native-tls = "0.9.0"
@ -110,7 +112,7 @@ egui = {version = "0.27.0", features=["callstack"]}
# Windows dependencies # Windows dependencies
[target.'cfg(windows)'.dependencies] [target.'cfg(windows)'.dependencies]
# glow start on windows but not wgpu # glow start on windows but not wgpu
eframe = { version = "0.27.0", features = ["glow"] } eframe = { version = "0.27.1", features = ["glow"] }
zip = "0.6.6" zip = "0.6.6"
is_elevated = "0.1.2" is_elevated = "0.1.2"

View file

@ -14,6 +14,7 @@
- [x] link and message hovering explaining registration and needs to read the rules. - [x] link and message hovering explaining registration and needs to read the rules.
- [x] token input - [x] token input
- [x] hero checkbox - [x] hero checkbox
- [x] without restart of xvb (next decision of algo)
- [x] log section - [x] log section
- [x] state of XvB process - [x] state of XvB process
- [x] selected XvB node - [x] selected XvB node
@ -23,6 +24,7 @@
- [x] from XvB API (fails, average 1h and 24h) - [x] from XvB API (fails, average 1h and 24h)
- [x] round type in - [x] round type in
- [x] win or loose - [x] win or loose
- [x] indicator of mining round and time left.
- [x] new process for XvB - [x] new process for XvB
- [x] update preferred XvB node based on ping and backup - [x] update preferred XvB node based on ping and backup
- [x] fix: xmrig will not do anything if node is not responding. Need to parse output of xmrig for error and update nodes. - [x] fix: xmrig will not do anything if node is not responding. Need to parse output of xmrig for error and update nodes.
@ -33,6 +35,7 @@
- [x] button to autostart - [x] button to autostart
- [x] distribute hashrate conforming to the algorithm. - [x] distribute hashrate conforming to the algorithm.
- [x] check every 10 minutes average Xmrig HR of last 15 minutes - [x] check every 10 minutes average Xmrig HR of last 15 minutes
- [x] fast startup (check 10s first decision then 1m second decision)
- [x] take into account outside HR - [x] take into account outside HR
- [x] mining on p2pool - [x] mining on p2pool
- [x] mining on XvB - [x] mining on XvB
@ -64,11 +67,10 @@
- [ ] use tor socks proxy instead of creating one - [ ] use tor socks proxy instead of creating one
- [x] remove arti - [x] remove arti
- [ ] bundle arti cmd binary - [ ] bundle arti cmd binary
- [ ] upgrade to hyper stable - [x] replace hyper with reqwest
- [ ] use hyper with socks proxy - [x] better organize new code
- [ ] better organize new code
- [x] merge commits from upstream - [x] merge commits from upstream
- [ ] tests for new function - [x] tests for new function
- [x] time calculated by algorithm - [x] time calculated by algorithm
- [x] pre-release - [x] pre-release
- [ ] feedback - [ ] feedback

View file

@ -523,6 +523,9 @@ impl App {
info!("App Init | Setting saved [Tab]..."); info!("App Init | Setting saved [Tab]...");
app.tab = app.state.gupax.tab; app.tab = app.state.gupax.tab;
// Set saved Hero mode to runtime.
app.xvb_api.lock().unwrap().stats_priv.runtime_hero_mode = app.state.xvb.hero;
// Check if [P2pool.node] exists // Check if [P2pool.node] exists
info!("App Init | Checking if saved remote node still exists..."); info!("App Init | Checking if saved remote node still exists...");
app.state.p2pool.node = RemoteNode::check_exists(&app.state.p2pool.node); app.state.p2pool.node = RemoteNode::check_exists(&app.state.p2pool.node);

View file

@ -1,5 +1,6 @@
use crate::app::keys::KeyPressed; use crate::app::keys::KeyPressed;
use crate::app::Tab; use crate::app::Tab;
use crate::helper::ProcessState;
use crate::utils::constants::*; use crate::utils::constants::*;
use crate::utils::errors::{ErrorButtons, ErrorFerris}; use crate::utils::errors::{ErrorButtons, ErrorFerris};
use crate::utils::macros::lock; use crate::utils::macros::lock;
@ -161,7 +162,7 @@ path_xmr: {:#?}\n
} }
Tab::Xvb => { Tab::Xvb => {
debug!("App | Entering [XvB] Tab"); debug!("App | Entering [XvB] Tab");
crate::disk::state::Xvb::show(&mut self.state.xvb, self.size, &self.state.p2pool.address, ctx, ui, &self.xvb_api, lock!(self.xvb).is_alive()&& !lock!(self.xvb).is_syncing() && !lock!(self.xvb).is_not_mining()); crate::disk::state::Xvb::show(&mut self.state.xvb, self.size, &self.state.p2pool.address, ctx, ui, &self.xvb_api, lock!(self.xvb).state == ProcessState::Alive);
} }
} }
}); });

View file

@ -5,7 +5,7 @@ use std::sync::{Arc, Mutex};
use crate::disk::state::Status; use crate::disk::state::Status;
use crate::helper::p2pool::{ImgP2pool, PubP2poolApi}; use crate::helper::p2pool::{ImgP2pool, PubP2poolApi};
use crate::helper::xmrig::{ImgXmrig, PubXmrigApi}; use crate::helper::xmrig::{ImgXmrig, PubXmrigApi};
use crate::helper::xvb::{PubXvbApi, XvbRound}; use crate::helper::xvb::{rounds::XvbRound, PubXvbApi};
use crate::helper::Sys; use crate::helper::Sys;
use crate::utils::macros::lock; use crate::utils::macros::lock;
use egui::TextStyle; use egui::TextStyle;

View file

@ -4,6 +4,7 @@ use egui::TextStyle::{self, Name};
use egui::{vec2, Image, RichText, TextEdit, Ui, Vec2}; use egui::{vec2, Image, RichText, TextEdit, Ui, Vec2};
use log::debug; use log::debug;
use readable::num::Float; use readable::num::Float;
use readable::up::Uptime;
use crate::helper::xvb::PubXvbApi; use crate::helper::xvb::PubXvbApi;
use crate::regex::num_lines; use crate::regex::num_lines;
@ -14,6 +15,7 @@ use crate::utils::constants::{
}; };
use crate::utils::macros::lock; use crate::utils::macros::lock;
use crate::utils::regex::Regexes; use crate::utils::regex::Regexes;
use crate::XVB_MINING_ON_FIELD;
use crate::{ use crate::{
constants::{BYTES_XVB, SPACE}, constants::{BYTES_XVB, SPACE},
utils::constants::{DARK_GRAY, XVB_URL}, utils::constants::{DARK_GRAY, XVB_URL},
@ -96,22 +98,28 @@ impl crate::disk::state::Xvb {
}; };
ui.add_space(space_h); ui.add_space(space_h);
ui.horizontal(|ui| { ui.horizontal(|ui| {
// hovering text is difficult because egui doesn't hover over inner widget. But on disabled does.
ui.group(|ui| { ui.group(|ui| {
ui.colored_label(color, text); ui.colored_label(color, text)
.on_hover_text(XVB_HELP);
ui.add( ui.add(
TextEdit::singleline(&mut self.token) TextEdit::singleline(&mut self.token)
.char_limit(9) .char_limit(9)
.desired_width(ui.text_style_height(&TextStyle::Body) * 9.0) .desired_width(ui.text_style_height(&TextStyle::Body) * 9.0)
.vertical_align(egui::Align::Center), .vertical_align(egui::Align::Center),
) ).on_hover_text(XVB_HELP)
}) })
.response .response
.on_hover_text_at_pointer(XVB_HELP); .on_hover_text(XVB_HELP);
ui.add_space(height / 48.0); ui.add_space(height / 48.0);
ui.style_mut().spacing.icon_width_inner = width / 45.0; ui.style_mut().spacing.icon_width_inner = width / 45.0;
ui.style_mut().spacing.icon_width = width / 35.0; ui.style_mut().spacing.icon_width = width / 35.0;
ui.style_mut().spacing.icon_spacing = space_h; ui.style_mut().spacing.icon_spacing = space_h;
ui.checkbox(&mut self.hero, "Hero Mode").on_hover_text(XVB_HERO_SELECT); if ui.checkbox(&mut self.hero, "Hero Mode").on_hover_text(XVB_HERO_SELECT).clicked() {
// also change hero mode of runtime.
lock!(api).stats_priv.runtime_hero_mode = self.hero;
}
// need to warn the user if no address is set in p2pool tab // need to warn the user if no address is set in p2pool tab
if !Regexes::addr_ok(address) { if !Regexes::addr_ok(address) {
ui.add_space(width / 16.0); ui.add_space(width / 16.0);
@ -125,12 +133,15 @@ impl crate::disk::state::Xvb {
}); });
// private stats // private stats
ui.add_space(space_h); ui.add_space(space_h);
// ui.add_enabled_ui(private_stats, |ui| {
ui.add_enabled_ui(private_stats, |ui| { ui.add_enabled_ui(private_stats, |ui| {
let api = &lock!(api);
let priv_stats = &api.stats_priv;
let current_node = &api.current_node;
let width_stat = (ui.available_width() - SPACE * 4.0) / 5.0;
let height_stat = 0.0;
let size_stat = vec2(width_stat, height_stat);
ui.horizontal(|ui| { ui.horizontal(|ui| {
let priv_stats = &lock!(api).stats_priv;
let width_stat = (ui.available_width() - SPACE * 4.0) / 5.0;
let height_stat = 0.0;
let size_stat = vec2(width_stat, height_stat);
let round = match &priv_stats.round_participate { let round = match &priv_stats.round_participate {
Some(r) => r.to_string(), Some(r) => r.to_string(),
None => "None".to_string(), None => "None".to_string(),
@ -205,6 +216,35 @@ impl crate::disk::state::Xvb {
.response .response
}); });
}); });
// indicators
ui.horizontal(|ui| {
ui.add_sized(size_stat, |ui: &mut Ui| {
ui.group(|ui| {
let size_stat = vec2(
ui.available_width(),
0.0, // + ui.spacing().item_spacing.y,
);
ui.add_sized(size_stat, |ui: &mut Ui| {
ui.vertical_centered(|ui| {
ui.label(XVB_MINING_ON_FIELD)
.on_hover_text_at_pointer(&priv_stats.msg_indicator);
ui.label(
current_node
.as_ref()
.map_or("No where".to_string(), |n| n.to_string()),
)
.on_hover_text_at_pointer(&priv_stats.msg_indicator);
ui.label(Uptime::from(priv_stats.time_switch_node).to_string())
.on_hover_text_at_pointer(&priv_stats.msg_indicator)
})
.response
})
})
.response
.on_disabled_hover_text("Algorithm is not running.")
})
// currently mining on
});
}); });
// Rules link help // Rules link help
ui.horizontal_centered(|ui| { ui.horizontal_centered(|ui| {

View file

@ -974,7 +974,7 @@ impl Pkg {
link: String, link: String,
user_agent: &'static str, user_agent: &'static str,
) -> Result<(), Error> { ) -> Result<(), Error> {
let request = Pkg::get_request(&client, link, user_agent)?; let request = Pkg::get_request(client, link, user_agent)?;
let response = request.send().await?; let response = request.send().await?;
dbg!(&response); dbg!(&response);
let body = response.json::<TagName>().await?; let body = response.json::<TagName>().await?;
@ -992,14 +992,14 @@ impl Pkg {
link: String, link: String,
user_agent: &'static str, user_agent: &'static str,
) -> Result<(), anyhow::Error> { ) -> Result<(), anyhow::Error> {
let request = Self::get_request(&client, link, user_agent)?; let request = Self::get_request(client, link, user_agent)?;
let mut response = request.send().await?; let mut response = request.send().await?;
// GitHub sends a 302 redirect, so we must follow // GitHub sends a 302 redirect, so we must follow
// the [Location] header... only if Reqwest had custom // the [Location] header... only if Reqwest had custom
// connectors so I didn't have to manually do this... // connectors so I didn't have to manually do this...
if response.headers().contains_key(LOCATION) { if response.headers().contains_key(LOCATION) {
response = Self::get_request( response = Self::get_request(
&client, client,
response response
.headers() .headers()
.get(LOCATION) .get(LOCATION)

View file

@ -50,6 +50,7 @@ use std::{
use self::xvb::PubXvbApi; use self::xvb::PubXvbApi;
pub mod p2pool; pub mod p2pool;
pub mod tests;
pub mod xmrig; pub mod xmrig;
pub mod xvb; pub mod xvb;
@ -189,16 +190,6 @@ impl Process {
pub fn is_waiting(&self) -> bool { pub fn is_waiting(&self) -> bool {
self.state == ProcessState::Middle || self.state == ProcessState::Waiting self.state == ProcessState::Middle || self.state == ProcessState::Waiting
} }
#[inline]
pub fn is_syncing(&self) -> bool {
self.state == ProcessState::Syncing
}
#[inline]
pub fn is_not_mining(&self) -> bool {
self.state == ProcessState::NotMining
}
} }
//---------------------------------------------------------------------------------------------------- [Process*] Enum //---------------------------------------------------------------------------------------------------- [Process*] Enum
@ -541,506 +532,3 @@ impl Helper {
}); });
} }
} }
//---------------------------------------------------------------------------------------------------- TESTS
#[cfg(test)]
mod test {
use crate::helper::p2pool::{PrivP2poolLocalApi, PrivP2poolNetworkApi};
use super::*;
#[test]
fn get_current_shares() {
let mut stdout = "
statusfromgupaxx
2024-03-25 21:31:21.7919 SideChain status
Monero node = node2.monerodevs.org:18089:ZMQ:18084 (37.187.74.171)
Main chain height = 3113042
Main chain hashrate = 1.985 GH/s
Side chain ID = mini
Side chain height = 7230432
Side chain hashrate = 8.925 MH/s
PPLNS window = 2160 blocks (+79 uncles, 0 orphans)
PPLNS window duration = 6h 9m 46s
Your wallet address = 4A5Dwt2qKwKEQrZfo4aBkSNtvDDAzSFbAJcyFkdW5RwDh9U4WgeZrgKT4hUoE2gv8h6NmsNMTyjsEL8eSLMbABds5rYFWnw
Your shares = 0 blocks (+0 uncles, 0 orphans)
Block reward share = 0.000% (0.000000000000 XMR)
2024-03-25 21:31:21.7920 StratumServer status
Hashrate (15m est) = 0 H/s
Hashrate (1h est) = 0 H/s
Hashrate (24h est) = 0 H/s
Total hashes = 0
Shares found = 0
Average effort = 0.000%
Current effort = 0.000%
Connections = 0 (0 incoming)
2024-03-25 21:31:21.7920 P2PServer status
Connections = 10 (0 incoming)
Peer list size = 1209
Uptime = 0h 2m 4s
".lines();
let mut shares = 1;
let mut status_output = false;
while let Some(line) = stdout.next() {
// if command status is sent by gupaxx process and not the user, forward it only to update_from_status method.
// 25 lines after the command are the result of status, with last line finishing by update.
if line.contains("statusfromgupaxx") {
status_output = true;
continue;
}
if status_output {
if line.contains("Your shares") {
// update sidechain shares
shares = line.split_once("=").expect("should be = at Your Share, maybe new version of p2pool has different output for status command ?").1.split_once("blocks").expect("should be a 'blocks' at Your Share, maybe new version of p2pool has different output for status command ?").0.trim().parse::<u32>().expect("this should be the number of share");
}
if line.contains("Uptime") {
// end of status
status_output = false;
}
continue;
}
}
assert_eq!(shares, 0);
}
#[test]
fn reset_gui_output() {
let max = crate::helper::GUI_OUTPUT_LEEWAY;
let mut string = String::with_capacity(max);
for _ in 0..=max {
string.push('0');
}
Helper::check_reset_gui_output(&mut string, ProcessName::P2pool);
// Some text gets added, so just check for less than 500 bytes.
assert!(string.len() < 500);
}
#[test]
fn combine_gui_pub_p2pool_api() {
use crate::helper::PubP2poolApi;
let mut gui_api = PubP2poolApi::new();
let mut pub_api = PubP2poolApi::new();
pub_api.payouts = 1;
pub_api.payouts_hour = 2.0;
pub_api.payouts_day = 3.0;
pub_api.payouts_month = 4.0;
pub_api.xmr = 1.0;
pub_api.xmr_hour = 2.0;
pub_api.xmr_day = 3.0;
pub_api.xmr_month = 4.0;
println!("BEFORE - GUI_API: {:#?}\nPUB_API: {:#?}", gui_api, pub_api);
assert_ne!(gui_api, pub_api);
PubP2poolApi::combine_gui_pub_api(&mut gui_api, &mut pub_api);
println!("AFTER - GUI_API: {:#?}\nPUB_API: {:#?}", gui_api, pub_api);
assert_eq!(gui_api, pub_api);
pub_api.xmr = 2.0;
PubP2poolApi::combine_gui_pub_api(&mut gui_api, &mut pub_api);
assert_eq!(gui_api, pub_api);
assert_eq!(gui_api.xmr, 2.0);
assert_eq!(pub_api.xmr, 2.0);
}
#[test]
fn calc_payouts_and_xmr_from_output_p2pool() {
use crate::helper::PubP2poolApi;
use std::sync::{Arc, Mutex};
let public = Arc::new(Mutex::new(PubP2poolApi::new()));
let output_parse = Arc::new(Mutex::new(String::from(
r#"payout of 5.000000000001 XMR in block 1111
payout of 5.000000000001 XMR in block 1112
payout of 5.000000000001 XMR in block 1113"#,
)));
let output_pub = Arc::new(Mutex::new(String::new()));
let elapsed = std::time::Duration::from_secs(60);
let process = Arc::new(Mutex::new(Process::new(
ProcessName::P2pool,
"".to_string(),
PathBuf::new(),
)));
PubP2poolApi::update_from_output(&public, &output_parse, &output_pub, elapsed, &process);
let public = public.lock().unwrap();
println!("{:#?}", public);
assert_eq!(public.payouts, 3);
assert_eq!(public.payouts_hour, 180.0);
assert_eq!(public.payouts_day, 4320.0);
assert_eq!(public.payouts_month, 129600.0);
assert_eq!(public.xmr, 15.000000000003);
assert_eq!(public.xmr_hour, 900.00000000018);
assert_eq!(public.xmr_day, 21600.00000000432);
assert_eq!(public.xmr_month, 648000.0000001296);
}
#[test]
fn set_p2pool_synchronized() {
use crate::helper::PubP2poolApi;
use std::sync::{Arc, Mutex};
let public = Arc::new(Mutex::new(PubP2poolApi::new()));
let output_parse = Arc::new(Mutex::new(String::from(
r#"payout of 5.000000000001 XMR in block 1111
NOTICE 2021-12-27 21:42:17.2008 SideChain SYNCHRONIZED
payout of 5.000000000001 XMR in block 1113"#,
)));
let output_pub = Arc::new(Mutex::new(String::new()));
let elapsed = std::time::Duration::from_secs(60);
let process = Arc::new(Mutex::new(Process::new(
ProcessName::P2pool,
"".to_string(),
PathBuf::new(),
)));
// It only gets checked if we're `Syncing`.
process.lock().unwrap().state = ProcessState::Syncing;
PubP2poolApi::update_from_output(&public, &output_parse, &output_pub, elapsed, &process);
println!("{:#?}", process);
assert!(process.lock().unwrap().state == ProcessState::Alive);
}
#[test]
fn p2pool_synchronized_false_positive() {
use crate::helper::PubP2poolApi;
use std::sync::{Arc, Mutex};
let public = Arc::new(Mutex::new(PubP2poolApi::new()));
// The SideChain that is "SYNCHRONIZED" in this output is
// probably not main/mini, but the sidechain started on height 1,
// so this should _not_ trigger alive state.
let output_parse = Arc::new(Mutex::new(String::from(
r#"payout of 5.000000000001 XMR in block 1111
SideChain new chain tip: next height = 1
NOTICE 2021-12-27 21:42:17.2008 SideChain SYNCHRONIZED
payout of 5.000000000001 XMR in block 1113"#,
)));
let output_pub = Arc::new(Mutex::new(String::new()));
let elapsed = std::time::Duration::from_secs(60);
let process = Arc::new(Mutex::new(Process::new(
ProcessName::P2pool,
"".to_string(),
PathBuf::new(),
)));
// It only gets checked if we're `Syncing`.
process.lock().unwrap().state = ProcessState::Syncing;
PubP2poolApi::update_from_output(&public, &output_parse, &output_pub, elapsed, &process);
println!("{:#?}", process);
assert!(process.lock().unwrap().state == ProcessState::Syncing); // still syncing
}
#[test]
fn p2pool_synchronized_double_synchronized() {
use crate::helper::PubP2poolApi;
use std::sync::{Arc, Mutex};
let public = Arc::new(Mutex::new(PubP2poolApi::new()));
// The 1st SideChain that is "SYNCHRONIZED" in this output is
// the sidechain started on height 1, but there is another one
// which means the real main/mini is probably synced,
// so this _should_ trigger alive state.
let output_parse = Arc::new(Mutex::new(String::from(
r#"payout of 5.000000000001 XMR in block 1111
SideChain new chain tip: next height = 1
NOTICE 2021-12-27 21:42:17.2008 SideChain SYNCHRONIZED
payout of 5.000000000001 XMR in block 1113
NOTICE 2021-12-27 21:42:17.2100 SideChain SYNCHRONIZED"#,
)));
let output_pub = Arc::new(Mutex::new(String::new()));
let elapsed = std::time::Duration::from_secs(60);
let process = Arc::new(Mutex::new(Process::new(
ProcessName::P2pool,
"".to_string(),
PathBuf::new(),
)));
// It only gets checked if we're `Syncing`.
process.lock().unwrap().state = ProcessState::Syncing;
PubP2poolApi::update_from_output(&public, &output_parse, &output_pub, elapsed, &process);
println!("{:#?}", process);
assert!(process.lock().unwrap().state == ProcessState::Alive);
}
#[test]
fn update_pub_p2pool_from_local_network_pool() {
use crate::helper::p2pool::PoolStatistics;
use crate::helper::p2pool::PrivP2poolLocalApi;
use crate::helper::p2pool::PrivP2poolNetworkApi;
use crate::helper::p2pool::PrivP2poolPoolApi;
use crate::helper::PubP2poolApi;
use std::sync::{Arc, Mutex};
let public = Arc::new(Mutex::new(PubP2poolApi::new()));
let local = PrivP2poolLocalApi {
hashrate_15m: 10_000,
hashrate_1h: 20_000,
hashrate_24h: 30_000,
shares_found: 1000,
average_effort: 100.000,
current_effort: 200.000,
connections: 1234,
};
let network = PrivP2poolNetworkApi {
difficulty: 300_000_000_000,
hash: "asdf".to_string(),
height: 1234,
reward: 2345,
timestamp: 3456,
};
let pool = PrivP2poolPoolApi {
pool_statistics: PoolStatistics {
hashRate: 1_000_000, // 1 MH/s
miners: 1_000,
},
};
// Update Local
PubP2poolApi::update_from_local(&public, local);
let p = public.lock().unwrap();
println!("AFTER LOCAL: {:#?}", p);
assert_eq!(p.hashrate_15m.to_string(), "10,000");
assert_eq!(p.hashrate_1h.to_string(), "20,000");
assert_eq!(p.hashrate_24h.to_string(), "30,000");
assert_eq!(
p.shares_found.expect("the value is set").to_string(),
"1000"
);
assert_eq!(p.average_effort.to_string(), "100.00%");
assert_eq!(p.current_effort.to_string(), "200.00%");
assert_eq!(p.connections.to_string(), "1,234");
assert_eq!(p.user_p2pool_hashrate_u64, 20000);
drop(p);
// Update Network + Pool
PubP2poolApi::update_from_network_pool(&public, network, pool);
let p = public.lock().unwrap();
println!("AFTER NETWORK+POOL: {:#?}", p);
assert_eq!(p.monero_difficulty.to_string(), "300,000,000,000");
assert_eq!(p.monero_hashrate.to_string(), "2.500 GH/s");
assert_eq!(p.hash.to_string(), "asdf");
assert_eq!(p.height.to_string(), "1,234");
assert_eq!(p.reward.to_u64(), 2345);
assert_eq!(p.p2pool_difficulty.to_string(), "10,000,000");
assert_eq!(p.p2pool_hashrate.to_string(), "1.000 MH/s");
assert_eq!(p.miners.to_string(), "1,000");
assert_eq!(
p.solo_block_mean.to_string(),
"5 months, 21 days, 9 hours, 52 minutes"
);
assert_eq!(
p.p2pool_block_mean.to_string(),
"3 days, 11 hours, 20 minutes"
);
assert_eq!(p.p2pool_share_mean.to_string(), "8 minutes, 20 seconds");
assert_eq!(p.p2pool_percent.to_string(), "0.040000%");
assert_eq!(p.user_p2pool_percent.to_string(), "2.000000%");
assert_eq!(p.user_monero_percent.to_string(), "0.000800%");
drop(p);
}
#[test]
fn set_xmrig_mining() {
use crate::helper::PubXmrigApi;
use std::sync::{Arc, Mutex};
let public = Arc::new(Mutex::new(PubXmrigApi::new()));
let output_parse = Arc::new(Mutex::new(String::from(
"[2022-02-12 12:49:30.311] net no active pools, stop mining",
)));
let output_pub = Arc::new(Mutex::new(String::new()));
let elapsed = std::time::Duration::from_secs(60);
let process = Arc::new(Mutex::new(Process::new(
ProcessName::Xmrig,
"".to_string(),
PathBuf::new(),
)));
process.lock().unwrap().state = ProcessState::Alive;
PubXmrigApi::update_from_output(&public, &output_parse, &output_pub, elapsed, &process);
println!("{:#?}", process);
assert!(process.lock().unwrap().state == ProcessState::NotMining);
let output_parse = Arc::new(Mutex::new(String::from("[2022-02-12 12:49:30.311] net new job from 192.168.2.1:3333 diff 402K algo rx/0 height 2241142 (11 tx)")));
PubXmrigApi::update_from_output(&public, &output_parse, &output_pub, elapsed, &process);
assert!(process.lock().unwrap().state == ProcessState::Alive);
}
#[test]
fn serde_priv_p2pool_local_api() {
let data = r#"{
"hashrate_15m": 12,
"hashrate_1h": 11111,
"hashrate_24h": 468967,
"total_hashes": 2019283840922394082390,
"shares_found": 289037,
"average_effort": 915.563,
"current_effort": 129.297,
"connections": 123,
"incoming_connections": 96
}"#;
let priv_api = PrivP2poolLocalApi::from_str(data).unwrap();
let json = serde_json::ser::to_string_pretty(&priv_api).unwrap();
println!("{}", json);
let data_after_ser = r#"{
"hashrate_15m": 12,
"hashrate_1h": 11111,
"hashrate_24h": 468967,
"shares_found": 289037,
"average_effort": 915.563,
"current_effort": 129.297,
"connections": 123
}"#;
assert_eq!(data_after_ser, json)
}
#[test]
fn serde_priv_p2pool_network_api() {
let data = r#"{
"difficulty": 319028180924,
"hash": "22ae1b83d727bb2ff4efc17b485bc47bc8bf5e29a7b3af65baf42213ac70a39b",
"height": 2776576,
"reward": 600499860000,
"timestamp": 1670953659
}"#;
let priv_api = PrivP2poolNetworkApi::from_str(data).unwrap();
let json = serde_json::ser::to_string_pretty(&priv_api).unwrap();
println!("{}", json);
let data_after_ser = r#"{
"difficulty": 319028180924,
"hash": "22ae1b83d727bb2ff4efc17b485bc47bc8bf5e29a7b3af65baf42213ac70a39b",
"height": 2776576,
"reward": 600499860000,
"timestamp": 1670953659
}"#;
assert_eq!(data_after_ser, json)
}
#[test]
fn serde_priv_p2pool_pool_api() {
let data = r#"{
"pool_list": ["pplns"],
"pool_statistics": {
"hashRate": 10225772,
"miners": 713,
"totalHashes": 487463929193948,
"lastBlockFoundTime": 1670453228,
"lastBlockFound": 2756570,
"totalBlocksFound": 4
}
}"#;
let priv_api = crate::helper::p2pool::PrivP2poolPoolApi::from_str(data).unwrap();
let json = serde_json::ser::to_string_pretty(&priv_api).unwrap();
println!("{}", json);
let data_after_ser = r#"{
"pool_statistics": {
"hashRate": 10225772,
"miners": 713
}
}"#;
assert_eq!(data_after_ser, json)
}
#[test]
fn serde_priv_xmrig_api() {
let data = r#"{
"id": "6226e3sd0cd1a6es",
"worker_id": "hinto",
"uptime": 123,
"restricted": true,
"resources": {
"memory": {
"free": 123,
"total": 123123,
"resident_set_memory": 123123123
},
"load_average": [10.97, 10.58, 10.47],
"hardware_concurrency": 12
},
"features": ["api", "asm", "http", "hwloc", "tls", "opencl", "cuda"],
"results": {
"diff_current": 123,
"shares_good": 123,
"shares_total": 123,
"avg_time": 123,
"avg_time_ms": 123,
"hashes_total": 123,
"best": [123, 123, 123, 13, 123, 123, 123, 123, 123, 123],
"error_log": []
},
"algo": "rx/0",
"connection": {
"pool": "localhost:3333",
"ip": "127.0.0.1",
"uptime": 123,
"uptime_ms": 123,
"ping": 0,
"failures": 0,
"tls": null,
"tls-fingerprint": null,
"algo": "rx/0",
"diff": 123,
"accepted": 123,
"rejected": 123,
"avg_time": 123,
"avg_time_ms": 123,
"hashes_total": 123,
"error_log": []
},
"version": "6.18.0",
"kind": "miner",
"ua": "XMRig/6.18.0 (Linux x86_64) libuv/2.0.0-dev gcc/10.2.1",
"cpu": {
"brand": "blah blah blah",
"family": 1,
"model": 2,
"stepping": 0,
"proc_info": 123,
"aes": true,
"avx2": true,
"x64": true,
"64_bit": true,
"l2": 123123,
"l3": 123123,
"cores": 12,
"threads": 24,
"packages": 1,
"nodes": 1,
"backend": "hwloc/2.8.0a1-git",
"msr": "ryzen_19h",
"assembly": "ryzen",
"arch": "x86_64",
"flags": ["aes", "vaes", "avx", "avx2", "bmi2", "osxsave", "pdpe1gb", "sse2", "ssse3", "sse4.1", "popcnt", "cat_l3"]
},
"donate_level": 0,
"paused": false,
"algorithms": ["cn/1", "cn/2", "cn/r", "cn/fast", "cn/half", "cn/xao", "cn/rto", "cn/rwz", "cn/zls", "cn/double", "cn/ccx", "cn-lite/1", "cn-heavy/0", "cn-heavy/tube", "cn-heavy/xhv", "cn-pico", "cn-pico/tlo", "cn/upx2", "rx/0", "rx/wow", "rx/arq", "rx/graft", "rx/sfx", "rx/keva", "argon2/chukwa", "argon2/chukwav2", "argon2/ninja", "astrobwt", "astrobwt/v2", "ghostrider"],
"hashrate": {
"total": [111.11, 111.11, 111.11],
"highest": 111.11,
"threads": [
[111.11, 111.11, 111.11]
]
},
"hugepages": true
}"#;
use crate::helper::xmrig::PrivXmrigApi;
let priv_api = serde_json::from_str::<PrivXmrigApi>(data).unwrap();
let json = serde_json::ser::to_string_pretty(&priv_api).unwrap();
println!("{}", json);
let data_after_ser = r#"{
"worker_id": "hinto",
"resources": {
"load_average": [
10.97,
10.58,
10.47
]
},
"connection": {
"diff": 123,
"accepted": 123,
"rejected": 123
},
"hashrate": {
"total": [
111.11,
111.11,
111.11
]
}
}"#;
assert_eq!(data_after_ser, json)
}
}

View file

@ -484,6 +484,7 @@ impl Helper {
*lock!(gui_api) = PubP2poolApi::new(); *lock!(gui_api) = PubP2poolApi::new();
// 4. Loop as watchdog // 4. Loop as watchdog
let mut first_loop = true;
info!("P2Pool | Entering watchdog mode... woof!"); info!("P2Pool | Entering watchdog mode... woof!");
loop { loop {
// Set timer // Set timer
@ -693,7 +694,9 @@ impl Helper {
} }
} }
} }
if lock!(gui_api).tick_status >= 10 && lock!(process).state == ProcessState::Alive { if (lock!(gui_api).tick_status >= 60 || first_loop)
&& lock!(process).state == ProcessState::Alive
{
debug!("P2Pool Watchdog | Reading status output of p2pool node"); debug!("P2Pool Watchdog | Reading status output of p2pool node");
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
if let Err(e) = write!(stdin, "statusfromgupaxx\r\n") { if let Err(e) = write!(stdin, "statusfromgupaxx\r\n") {
@ -712,6 +715,9 @@ impl Helper {
// Sleep (only if 900ms hasn't passed) // Sleep (only if 900ms hasn't passed)
let elapsed = now.elapsed().as_millis(); let elapsed = now.elapsed().as_millis();
if first_loop {
first_loop = false;
}
// Since logic goes off if less than 1000, casting should be safe // Since logic goes off if less than 1000, casting should be safe
if elapsed < 900 { if elapsed < 900 {
let sleep = (900 - elapsed) as u64; let sleep = (900 - elapsed) as u64;

808
src/helper/tests.rs Normal file
View file

@ -0,0 +1,808 @@
#[cfg(test)]
mod test {
use crate::helper::{
p2pool::{PrivP2poolLocalApi, PrivP2poolNetworkApi},
xvb::{algorithm::calcul_donated_time, rounds::round_type},
Helper, Process, ProcessName, ProcessState,
};
#[test]
fn get_current_shares() {
let stdout = "
statusfromgupaxx
2024-03-25 21:31:21.7919 SideChain status
Monero node = node2.monerodevs.org:18089:ZMQ:18084 (37.187.74.171)
Main chain height = 3113042
Main chain hashrate = 1.985 GH/s
Side chain ID = mini
Side chain height = 7230432
Side chain hashrate = 8.925 MH/s
PPLNS window = 2160 blocks (+79 uncles, 0 orphans)
PPLNS window duration = 6h 9m 46s
Your wallet address = 4A5Dwt2qKwKEQrZfo4aBkSNtvDDAzSFbAJcyFkdW5RwDh9U4WgeZrgKT4hUoE2gv8h6NmsNMTyjsEL8eSLMbABds5rYFWnw
Your shares = 0 blocks (+0 uncles, 0 orphans)
Block reward share = 0.000% (0.000000000000 XMR)
2024-03-25 21:31:21.7920 StratumServer status
Hashrate (15m est) = 0 H/s
Hashrate (1h est) = 0 H/s
Hashrate (24h est) = 0 H/s
Total hashes = 0
Shares found = 0
Average effort = 0.000%
Current effort = 0.000%
Connections = 0 (0 incoming)
2024-03-25 21:31:21.7920 P2PServer status
Connections = 10 (0 incoming)
Peer list size = 1209
Uptime = 0h 2m 4s
".lines();
let mut shares = 1;
let mut status_output = false;
for line in stdout {
// if command status is sent by gupaxx process and not the user, forward it only to update_from_status method.
// 25 lines after the command are the result of status, with last line finishing by update.
if line.contains("statusfromgupaxx") {
status_output = true;
continue;
}
if status_output {
if line.contains("Your shares") {
// update sidechain shares
shares = line.split_once('=').expect("should be = at Your Share, maybe new version of p2pool has different output for status command ?").1.split_once("blocks").expect("should be a 'blocks' at Your Share, maybe new version of p2pool has different output for status command ?").0.trim().parse::<u32>().expect("this should be the number of share");
}
if line.contains("Uptime") {
// end of status
status_output = false;
}
continue;
}
}
assert_eq!(shares, 0);
}
#[test]
fn reset_gui_output() {
let max = crate::helper::GUI_OUTPUT_LEEWAY;
let mut string = String::with_capacity(max);
for _ in 0..=max {
string.push('0');
}
Helper::check_reset_gui_output(&mut string, ProcessName::P2pool);
// Some text gets added, so just check for less than 500 bytes.
assert!(string.len() < 500);
}
#[test]
fn combine_gui_pub_p2pool_api() {
use crate::helper::PubP2poolApi;
let mut gui_api = PubP2poolApi::new();
let mut pub_api = PubP2poolApi::new();
pub_api.payouts = 1;
pub_api.payouts_hour = 2.0;
pub_api.payouts_day = 3.0;
pub_api.payouts_month = 4.0;
pub_api.xmr = 1.0;
pub_api.xmr_hour = 2.0;
pub_api.xmr_day = 3.0;
pub_api.xmr_month = 4.0;
println!("BEFORE - GUI_API: {:#?}\nPUB_API: {:#?}", gui_api, pub_api);
assert_ne!(gui_api, pub_api);
PubP2poolApi::combine_gui_pub_api(&mut gui_api, &mut pub_api);
println!("AFTER - GUI_API: {:#?}\nPUB_API: {:#?}", gui_api, pub_api);
assert_eq!(gui_api, pub_api);
pub_api.xmr = 2.0;
PubP2poolApi::combine_gui_pub_api(&mut gui_api, &mut pub_api);
assert_eq!(gui_api, pub_api);
assert_eq!(gui_api.xmr, 2.0);
assert_eq!(pub_api.xmr, 2.0);
}
#[test]
fn calc_payouts_and_xmr_from_output_p2pool() {
use crate::helper::PubP2poolApi;
use std::sync::{Arc, Mutex};
let public = Arc::new(Mutex::new(PubP2poolApi::new()));
let output_parse = Arc::new(Mutex::new(String::from(
r#"payout of 5.000000000001 XMR in block 1111
payout of 5.000000000001 XMR in block 1112
payout of 5.000000000001 XMR in block 1113"#,
)));
let output_pub = Arc::new(Mutex::new(String::new()));
let elapsed = std::time::Duration::from_secs(60);
let process = Arc::new(Mutex::new(Process::new(
ProcessName::P2pool,
"".to_string(),
PathBuf::new(),
)));
PubP2poolApi::update_from_output(&public, &output_parse, &output_pub, elapsed, &process);
let public = public.lock().unwrap();
println!("{:#?}", public);
assert_eq!(public.payouts, 3);
assert_eq!(public.payouts_hour, 180.0);
assert_eq!(public.payouts_day, 4320.0);
assert_eq!(public.payouts_month, 129600.0);
assert_eq!(public.xmr, 15.000000000003);
assert_eq!(public.xmr_hour, 900.00000000018);
assert_eq!(public.xmr_day, 21600.00000000432);
assert_eq!(public.xmr_month, 648000.0000001296);
}
#[test]
fn set_p2pool_synchronized() {
use crate::helper::PubP2poolApi;
use std::sync::{Arc, Mutex};
let public = Arc::new(Mutex::new(PubP2poolApi::new()));
let output_parse = Arc::new(Mutex::new(String::from(
r#"payout of 5.000000000001 XMR in block 1111
NOTICE 2021-12-27 21:42:17.2008 SideChain SYNCHRONIZED
payout of 5.000000000001 XMR in block 1113"#,
)));
let output_pub = Arc::new(Mutex::new(String::new()));
let elapsed = std::time::Duration::from_secs(60);
let process = Arc::new(Mutex::new(Process::new(
ProcessName::P2pool,
"".to_string(),
PathBuf::new(),
)));
// It only gets checked if we're `Syncing`.
process.lock().unwrap().state = ProcessState::Syncing;
PubP2poolApi::update_from_output(&public, &output_parse, &output_pub, elapsed, &process);
println!("{:#?}", process);
assert!(process.lock().unwrap().state == ProcessState::Alive);
}
#[test]
fn p2pool_synchronized_false_positive() {
use crate::helper::PubP2poolApi;
use std::sync::{Arc, Mutex};
let public = Arc::new(Mutex::new(PubP2poolApi::new()));
// The SideChain that is "SYNCHRONIZED" in this output is
// probably not main/mini, but the sidechain started on height 1,
// so this should _not_ trigger alive state.
let output_parse = Arc::new(Mutex::new(String::from(
r#"payout of 5.000000000001 XMR in block 1111
SideChain new chain tip: next height = 1
NOTICE 2021-12-27 21:42:17.2008 SideChain SYNCHRONIZED
payout of 5.000000000001 XMR in block 1113"#,
)));
let output_pub = Arc::new(Mutex::new(String::new()));
let elapsed = std::time::Duration::from_secs(60);
let process = Arc::new(Mutex::new(Process::new(
ProcessName::P2pool,
"".to_string(),
PathBuf::new(),
)));
// It only gets checked if we're `Syncing`.
process.lock().unwrap().state = ProcessState::Syncing;
PubP2poolApi::update_from_output(&public, &output_parse, &output_pub, elapsed, &process);
println!("{:#?}", process);
assert!(process.lock().unwrap().state == ProcessState::Syncing); // still syncing
}
#[test]
fn p2pool_synchronized_double_synchronized() {
use crate::helper::PubP2poolApi;
use std::sync::{Arc, Mutex};
let public = Arc::new(Mutex::new(PubP2poolApi::new()));
// The 1st SideChain that is "SYNCHRONIZED" in this output is
// the sidechain started on height 1, but there is another one
// which means the real main/mini is probably synced,
// so this _should_ trigger alive state.
let output_parse = Arc::new(Mutex::new(String::from(
r#"payout of 5.000000000001 XMR in block 1111
SideChain new chain tip: next height = 1
NOTICE 2021-12-27 21:42:17.2008 SideChain SYNCHRONIZED
payout of 5.000000000001 XMR in block 1113
NOTICE 2021-12-27 21:42:17.2100 SideChain SYNCHRONIZED"#,
)));
let output_pub = Arc::new(Mutex::new(String::new()));
let elapsed = std::time::Duration::from_secs(60);
let process = Arc::new(Mutex::new(Process::new(
ProcessName::P2pool,
"".to_string(),
PathBuf::new(),
)));
// It only gets checked if we're `Syncing`.
process.lock().unwrap().state = ProcessState::Syncing;
PubP2poolApi::update_from_output(&public, &output_parse, &output_pub, elapsed, &process);
println!("{:#?}", process);
assert!(process.lock().unwrap().state == ProcessState::Alive);
}
#[test]
fn update_pub_p2pool_from_local_network_pool() {
use crate::helper::p2pool::PoolStatistics;
use crate::helper::p2pool::PrivP2poolLocalApi;
use crate::helper::p2pool::PrivP2poolNetworkApi;
use crate::helper::p2pool::PrivP2poolPoolApi;
use crate::helper::PubP2poolApi;
use std::sync::{Arc, Mutex};
let public = Arc::new(Mutex::new(PubP2poolApi::new()));
let local = PrivP2poolLocalApi {
hashrate_15m: 10_000,
hashrate_1h: 20_000,
hashrate_24h: 30_000,
shares_found: 1000,
average_effort: 100.000,
current_effort: 200.000,
connections: 1234,
};
let network = PrivP2poolNetworkApi {
difficulty: 300_000_000_000,
hash: "asdf".to_string(),
height: 1234,
reward: 2345,
timestamp: 3456,
};
let pool = PrivP2poolPoolApi {
pool_statistics: PoolStatistics {
hashRate: 1_000_000, // 1 MH/s
miners: 1_000,
},
};
// Update Local
PubP2poolApi::update_from_local(&public, local);
let p = public.lock().unwrap();
println!("AFTER LOCAL: {:#?}", p);
assert_eq!(p.hashrate_15m.to_string(), "10,000");
assert_eq!(p.hashrate_1h.to_string(), "20,000");
assert_eq!(p.hashrate_24h.to_string(), "30,000");
assert_eq!(
p.shares_found.expect("the value is set").to_string(),
"1000"
);
assert_eq!(p.average_effort.to_string(), "100.00%");
assert_eq!(p.current_effort.to_string(), "200.00%");
assert_eq!(p.connections.to_string(), "1,234");
assert_eq!(p.user_p2pool_hashrate_u64, 20000);
drop(p);
// Update Network + Pool
PubP2poolApi::update_from_network_pool(&public, network, pool);
let p = public.lock().unwrap();
println!("AFTER NETWORK+POOL: {:#?}", p);
assert_eq!(p.monero_difficulty.to_string(), "300,000,000,000");
assert_eq!(p.monero_hashrate.to_string(), "2.500 GH/s");
assert_eq!(p.hash.to_string(), "asdf");
assert_eq!(p.height.to_string(), "1,234");
assert_eq!(p.reward.to_u64(), 2345);
assert_eq!(p.p2pool_difficulty.to_string(), "10,000,000");
assert_eq!(p.p2pool_hashrate.to_string(), "1.000 MH/s");
assert_eq!(p.miners.to_string(), "1,000");
assert_eq!(
p.solo_block_mean.to_string(),
"5 months, 21 days, 9 hours, 52 minutes"
);
assert_eq!(
p.p2pool_block_mean.to_string(),
"3 days, 11 hours, 20 minutes"
);
assert_eq!(p.p2pool_share_mean.to_string(), "8 minutes, 20 seconds");
assert_eq!(p.p2pool_percent.to_string(), "0.040000%");
assert_eq!(p.user_p2pool_percent.to_string(), "2.000000%");
assert_eq!(p.user_monero_percent.to_string(), "0.000800%");
drop(p);
}
#[test]
fn set_xmrig_mining() {
use crate::helper::PubXmrigApi;
use std::sync::{Arc, Mutex};
let public = Arc::new(Mutex::new(PubXmrigApi::new()));
let output_parse = Arc::new(Mutex::new(String::from(
"[2022-02-12 12:49:30.311] net no active pools, stop mining",
)));
let output_pub = Arc::new(Mutex::new(String::new()));
let elapsed = std::time::Duration::from_secs(60);
let process = Arc::new(Mutex::new(Process::new(
ProcessName::Xmrig,
"".to_string(),
PathBuf::new(),
)));
process.lock().unwrap().state = ProcessState::Alive;
PubXmrigApi::update_from_output(&public, &output_parse, &output_pub, elapsed, &process);
println!("{:#?}", process);
assert!(process.lock().unwrap().state == ProcessState::NotMining);
let output_parse = Arc::new(Mutex::new(String::from("[2022-02-12 12:49:30.311] net new job from 192.168.2.1:3333 diff 402K algo rx/0 height 2241142 (11 tx)")));
PubXmrigApi::update_from_output(&public, &output_parse, &output_pub, elapsed, &process);
assert!(process.lock().unwrap().state == ProcessState::Alive);
}
#[test]
fn serde_priv_p2pool_local_api() {
let data = r#"{
"hashrate_15m": 12,
"hashrate_1h": 11111,
"hashrate_24h": 468967,
"total_hashes": 2019283840922394082390,
"shares_found": 289037,
"average_effort": 915.563,
"current_effort": 129.297,
"connections": 123,
"incoming_connections": 96
}"#;
let priv_api = PrivP2poolLocalApi::from_str(data).unwrap();
let json = serde_json::ser::to_string_pretty(&priv_api).unwrap();
println!("{}", json);
let data_after_ser = r#"{
"hashrate_15m": 12,
"hashrate_1h": 11111,
"hashrate_24h": 468967,
"shares_found": 289037,
"average_effort": 915.563,
"current_effort": 129.297,
"connections": 123
}"#;
assert_eq!(data_after_ser, json)
}
#[test]
fn serde_priv_p2pool_network_api() {
let data = r#"{
"difficulty": 319028180924,
"hash": "22ae1b83d727bb2ff4efc17b485bc47bc8bf5e29a7b3af65baf42213ac70a39b",
"height": 2776576,
"reward": 600499860000,
"timestamp": 1670953659
}"#;
let priv_api = PrivP2poolNetworkApi::from_str(data).unwrap();
let json = serde_json::ser::to_string_pretty(&priv_api).unwrap();
println!("{}", json);
let data_after_ser = r#"{
"difficulty": 319028180924,
"hash": "22ae1b83d727bb2ff4efc17b485bc47bc8bf5e29a7b3af65baf42213ac70a39b",
"height": 2776576,
"reward": 600499860000,
"timestamp": 1670953659
}"#;
assert_eq!(data_after_ser, json)
}
#[test]
fn serde_priv_p2pool_pool_api() {
let data = r#"{
"pool_list": ["pplns"],
"pool_statistics": {
"hashRate": 10225772,
"miners": 713,
"totalHashes": 487463929193948,
"lastBlockFoundTime": 1670453228,
"lastBlockFound": 2756570,
"totalBlocksFound": 4
}
}"#;
let priv_api = crate::helper::p2pool::PrivP2poolPoolApi::from_str(data).unwrap();
let json = serde_json::ser::to_string_pretty(&priv_api).unwrap();
println!("{}", json);
let data_after_ser = r#"{
"pool_statistics": {
"hashRate": 10225772,
"miners": 713
}
}"#;
assert_eq!(data_after_ser, json)
}
#[test]
fn serde_priv_xmrig_api() {
let data = r#"{
"id": "6226e3sd0cd1a6es",
"worker_id": "hinto",
"uptime": 123,
"restricted": true,
"resources": {
"memory": {
"free": 123,
"total": 123123,
"resident_set_memory": 123123123
},
"load_average": [10.97, 10.58, 10.47],
"hardware_concurrency": 12
},
"features": ["api", "asm", "http", "hwloc", "tls", "opencl", "cuda"],
"results": {
"diff_current": 123,
"shares_good": 123,
"shares_total": 123,
"avg_time": 123,
"avg_time_ms": 123,
"hashes_total": 123,
"best": [123, 123, 123, 13, 123, 123, 123, 123, 123, 123],
"error_log": []
},
"algo": "rx/0",
"connection": {
"pool": "localhost:3333",
"ip": "127.0.0.1",
"uptime": 123,
"uptime_ms": 123,
"ping": 0,
"failures": 0,
"tls": null,
"tls-fingerprint": null,
"algo": "rx/0",
"diff": 123,
"accepted": 123,
"rejected": 123,
"avg_time": 123,
"avg_time_ms": 123,
"hashes_total": 123,
"error_log": []
},
"version": "6.18.0",
"kind": "miner",
"ua": "XMRig/6.18.0 (Linux x86_64) libuv/2.0.0-dev gcc/10.2.1",
"cpu": {
"brand": "blah blah blah",
"family": 1,
"model": 2,
"stepping": 0,
"proc_info": 123,
"aes": true,
"avx2": true,
"x64": true,
"64_bit": true,
"l2": 123123,
"l3": 123123,
"cores": 12,
"threads": 24,
"packages": 1,
"nodes": 1,
"backend": "hwloc/2.8.0a1-git",
"msr": "ryzen_19h",
"assembly": "ryzen",
"arch": "x86_64",
"flags": ["aes", "vaes", "avx", "avx2", "bmi2", "osxsave", "pdpe1gb", "sse2", "ssse3", "sse4.1", "popcnt", "cat_l3"]
},
"donate_level": 0,
"paused": false,
"algorithms": ["cn/1", "cn/2", "cn/r", "cn/fast", "cn/half", "cn/xao", "cn/rto", "cn/rwz", "cn/zls", "cn/double", "cn/ccx", "cn-lite/1", "cn-heavy/0", "cn-heavy/tube", "cn-heavy/xhv", "cn-pico", "cn-pico/tlo", "cn/upx2", "rx/0", "rx/wow", "rx/arq", "rx/graft", "rx/sfx", "rx/keva", "argon2/chukwa", "argon2/chukwav2", "argon2/ninja", "astrobwt", "astrobwt/v2", "ghostrider"],
"hashrate": {
"total": [111.11, 111.11, 111.11],
"highest": 111.11,
"threads": [
[111.11, 111.11, 111.11]
]
},
"hugepages": true
}"#;
use crate::helper::xmrig::PrivXmrigApi;
let priv_api = serde_json::from_str::<PrivXmrigApi>(data).unwrap();
let json = serde_json::ser::to_string_pretty(&priv_api).unwrap();
println!("{}", json);
let data_after_ser = r#"{
"worker_id": "hinto",
"resources": {
"load_average": [
10.97,
10.58,
10.47
]
},
"connection": {
"diff": 123,
"accepted": 123,
"rejected": 123
},
"hashrate": {
"total": [
111.11,
111.11,
111.11
]
}
}"#;
assert_eq!(data_after_ser, json)
}
use std::{
path::PathBuf,
sync::{Arc, Mutex},
thread,
};
use crate::{
disk::state::P2pool,
helper::{p2pool::PubP2poolApi, xmrig::PubXmrigApi, xvb::rounds::XvbRound},
macros::lock,
XVB_TIME_ALGO,
};
use crate::helper::xvb::{public_stats::XvbPubStats, PubXvbApi};
use reqwest::Client;
#[test]
fn public_api_deserialize() {
let client = Client::new();
let new_data = thread::spawn(move || corr(&client)).join().unwrap();
assert!(!new_data.reward_yearly.is_empty());
}
#[tokio::main]
async fn corr(client: &Client) -> XvbPubStats {
XvbPubStats::request_api(client).await.unwrap()
}
#[test]
fn algorithm_time_given() {
let gui_api_xvb = Arc::new(Mutex::new(PubXvbApi::new()));
let gui_api_p2pool = Arc::new(Mutex::new(PubP2poolApi::new()));
let gui_api_xmrig = Arc::new(Mutex::new(PubXmrigApi::new()));
let state_p2pool = P2pool::default();
lock!(gui_api_p2pool).p2pool_difficulty_u64 = 95000000;
let share = 1;
// verify that if one share found (enough for vip round) but not enough for donor round, no time will be given to xvb, except if in hero mode.
// 15mn average HR of xmrig is 5kH/s
lock!(gui_api_xvb).stats_priv.donor_1hr_avg = 0.0;
lock!(gui_api_xmrig).hashrate_raw_15m = 5000.0;
lock!(gui_api_xvb).stats_priv.runtime_hero_mode = false;
let given_time = calcul_donated_time(
lock!(gui_api_xmrig).hashrate_raw_15m,
&gui_api_p2pool,
&gui_api_xvb,
&state_p2pool,
);
// verify that default mode will give x seconds
assert_eq!(given_time, 0);
// given time should always be less than XVB_TIME_ALGO
assert!(given_time < XVB_TIME_ALGO);
// verify that right round should be detected.
lock!(gui_api_xvb).stats_priv.donor_1hr_avg =
(((given_time as f32 / XVB_TIME_ALGO as f32) * lock!(gui_api_xmrig).hashrate_raw_15m)
/ 1000.0)
* 1.2;
lock!(gui_api_xvb).stats_priv.donor_24hr_avg =
(((given_time as f32 / XVB_TIME_ALGO as f32) * lock!(gui_api_xmrig).hashrate_raw_15m)
/ 1000.0)
* 1.2;
assert_eq!(round_type(share, &gui_api_xvb), Some(XvbRound::Vip));
// verify that hero mode will give x seconds
lock!(gui_api_xvb).stats_priv.runtime_hero_mode = true;
let given_time = calcul_donated_time(
lock!(gui_api_xmrig).hashrate_raw_15m,
&gui_api_p2pool,
&gui_api_xvb,
&state_p2pool,
);
assert_eq!(given_time, 45);
// verify that right round should be detected.
lock!(gui_api_xvb).stats_priv.donor_1hr_avg =
(((given_time as f32 / XVB_TIME_ALGO as f32) * lock!(gui_api_xmrig).hashrate_raw_15m)
/ 1000.0)
* 1.2;
lock!(gui_api_xvb).stats_priv.donor_24hr_avg =
(((given_time as f32 / XVB_TIME_ALGO as f32) * lock!(gui_api_xmrig).hashrate_raw_15m)
/ 1000.0)
* 1.2;
assert_eq!(round_type(share, &gui_api_xvb), Some(XvbRound::Vip));
// verify that if one share and not enough for donor vip round (should be in donor round), right amount of time will be given to xvb for default and hero mode
lock!(gui_api_xvb).stats_priv.donor_1hr_avg = 0.0;
lock!(gui_api_xmrig).hashrate_raw_15m = 8000.0;
lock!(gui_api_xvb).stats_priv.runtime_hero_mode = false;
let given_time = calcul_donated_time(
lock!(gui_api_xmrig).hashrate_raw_15m,
&gui_api_p2pool,
&gui_api_xvb,
&state_p2pool,
);
// verify that default mode will give x seconds
assert_eq!(given_time, 75);
// given time should always be less than XVB_TIME_ALGO
assert!(given_time < XVB_TIME_ALGO);
// verify that right round should be detected.
lock!(gui_api_xvb).stats_priv.donor_1hr_avg =
(((given_time as f32 / XVB_TIME_ALGO as f32) * lock!(gui_api_xmrig).hashrate_raw_15m)
/ 1000.0)
* 1.2;
lock!(gui_api_xvb).stats_priv.donor_24hr_avg =
(((given_time as f32 / XVB_TIME_ALGO as f32) * lock!(gui_api_xmrig).hashrate_raw_15m)
/ 1000.0)
* 1.2;
assert_eq!(round_type(share, &gui_api_xvb), Some(XvbRound::Donor));
// verify that hero mode will give x seconds
lock!(gui_api_xvb).stats_priv.runtime_hero_mode = true;
let given_time = calcul_donated_time(
lock!(gui_api_xmrig).hashrate_raw_15m,
&gui_api_p2pool,
&gui_api_xvb,
&state_p2pool,
);
assert_eq!(given_time, 253);
// verify that right round should be detected.
lock!(gui_api_xvb).stats_priv.donor_1hr_avg =
(((given_time as f32 / XVB_TIME_ALGO as f32) * lock!(gui_api_xmrig).hashrate_raw_15m)
/ 1000.0)
* 1.2;
lock!(gui_api_xvb).stats_priv.donor_24hr_avg =
(((given_time as f32 / XVB_TIME_ALGO as f32) * lock!(gui_api_xmrig).hashrate_raw_15m)
/ 1000.0)
* 1.2;
assert_eq!(round_type(share, &gui_api_xvb), Some(XvbRound::Donor));
// verify that if one share and not enough for donor whale round(should be in donor vip), right amount of time will be given to xvb for default and hero mode
lock!(gui_api_xvb).stats_priv.donor_1hr_avg = 0.0;
lock!(gui_api_xmrig).hashrate_raw_15m = 19000.0;
lock!(gui_api_xvb).stats_priv.runtime_hero_mode = false;
let given_time = calcul_donated_time(
lock!(gui_api_xmrig).hashrate_raw_15m,
&gui_api_p2pool,
&gui_api_xvb,
&state_p2pool,
);
// verify that default mode will give x seconds
assert_eq!(given_time, 315);
// given time should always be less than XVB_TIME_ALGO
assert!(given_time < XVB_TIME_ALGO);
// verify that right round should be detected.
lock!(gui_api_xvb).stats_priv.donor_1hr_avg =
(((given_time as f32 / XVB_TIME_ALGO as f32) * lock!(gui_api_xmrig).hashrate_raw_15m)
/ 1000.0)
* 1.2;
lock!(gui_api_xvb).stats_priv.donor_24hr_avg =
(((given_time as f32 / XVB_TIME_ALGO as f32) * lock!(gui_api_xmrig).hashrate_raw_15m)
/ 1000.0)
* 1.2;
assert_eq!(round_type(share, &gui_api_xvb), Some(XvbRound::DonorVip));
// verify that hero mode will give x seconds
lock!(gui_api_xvb).stats_priv.runtime_hero_mode = true;
let given_time = calcul_donated_time(
lock!(gui_api_xmrig).hashrate_raw_15m,
&gui_api_p2pool,
&gui_api_xvb,
&state_p2pool,
);
assert_eq!(given_time, 454);
// verify that right round should be detected.
lock!(gui_api_xvb).stats_priv.donor_1hr_avg =
(((given_time as f32 / XVB_TIME_ALGO as f32) * lock!(gui_api_xmrig).hashrate_raw_15m)
/ 1000.0)
* 1.2;
lock!(gui_api_xvb).stats_priv.donor_24hr_avg =
(((given_time as f32 / XVB_TIME_ALGO as f32) * lock!(gui_api_xmrig).hashrate_raw_15m)
/ 1000.0)
* 1.2;
assert_eq!(round_type(share, &gui_api_xvb), Some(XvbRound::DonorVip));
// verify that if one share and not enough for donor mega round, right amount of time will be given to xvb for default and hero mode
lock!(gui_api_xvb).stats_priv.donor_1hr_avg = 0.0;
lock!(gui_api_xmrig).hashrate_raw_15m = 105000.0;
lock!(gui_api_xvb).stats_priv.runtime_hero_mode = false;
let given_time = calcul_donated_time(
lock!(gui_api_xmrig).hashrate_raw_15m,
&gui_api_p2pool,
&gui_api_xvb,
&state_p2pool,
);
// verify that default mode will give x seconds
assert_eq!(given_time, 571);
// given time should always be less than XVB_TIME_ALGO
assert!(given_time < XVB_TIME_ALGO);
// verify that right round should be detected.
lock!(gui_api_xvb).stats_priv.donor_1hr_avg =
(((given_time as f32 / XVB_TIME_ALGO as f32) * lock!(gui_api_xmrig).hashrate_raw_15m)
/ 1000.0)
* 1.2;
lock!(gui_api_xvb).stats_priv.donor_24hr_avg =
(((given_time as f32 / XVB_TIME_ALGO as f32) * lock!(gui_api_xmrig).hashrate_raw_15m)
/ 1000.0)
* 1.2;
assert_eq!(round_type(share, &gui_api_xvb), Some(XvbRound::DonorWhale));
// verify that hero mode will give x seconds
lock!(gui_api_xvb).stats_priv.runtime_hero_mode = true;
let given_time = calcul_donated_time(
lock!(gui_api_xmrig).hashrate_raw_15m,
&gui_api_p2pool,
&gui_api_xvb,
&state_p2pool,
);
assert_eq!(given_time, 573);
// verify that right round should be detected.
lock!(gui_api_xvb).stats_priv.donor_1hr_avg =
(((given_time as f32 / XVB_TIME_ALGO as f32) * lock!(gui_api_xmrig).hashrate_raw_15m)
/ 1000.0)
* 1.2;
lock!(gui_api_xvb).stats_priv.donor_24hr_avg =
(((given_time as f32 / XVB_TIME_ALGO as f32) * lock!(gui_api_xmrig).hashrate_raw_15m)
/ 1000.0)
* 1.2;
assert_eq!(round_type(share, &gui_api_xvb), Some(XvbRound::DonorWhale));
// verify that if one share and enough for donor mega round, right amount of time will be given to xvb for default and hero mode
lock!(gui_api_xvb).stats_priv.donor_1hr_avg = 0.0;
lock!(gui_api_xmrig).hashrate_raw_15m = 1205000.0;
lock!(gui_api_xvb).stats_priv.runtime_hero_mode = false;
let given_time = calcul_donated_time(
lock!(gui_api_xmrig).hashrate_raw_15m,
&gui_api_p2pool,
&gui_api_xvb,
&state_p2pool,
);
// verify that default mode will give x seconds
assert_eq!(given_time, 497);
// given time should always be less than XVB_TIME_ALGO
assert!(given_time < XVB_TIME_ALGO);
// verify that right round should be detected.
lock!(gui_api_xvb).stats_priv.donor_1hr_avg =
(((given_time as f32 / XVB_TIME_ALGO as f32) * lock!(gui_api_xmrig).hashrate_raw_15m)
/ 1000.0)
* 1.2;
lock!(gui_api_xvb).stats_priv.donor_24hr_avg =
(((given_time as f32 / XVB_TIME_ALGO as f32) * lock!(gui_api_xmrig).hashrate_raw_15m)
/ 1000.0)
* 1.2;
assert_eq!(round_type(share, &gui_api_xvb), Some(XvbRound::DonorMega));
// verify that hero mode will give x seconds
lock!(gui_api_xvb).stats_priv.runtime_hero_mode = true;
let given_time = calcul_donated_time(
lock!(gui_api_xmrig).hashrate_raw_15m,
&gui_api_p2pool,
&gui_api_xvb,
&state_p2pool,
);
assert_eq!(given_time, 597);
// verify that right round should be detected.
lock!(gui_api_xvb).stats_priv.donor_1hr_avg =
(((given_time as f32 / XVB_TIME_ALGO as f32) * lock!(gui_api_xmrig).hashrate_raw_15m)
/ 1000.0)
* 1.2;
lock!(gui_api_xvb).stats_priv.donor_24hr_avg =
(((given_time as f32 / XVB_TIME_ALGO as f32) * lock!(gui_api_xmrig).hashrate_raw_15m)
/ 1000.0)
* 1.2;
assert_eq!(round_type(share, &gui_api_xvb), Some(XvbRound::DonorMega));
// verify that if one share and enough for donor vp round if XvB oHR is given, right amount of time will be given to xvb for default and hero mode
lock!(gui_api_xvb).output.clear();
lock!(gui_api_xmrig).hashrate_raw_15m = 12500.0;
lock!(gui_api_xvb).stats_priv.donor_1hr_avg = 5.0;
lock!(gui_api_xvb).stats_priv.runtime_hero_mode = false;
let given_time = calcul_donated_time(
lock!(gui_api_xmrig).hashrate_raw_15m,
&gui_api_p2pool,
&gui_api_xvb,
&state_p2pool,
);
// verify that default mode will give x seconds
assert_eq!(given_time, 240);
// given time should always be less than XVB_TIME_ALGO
assert!(given_time < XVB_TIME_ALGO);
// verify that right round should be detected.
lock!(gui_api_xvb).stats_priv.donor_1hr_avg = ((((given_time as f32
/ XVB_TIME_ALGO as f32)
* lock!(gui_api_xmrig).hashrate_raw_15m)
+ 5000.0)
/ 1000.0)
* 1.2;
lock!(gui_api_xvb).stats_priv.donor_24hr_avg = ((((given_time as f32
/ XVB_TIME_ALGO as f32)
* lock!(gui_api_xmrig).hashrate_raw_15m)
+ 5000.0)
/ 1000.0)
* 1.2;
assert_eq!(round_type(share, &gui_api_xvb), Some(XvbRound::DonorVip));
// verify that hero mode will give x seconds
lock!(gui_api_xvb).stats_priv.donor_1hr_avg = 5.0;
lock!(gui_api_xvb).stats_priv.runtime_hero_mode = true;
let given_time = calcul_donated_time(
lock!(gui_api_xmrig).hashrate_raw_15m,
&gui_api_p2pool,
&gui_api_xvb,
&state_p2pool,
);
assert_eq!(given_time, 378);
// verify that right round should be detected.
lock!(gui_api_xvb).stats_priv.donor_1hr_avg = ((((given_time as f32
/ XVB_TIME_ALGO as f32)
* lock!(gui_api_xmrig).hashrate_raw_15m)
+ 5000.0)
/ 1000.0)
* 1.2;
lock!(gui_api_xvb).stats_priv.donor_24hr_avg = ((((given_time as f32
/ XVB_TIME_ALGO as f32)
* lock!(gui_api_xmrig).hashrate_raw_15m)
+ 5000.0)
/ 1000.0)
* 1.2;
assert_eq!(round_type(share, &gui_api_xvb), Some(XvbRound::DonorVip));
}
}

View file

@ -1,9 +1,10 @@
use crate::helper::{ProcessName, ProcessSignal, ProcessState}; use crate::helper::{ProcessName, ProcessSignal, ProcessState};
use crate::regex::XMRIG_REGEX; use crate::regex::{contains_connect_error, contains_usepool, detect_new_node_xmrig, XMRIG_REGEX};
use crate::utils::human::HumanNumber; use crate::utils::human::HumanNumber;
use crate::utils::sudo::SudoState; use crate::utils::sudo::SudoState;
use crate::{constants::*, macros::*}; use crate::{constants::*, macros::*};
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use enclose::enclose;
use log::*; use log::*;
use readable::num::Unsigned; use readable::num::Unsigned;
use readable::up::Uptime; use readable::up::Uptime;
@ -22,7 +23,8 @@ use std::{
}; };
use tokio::spawn; use tokio::spawn;
use super::xvb::XvbNode; use super::xvb::nodes::XvbNode;
use super::xvb::PubXvbApi;
use super::{Helper, Process}; use super::{Helper, Process};
impl Helper { impl Helper {
#[cold] #[cold]
@ -32,6 +34,7 @@ impl Helper {
output_pub: Arc<Mutex<String>>, output_pub: Arc<Mutex<String>>,
reader: Box<dyn std::io::Read + Send>, reader: Box<dyn std::io::Read + Send>,
process_xvb: Arc<Mutex<Process>>, process_xvb: Arc<Mutex<Process>>,
pub_api_xvb: &Arc<Mutex<PubXvbApi>>,
) { ) {
use std::io::BufRead; use std::io::BufRead;
let mut stdout = std::io::BufReader::new(reader).lines(); let mut stdout = std::io::BufReader::new(reader).lines();
@ -46,7 +49,7 @@ impl Helper {
if let Err(e) = writeln!(lock!(output_pub), "{}", line) { if let Err(e) = writeln!(lock!(output_pub), "{}", line) {
error!("XMRig PTY Pub | Output error: {}", e); error!("XMRig PTY Pub | Output error: {}", e);
} }
if i > 20 { if i > 13 {
break; break;
} else { } else {
i += 1; i += 1;
@ -56,47 +59,23 @@ impl Helper {
while let Some(Ok(line)) = stdout.next() { while let Some(Ok(line)) = stdout.next() {
// need to verify if node still working // need to verify if node still working
// for that need to catch "connect error" // for that need to catch "connect error"
if line.contains("connect error") { if contains_connect_error(&line) {
let process_xvb_c = process_xvb.clone(); // updating current node to None.
// if waiting, it is restarting or already updating nodes, so do not send signal. lock!(pub_api_xvb).current_node = None;
if lock!(process_xvb_c).state != ProcessState::Waiting { // send signal to update node.
info!("node is offline, switching to backup."); warn!("XMRig PTY Parse | node is offline, switching to backup.");
lock!(process_xvb_c).signal = ProcessSignal::UpdateNodes; lock!(process_xvb).signal = ProcessSignal::UpdateNodes;
}
if contains_usepool(&line) {
info!("XMRig PTY Parse | new pool detected");
// need to update current node because it was updated.
// if custom node made by user, it is not supported because algo is deciding which node to use.
let node = detect_new_node_xmrig(&line);
if node.is_none() {
error!("XMRig PTY Parse | node is not understood, switching to backup.");
lock!(process_xvb).signal = ProcessSignal::UpdateNodes;
} }
// let address = state_p2pool.address.clone(); lock!(pub_api_xvb).current_node = node;
// let token = state_xmrig.token.clone();
// let pub_api_xvb_c = pub_api_xvb.clone();
// issue because while this future is executing, other connect error could arrive and repeat the process.
// spawn(async move {
// // need to create client
// let client_http = Arc::new(
// hyper::Client::builder().build(hyper::client::HttpConnector::new()),
// );
// // need to spawn and wait update fastest node.
// XvbNode::update_fastest_node(&client_http, &pub_api_xvb_c, &process_xvb_c)
// .await;
// // need to check new value of node.
// let node = lock!(pub_api_xvb_c).stats_priv.node.clone();
// // send new value to update config.
// if let Err(err) = PrivXmrigApi::update_xmrig_config(
// &client_http,
// XMRIG_CONFIG_URI,
// &token,
// &node,
// &address,
// )
// .await
// {
// // show to console error about updating xmrig config
// if let Err(e) = writeln!(
// lock!(pub_api_xvb_c).output,
// "Failure to update xmrig config with HTTP API.\nError: {}",
// err
// ) {
// error!("XvB Watchdog | GUI status write failed: {}", e);
// }
// }
// });
} }
// println!("{}", line); // For debugging. // println!("{}", line); // For debugging.
if let Err(e) = writeln!(lock!(output_parse), "{}", line) { if let Err(e) = writeln!(lock!(output_parse), "{}", line) {
@ -194,6 +173,7 @@ impl Helper {
let path = path.to_path_buf(); let path = path.to_path_buf();
let token = state.token.clone(); let token = state.token.clone();
let img_xmrig = Arc::clone(&lock!(helper).img_xmrig); let img_xmrig = Arc::clone(&lock!(helper).img_xmrig);
let pub_api_xvb = Arc::clone(&lock!(helper).pub_api_xvb);
thread::spawn(move || { thread::spawn(move || {
Self::spawn_xmrig_watchdog( Self::spawn_xmrig_watchdog(
process, process,
@ -206,6 +186,7 @@ impl Helper {
&token, &token,
process_xvb, process_xvb,
&img_xmrig, &img_xmrig,
&pub_api_xvb,
); );
}); });
} }
@ -386,6 +367,7 @@ impl Helper {
token: &str, token: &str,
process_xvb: Arc<Mutex<Process>>, process_xvb: Arc<Mutex<Process>>,
img_xmrig: &Arc<Mutex<ImgXmrig>>, img_xmrig: &Arc<Mutex<ImgXmrig>>,
pub_api_xvb: &Arc<Mutex<PubXvbApi>>,
) { ) {
// 1a. Create PTY // 1a. Create PTY
debug!("XMRig | Creating PTY..."); debug!("XMRig | Creating PTY...");
@ -398,6 +380,14 @@ impl Helper {
pixel_height: 0, pixel_height: 0,
}) })
.unwrap(); .unwrap();
// 4. Spawn PTY read thread
debug!("XMRig | Spawning PTY read thread...");
let reader = pair.master.try_clone_reader().unwrap(); // Get STDOUT/STDERR before moving the PTY
let output_parse = Arc::clone(&lock!(process).output_parse);
let output_pub = Arc::clone(&lock!(process).output_pub);
spawn(enclose!((pub_api_xvb) async move {
Self::read_pty_xmrig(output_parse, output_pub, reader, process_xvb, &pub_api_xvb).await;
}));
// 1b. Create command // 1b. Create command
debug!("XMRig | Creating command..."); debug!("XMRig | Creating command...");
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
@ -433,16 +423,15 @@ impl Helper {
lock.state = ProcessState::NotMining; lock.state = ProcessState::NotMining;
lock.signal = ProcessSignal::None; lock.signal = ProcessSignal::None;
lock.start = Instant::now(); lock.start = Instant::now();
let reader = pair.master.try_clone_reader().unwrap(); // Get STDOUT/STDERR before moving the PTY
drop(lock); drop(lock);
// 4. Spawn PTY read thread // // 4. Spawn PTY read thread
debug!("XMRig | Spawning PTY read thread..."); // debug!("XMRig | Spawning PTY read thread...");
let output_parse = Arc::clone(&lock!(process).output_parse); // let output_parse = Arc::clone(&lock!(process).output_parse);
let output_pub = Arc::clone(&lock!(process).output_pub); // let output_pub = Arc::clone(&lock!(process).output_pub);
spawn(async move { // spawn(enclose!((pub_api_xvb) async move {
Self::read_pty_xmrig(output_parse, output_pub, reader, process_xvb).await; // Self::read_pty_xmrig(output_parse, output_pub, reader, process_xvb, &pub_api_xvb).await;
}); // }));
let output_parse = Arc::clone(&lock!(process).output_parse); let output_parse = Arc::clone(&lock!(process).output_parse);
let output_pub = Arc::clone(&lock!(process).output_pub); let output_pub = Arc::clone(&lock!(process).output_pub);
@ -769,7 +758,7 @@ impl PubXmrigApi {
Some(Some(h)) => *h, Some(Some(h)) => *h,
_ => 0.0, _ => 0.0,
}; };
let hashrate_raw_1m = match private.hashrate.total.iter().nth(1) { let hashrate_raw_1m = match private.hashrate.total.get(1) {
Some(Some(h)) => *h, Some(Some(h)) => *h,
_ => 0.0, _ => 0.0,
}; };
@ -820,7 +809,9 @@ impl PrivXmrigApi {
Ok(request Ok(request
.timeout(std::time::Duration::from_millis(5000)) .timeout(std::time::Duration::from_millis(5000))
.send() .send()
.await?.json().await?) .await?
.json()
.await?)
} }
#[inline] #[inline]
// // Replace config with new node // // Replace config with new node
@ -830,7 +821,7 @@ impl PrivXmrigApi {
token: &str, token: &str,
node: &XvbNode, node: &XvbNode,
address: &str, address: &str,
gui_api_xmrig: &Arc<Mutex<PubXmrigApi>>, pub_api_xmrig: &Arc<Mutex<PubXmrigApi>>,
) -> Result<()> { ) -> Result<()> {
// get config // get config
let request = client let request = client
@ -845,8 +836,9 @@ impl PrivXmrigApi {
.ok_or_else(|| anyhow!("pools/0/url does not exist in xmrig config"))? = uri.into(); .ok_or_else(|| anyhow!("pools/0/url does not exist in xmrig config"))? = uri.into();
*config *config
.pointer_mut("/pools/0/user") .pointer_mut("/pools/0/user")
.ok_or_else(|| anyhow!("pools/0/user does not exist in xmrig config"))? = .ok_or_else(|| anyhow!("pools/0/user does not exist in xmrig config"))? = node
node.user(address).into(); .user(&address.chars().take(8).collect::<String>())
.into();
*config *config
.pointer_mut("/pools/0/tls") .pointer_mut("/pools/0/tls")
.ok_or_else(|| anyhow!("pools/0/tls does not exist in xmrig config"))? = .ok_or_else(|| anyhow!("pools/0/tls does not exist in xmrig config"))? =
@ -865,7 +857,7 @@ impl PrivXmrigApi {
.send() .send()
.await?; .await?;
// update process status // update process status
lock!(gui_api_xmrig).node = node.to_string(); lock!(pub_api_xmrig).node = node.to_string();
anyhow::Ok(()) anyhow::Ok(())
} }
} }

File diff suppressed because it is too large Load diff

358
src/helper/xvb/algorithm.rs Normal file
View file

@ -0,0 +1,358 @@
use std::{
sync::{Arc, Mutex},
time::Duration,
};
use log::{debug, info, warn};
use readable::num::Float;
use reqwest::Client;
use tokio::time::{sleep_until, Instant};
use crate::{
helper::{
p2pool::PubP2poolApi,
xmrig::{PrivXmrigApi, PubXmrigApi},
xvb::{nodes::XvbNode, output_console, output_console_without_time},
},
macros::lock,
BLOCK_PPLNS_WINDOW_MAIN, BLOCK_PPLNS_WINDOW_MINI, SECOND_PER_BLOCK_P2POOL, XMRIG_CONFIG_URI,
XVB_BUFFER, XVB_ROUND_DONOR_MEGA_MIN_HR, XVB_ROUND_DONOR_MIN_HR, XVB_ROUND_DONOR_VIP_MIN_HR,
XVB_ROUND_DONOR_WHALE_MIN_HR, XVB_TIME_ALGO,
};
use super::{PubXvbApi, SamplesAverageHour};
pub(crate) fn calcul_donated_time(
lhr: f32,
gui_api_p2pool: &Arc<Mutex<PubP2poolApi>>,
gui_api_xvb: &Arc<Mutex<PubXvbApi>>,
state_p2pool: &crate::disk::state::P2pool,
) -> u32 {
let p2pool_ehr = lock!(gui_api_p2pool).sidechain_ehr;
// what if ehr stay still for the next ten minutes ? mHR will augment every ten minutes because it thinks that oHR is decreasing.
//
let p2pool_ohr = p2pool_ehr
- calc_last_hour_avg_hash_rate(&lock!(gui_api_xvb).p2pool_sent_last_hour_samples);
let mut min_hr = minimum_hashrate_share(
lock!(gui_api_p2pool).p2pool_difficulty_u64,
state_p2pool.mini,
p2pool_ohr,
);
if min_hr.is_sign_negative() {
min_hr = 0.0;
}
debug!("Xvb Process | hr {}, min_hr: {} ", lhr, min_hr);
// numbers are divided by a thousands to print kH/s and not H/s
let msg_lhr = format!(
"{} kH/s local HR from Xmrig",
Float::from_3((lhr / 1000.0).into())
);
let msg_mhr = format!(
"{} kH/s minimum required local HR to keep a share in PPLNS window",
Float::from_3((min_hr / 1000.0).into())
);
let msg_ehr = format!(
"{} kH/s estimated sent the last hour for your address on p2pool, including this instance",
Float::from_3((p2pool_ehr / 1000.0).into())
);
output_console(gui_api_xvb, &msg_lhr);
output_console(gui_api_xvb, &msg_mhr);
output_console(gui_api_xvb, &msg_ehr);
// calculate how much time can be spared
let mut spared_time = time_that_could_be_spared(lhr, min_hr);
if spared_time > 0 {
// if not hero option
if !lock!(gui_api_xvb).stats_priv.runtime_hero_mode {
let xvb_chr = lock!(gui_api_xvb).stats_priv.donor_1hr_avg * 1000.0;
info!("current HR on XVB (last hour): {xvb_chr}");
let shr = calc_last_hour_avg_hash_rate(&lock!(gui_api_xvb).xvb_sent_last_hour_samples);
// calculate how much time needed to be spared to be in most round type minimum HR + buffer
spared_time = minimum_time_for_highest_accessible_round(spared_time, lhr, xvb_chr, shr);
}
}
if lock!(gui_api_xvb).stats_priv.runtime_hero_mode {
output_console(gui_api_xvb, "Hero mode is enabled for this decision");
}
spared_time
}
fn minimum_hashrate_share(difficulty: u64, mini: bool, ohr: f32) -> f32 {
let pws = if mini {
BLOCK_PPLNS_WINDOW_MINI
} else {
BLOCK_PPLNS_WINDOW_MAIN
};
((difficulty / (pws * SECOND_PER_BLOCK_P2POOL)) as f32 * XVB_BUFFER) - ohr
}
fn time_that_could_be_spared(hr: f32, min_hr: f32) -> u32 {
// percent of time minimum
let minimum_time_required_on_p2pool = XVB_TIME_ALGO as f32 / (hr / min_hr);
let spared_time = XVB_TIME_ALGO as f32 - minimum_time_required_on_p2pool;
// if less than 6 seconds, XMRig could hardly have the time to mine anything.
if spared_time >= 6.0 {
return spared_time as u32;
}
0
}
// spared time, local hr, current 1h average hr already mining on XvB, 1h average local HR sent on XvB.
fn minimum_time_for_highest_accessible_round(st: u32, lhr: f32, chr: f32, shr: f32) -> u32 {
let hr_for_xvb = (st as f32 / XVB_TIME_ALGO as f32) * lhr;
info!(
"hr for xvb is: ({st} / {}) * {lhr} = {hr_for_xvb}H/s",
XVB_TIME_ALGO
);
let ohr = chr - shr;
info!("ohr is: {chr} - {shr} = {ohr}H/s");
let min_mega = XVB_ROUND_DONOR_MEGA_MIN_HR as f32 - ohr;
info!(
"minimum required HR for mega round is: {} - {ohr} = {min_mega}H/s",
XVB_ROUND_DONOR_MEGA_MIN_HR
);
let min_whale = XVB_ROUND_DONOR_WHALE_MIN_HR as f32 - ohr;
info!(
"minimum required HR for whale round is: {} - {ohr} = {min_whale}H/s",
XVB_ROUND_DONOR_WHALE_MIN_HR
);
let min_donorvip = XVB_ROUND_DONOR_VIP_MIN_HR as f32 - ohr;
info!(
"minimum required HR for donor vip round is: {} - {ohr} = {min_donorvip}H/s",
XVB_ROUND_DONOR_VIP_MIN_HR
);
let min_donor = XVB_ROUND_DONOR_MIN_HR as f32 - ohr;
info!(
"minimum required HR for donor round is: {} - {ohr} = {min_donor}H/s",
XVB_ROUND_DONOR_MIN_HR
);
match hr_for_xvb {
x if x > min_mega => {
info!("trying to get Mega round");
info!(
"minimum second to send = ((({x} - ({x} - {min_mega})) / {lhr}) * {}) ",
XVB_TIME_ALGO
);
(((x - (x - min_mega)) / lhr) * XVB_TIME_ALGO as f32) as u32
}
x if x > min_whale => {
info!("trying to get Whale round");
info!(
"minimum second to send = ((({x} - ({x} - {min_whale})) / {lhr}) * {}) ",
XVB_TIME_ALGO
);
(((x - (x - min_whale)) / lhr) * XVB_TIME_ALGO as f32) as u32
}
x if x > min_donorvip => {
info!("trying to get Vip Donor round");
info!(
"minimum second to send = ((({x} - ({x} - {min_donorvip})) / {lhr}) * {}) ",
XVB_TIME_ALGO
);
(((x - (x - min_donorvip)) / lhr) * XVB_TIME_ALGO as f32) as u32
}
x if x > min_donor => {
info!("trying to get Donor round");
info!(
"minimum second to send = ((({x} - ({x} - {min_donor})) / {lhr}) * {}) ",
XVB_TIME_ALGO
);
(((x - (x - min_donor)) / lhr) * XVB_TIME_ALGO as f32) as u32
}
_ => 0,
}
}
#[allow(clippy::too_many_arguments)]
async fn sleep_then_update_node_xmrig(
was_instant: Instant,
spared_time: u32,
client: &Client,
api_uri: &str,
token_xmrig: &str,
address: &str,
gui_api_xvb: &Arc<Mutex<PubXvbApi>>,
gui_api_xmrig: &Arc<Mutex<PubXmrigApi>>,
) {
let node = lock!(gui_api_xvb).stats_priv.node.clone();
debug!(
"Xvb Process | algo sleep for {} while mining on P2pool",
XVB_TIME_ALGO - spared_time
);
sleep_until(was_instant + Duration::from_secs((XVB_TIME_ALGO - spared_time) as u64)).await;
// only update xmrig config if it is actually mining.
if spared_time > 0 {
debug!("Xvb Process | request xmrig to mine on XvB");
if lock!(gui_api_xvb).current_node.is_none()
|| lock!(gui_api_xvb)
.current_node
.as_ref()
.is_some_and(|n| n == &XvbNode::P2pool)
{
if let Err(err) = PrivXmrigApi::update_xmrig_config(
client,
api_uri,
token_xmrig,
&node,
address,
gui_api_xmrig,
)
.await
{
// show to console error about updating xmrig config
warn!("Xvb Process | Failed request HTTP API Xmrig");
output_console(
gui_api_xvb,
&format!(
"Failure to update xmrig config with HTTP API.\nError: {}",
err
),
);
} else {
debug!("Xvb Process | mining on XvB pool");
}
}
// will not quit the process until it is really done.
// xvb process watch this algo handle to see if process is finished or not.
sleep_until(was_instant + Duration::from_secs(spared_time.into())).await;
}
}
// push new value into samples before executing this calcul
fn calc_last_hour_avg_hash_rate(samples: &SamplesAverageHour) -> f32 {
samples.0.iter().sum::<f32>() / samples.0.len() as f32
}
#[allow(clippy::too_many_arguments)]
pub(crate) async fn algorithm(
client: &Client,
last_algorithm: Instant,
gui_api_xvb: &Arc<Mutex<PubXvbApi>>,
gui_api_xmrig: &Arc<Mutex<PubXmrigApi>>,
gui_api_p2pool: &Arc<Mutex<PubP2poolApi>>,
token_xmrig: &str,
state_p2pool: &crate::disk::state::P2pool,
share: u32,
time_donated: &mut u32,
) {
debug!("Xvb Process | Algorithm is started");
output_console(
gui_api_xvb,
"Algorithm of distribution HR started for the next ten minutes.",
);
// the time that takes the algorithm do decide the next ten minutes could means less p2pool mining. It is solved by the buffer and spawning requests.
let address = &state_p2pool.address;
// request XMrig to mine on P2pool
// if share is in PW,
if share > 0 {
debug!("Xvb Process | Algorithm share is in current window");
// calcul minimum HR
output_console(
gui_api_xvb,
"At least one share is in current PPLNS window.",
);
let hashrate_xmrig = {
if lock!(gui_api_xmrig).hashrate_raw_15m > 0.0 {
lock!(gui_api_xmrig).hashrate_raw_15m
} else if lock!(gui_api_xmrig).hashrate_raw_1m > 0.0 {
lock!(gui_api_xmrig).hashrate_raw_1m
} else {
lock!(gui_api_xmrig).hashrate_raw
}
};
*time_donated =
calcul_donated_time(hashrate_xmrig, gui_api_p2pool, gui_api_xvb, state_p2pool);
debug!("Xvb Process | Donated time {} ", time_donated);
output_console(
gui_api_xvb,
&format!(
"Mining on P2pool node for {} seconds then on XvB for {} seconds.",
XVB_TIME_ALGO - *time_donated,
time_donated
),
);
// p2pool need to be mined if donated time is not equal to xvb_time_algo
if *time_donated != XVB_TIME_ALGO
&& lock!(gui_api_xvb).current_node != Some(XvbNode::P2pool)
{
debug!("Xvb Process | request xmrig to mine on p2pool");
if let Err(err) = PrivXmrigApi::update_xmrig_config(
client,
XMRIG_CONFIG_URI,
token_xmrig,
&XvbNode::P2pool,
address,
gui_api_xmrig,
)
.await
{
warn!("Xvb Process | Failed request HTTP API Xmrig");
output_console(
gui_api_xvb,
&format!(
"Failure to update xmrig config with HTTP API.\nError: {}",
err
),
);
}
}
// sleep 10m less spared time then request XMrig to mine on XvB
sleep_then_update_node_xmrig(
last_algorithm,
*time_donated,
client,
XMRIG_CONFIG_URI,
token_xmrig,
address,
gui_api_xvb,
gui_api_xmrig,
)
.await;
lock!(gui_api_xvb)
.p2pool_sent_last_hour_samples
.0
.push_back(hashrate_xmrig * ((XVB_TIME_ALGO - *time_donated) / XVB_TIME_ALGO) as f32);
lock!(gui_api_xvb)
.xvb_sent_last_hour_samples
.0
.push_back(hashrate_xmrig * (*time_donated / XVB_TIME_ALGO) as f32);
} else {
// no share, so we mine on p2pool. We update xmrig only if it was still mining on XvB.
if lock!(gui_api_xvb).current_node != Some(XvbNode::P2pool) {
info!("Xvb Process | request xmrig to mine on p2pool");
if let Err(err) = PrivXmrigApi::update_xmrig_config(
client,
XMRIG_CONFIG_URI,
token_xmrig,
&XvbNode::P2pool,
address,
gui_api_xmrig,
)
.await
{
warn!("Xvb Process | Failed request HTTP API Xmrig");
output_console(
gui_api_xvb,
&format!(
"Failure to update xmrig config with HTTP API.\nError: {}",
err
),
);
}
}
output_console(gui_api_xvb, "No share in the current PPLNS Window !");
output_console(gui_api_xvb, "Mining on P2pool for the next ten minutes.");
sleep_until(last_algorithm + Duration::from_secs(XVB_TIME_ALGO.into())).await;
lock!(gui_api_xvb)
.p2pool_sent_last_hour_samples
.0
.push_back(lock!(gui_api_xmrig).hashrate_raw_15m);
lock!(gui_api_xvb)
.p2pool_sent_last_hour_samples
.0
.push_back(0.0);
}
// algorithm has run, so do not retry but run normally
// put a space to mark the difference with the next run.
output_console_without_time(gui_api_xvb, "");
}

735
src/helper/xvb/mod.rs Normal file
View file

@ -0,0 +1,735 @@
use crate::helper::xvb::algorithm::algorithm;
use crate::helper::xvb::priv_stats::XvbPrivStats;
use crate::helper::xvb::public_stats::XvbPubStats;
use bounded_vec_deque::BoundedVecDeque;
use enclose::enc;
use log::{debug, error, info, warn};
use readable::up::Uptime;
use reqwest::Client;
use std::fmt::Write;
use std::mem;
use std::time::Duration;
use std::{
sync::{Arc, Mutex},
thread,
};
use tokio::spawn;
use tokio::task::JoinHandle;
use tokio::time::{sleep, Instant};
use crate::helper::xmrig::PrivXmrigApi;
use crate::helper::xvb::rounds::round_type;
use crate::utils::constants::{XMRIG_CONFIG_URI, XVB_PUBLIC_ONLY, XVB_TIME_ALGO};
use crate::{
helper::{ProcessSignal, ProcessState},
utils::macros::{lock, lock2, sleep},
};
use self::nodes::XvbNode;
use super::p2pool::PubP2poolApi;
use super::xmrig::PubXmrigApi;
use super::{Helper, Process};
pub mod algorithm;
pub mod nodes;
pub mod priv_stats;
pub mod public_stats;
pub mod rounds;
impl Helper {
// Just sets some signals for the watchdog thread to pick up on.
pub fn stop_xvb(helper: &Arc<Mutex<Self>>) {
info!("XvB | Attempting to stop...");
lock2!(helper, xvb).signal = ProcessSignal::Stop;
lock2!(helper, xvb).state = ProcessState::Middle;
}
pub fn restart_xvb(
helper: &Arc<Mutex<Self>>,
state_xvb: &crate::disk::state::Xvb,
state_p2pool: &crate::disk::state::P2pool,
state_xmrig: &crate::disk::state::Xmrig,
) {
info!("XvB | Attempting to restart...");
lock2!(helper, xvb).signal = ProcessSignal::Restart;
lock2!(helper, xvb).state = ProcessState::Middle;
let helper = helper.clone();
let state_xvb = state_xvb.clone();
let state_p2pool = state_p2pool.clone();
let state_xmrig = state_xmrig.clone();
// This thread lives to wait, start xmrig then die.
thread::spawn(move || {
while lock2!(helper, xvb).state != ProcessState::Waiting {
warn!("XvB | Want to restart but process is still alive, waiting...");
sleep!(1000);
}
// Ok, process is not alive, start the new one!
info!("XvB | Old process seems dead, starting new one!");
Self::start_xvb(&helper, &state_xvb, &state_p2pool, &state_xmrig);
});
info!("XMRig | Restart ... OK");
}
pub fn start_xvb(
helper: &Arc<Mutex<Self>>,
state_xvb: &crate::disk::state::Xvb,
state_p2pool: &crate::disk::state::P2pool,
state_xmrig: &crate::disk::state::Xmrig,
) {
// 1. Clone Arc value from Helper
// pub for writing new values that will show up on UI after helper thread update. (every seconds.)
// gui for reading values from other thread and writing directly without waiting one second (terminal output).
// if only gui was used, values would update on UI at different time which is not a good user experience.
info!("XvB | cloning helper arc fields");
// without xmrig alive, it doesn't make sense to use XvB.
// needed to see if it is alive. For XvB process to function completely, p2pool node must be alive to check the shares in the pplns window.
let gui_api = Arc::clone(&lock!(helper).gui_api_xvb);
let pub_api = Arc::clone(&lock!(helper).pub_api_xvb);
let process = Arc::clone(&lock!(helper).xvb);
let process_p2pool = Arc::clone(&lock!(helper).p2pool);
let gui_api_p2pool = Arc::clone(&lock!(helper).gui_api_p2pool);
let process_xmrig = Arc::clone(&lock!(helper).xmrig);
let gui_api_xmrig = Arc::clone(&lock!(helper).gui_api_xmrig);
let pub_api_xmrig = Arc::clone(&lock!(helper).pub_api_xmrig);
// Reset before printing to output.
// Need to reset because values of stats would stay otherwise which could bring confusion even if panel is with a disabled theme.
// at the start of a process, values must be default.
info!(
"XvB | resetting pub and gui but keep current node as it is updated by xmrig console."
);
reset_data_xvb(&pub_api, &gui_api);
// we reset the console output because it is complete start.
lock!(gui_api).output.clear();
// 2. Set process state
// XvB has not yet decided if it can continue.
// it could fail if XvB server is offline or start partially if token/address is invalid or if p2pool or xmrig are offline.
// this state will be received accessed by the UI directly and put the status on yellow.
info!("XvB | Setting process state...");
{
let mut lock = lock!(process);
lock.state = ProcessState::Middle;
lock.signal = ProcessSignal::None;
lock.start = std::time::Instant::now();
}
// verify if token and address are existent on XvB server
info!("XvB | spawn watchdog");
thread::spawn(enc!((state_xvb, state_p2pool, state_xmrig) move || {
Self::spawn_xvb_watchdog(
&gui_api,
&pub_api,
&process,
&state_xvb,
&state_p2pool,
&state_xmrig,
&gui_api_p2pool,
&process_p2pool,
&gui_api_xmrig,
&pub_api_xmrig,
&process_xmrig,
);
}));
}
// need the helper so we can restart the thread after getting a signal not caused by a restart.
#[allow(clippy::too_many_arguments)]
#[tokio::main]
async fn spawn_xvb_watchdog(
gui_api: &Arc<Mutex<PubXvbApi>>,
pub_api: &Arc<Mutex<PubXvbApi>>,
process: &Arc<Mutex<Process>>,
state_xvb: &crate::disk::state::Xvb,
state_p2pool: &crate::disk::state::P2pool,
state_xmrig: &crate::disk::state::Xmrig,
gui_api_p2pool: &Arc<Mutex<PubP2poolApi>>,
process_p2pool: &Arc<Mutex<Process>>,
gui_api_xmrig: &Arc<Mutex<PubXmrigApi>>,
pub_api_xmrig: &Arc<Mutex<PubXmrigApi>>,
process_xmrig: &Arc<Mutex<Process>>,
) {
// create uniq client that is going to be used for during the life of the thread.
let client = reqwest::Client::new();
// checks confition to start XvB, will set proper state of XvB.
// if state is middle (everything fine here),set which xvb node could be used.
// should wait for it, because algo needs to not be started if at least one node of XvB are not responsive.
// if no node respond, state will be AllNodeOffline.
// state could be offline nodes or alive at this point
// if offlines nodes, state must not let private api and algo run. only public info, so state must not be alive.
// in that case, a spawn would retry and change state if they are available again.
check_conditions_for_start(
&client,
gui_api,
process_p2pool,
process_xmrig,
process,
state_p2pool,
state_xvb,
)
.await;
// uptime for log of signal check ?
let start = lock!(process).start;
// uptime of last run of algo
let last_algorithm = Arc::new(Mutex::new(tokio::time::Instant::now()));
// uptime of last request (public and private)
let mut last_request = tokio::time::Instant::now();
// algo check if his behavior must be like the first time or second time. It can reset those values to re-act like first time even if it's not the case.
let mut first_loop = true;
// retry will be accessed from the 1m spawn, it can influence the start of algo.
let retry = Arc::new(Mutex::new(false));
// time donated by algorithm. With being persistent across loop, we can construct the indicator.
let mut time_donated = 0;
// let handles;
let handle_algo = Arc::new(Mutex::new(None));
let mut handle_request = None;
let mut msg_retry_done = false;
info!("XvB | Entering Process mode... ");
loop {
debug!("XvB Watchdog | ----------- Start of loop -----------");
// Set timer of loop
let start_loop = Instant::now();
// verify if p2pool and xmrig are running, else XvB must be reloaded with another token/address to start verifying the other process.
check_state_outcauses_xvb(
&client,
gui_api,
pub_api,
process,
process_xmrig,
process_p2pool,
&mut first_loop,
&handle_algo,
pub_api_xmrig,
state_p2pool,
state_xmrig,
);
// check signal
debug!("XvB | check signal");
if signal_interrupt(
process,
start.into(),
&client,
pub_api,
gui_api,
gui_api_xmrig,
state_p2pool,
state_xmrig,
) {
info!("XvB Watchdog | Signal has stopped the loop");
break;
}
let handle = lock!(handle_algo);
let is_algo_started_once = handle.is_some();
let is_algo_finished = handle.as_ref().is_some_and(|algo| algo.is_finished());
drop(handle);
// Send an HTTP API request only if one minute is passed since the last request or if first loop or if algorithm need to retry or if request is finished and algo is finished or almost finished (only public and private stats). We make sure public and private stats are refreshed before doing another run of the algo.
// We make sure algo or request are not rerun when they are not over.
if last_request.elapsed() >= Duration::from_secs(60)
|| first_loop
|| *lock!(retry)
|| ((is_algo_finished
|| lock!(last_algorithm).elapsed()
>= Duration::from_secs((XVB_TIME_ALGO as f32 * 0.95) as u64))
&& (handle_request
.as_ref()
.is_some_and(|req: &JoinHandle<()>| req.is_finished())
|| handle_request.is_none()))
{
// do not wait for the request to finish so that they are retrieved at exactly one minute interval and not block the thread.
// Private API will also use this instant if XvB is Alive.
last_request = tokio::time::Instant::now();
// first_loop is false here but could be changed to true under some conditions.
// will send a stop signal if public stats failed or update data with new one.
handle_request = Some(spawn(
enc!((client, pub_api, gui_api, gui_api_p2pool, gui_api_xmrig, state_xvb, state_p2pool, state_xmrig, process, last_algorithm, retry, handle_algo) async move {
// needs to wait here for public stats to get private stats.
if last_request.elapsed() >= Duration::from_secs(60) || first_loop || lock!(last_algorithm).elapsed() >= Duration::from_secs((XVB_TIME_ALGO as f32 * 0.95)as u64) {
XvbPubStats::update_stats(&client, &gui_api, &pub_api, &process).await;
}
// private stats needs valid token and address.
// other stats needs everything to be alive, so just require alive here for now.
// maybe later differentiate to add a way to get private stats without running the algo ?
if lock!(process).state == ProcessState::Alive {
// get current share to know if we are in a round and this is a required data for algo.
let share = lock!(gui_api_p2pool).sidechain_shares;
debug!("XvB | Number of current shares: {}", share);
// private stats can be requested every minute or first loop or if the have almost finished.
if last_request.elapsed() >= Duration::from_secs(60) || first_loop || lock!(last_algorithm).elapsed() >= Duration::from_secs((XVB_TIME_ALGO as f32 * 0.95)as u64) {
debug!("XvB Watchdog | Attempting HTTP private API request...");
// reload private stats, it send a signal if error that will be captured on the upper thread.
XvbPrivStats::update_stats(
&client, &state_p2pool.address, &state_xvb.token, &pub_api, &gui_api, &process,
)
.await;
// verify in which round type we are
let round = round_type(share, &pub_api);
// refresh the round we participate in.
debug!("XvB | Round type: {:#?}", round);
lock!(pub_api).stats_priv.round_participate = round;
// verify if we are the winner of the current round
if lock!(pub_api).stats_pub.winner
== Helper::head_tail_of_monero_address(&state_p2pool.address).as_str()
{
lock!(pub_api).stats_priv.win_current = true
}
}
if (first_loop || *lock!(retry)|| is_algo_finished) && lock!(gui_api_xmrig).hashrate_raw > 0.0
{
// if algo was started, it must not retry next loop.
*lock!(retry) = false;
// reset instant because algo will start.
*lock!(last_algorithm) = Instant::now();
// send the instant that will be consumed by algo. Algo does not modify it.
let last_algorithm = *lock!(last_algorithm);
*lock!(handle_algo) = Some(spawn(enc!((client, gui_api, gui_api_xmrig, state_xmrig) async move {
algorithm(
&client,
last_algorithm,
&gui_api,
&gui_api_xmrig,
&gui_api_p2pool,
&state_xmrig.token,
&state_p2pool,
share,
&mut time_donated,
).await;
})));
} else {
// if xmrig is still at 0 HR but is alive and algorithm is skipped, recheck first 10s of xmrig inside algorithm next time (in one minute)
if lock!(gui_api_xmrig).hashrate_raw == 0.0 {
*lock!(retry) = true
}
}
}
}),
));
}
// if retry is false, next time the message about waiting for xmrig HR can be shown.
if !*lock!(retry) {
msg_retry_done = false;
}
// inform user that algorithm has not yet started because it is waiting for xmrig HR.
// show this message only once before the start of algo
if *lock!(retry) && !msg_retry_done {
output_console(
gui_api,
"Algorithm is waiting for 10 seconds average HR of XMRig.",
);
msg_retry_done = true;
}
// update indicator (time before switch and mining location) in private stats
// if algo not running, second message.
// will update countdown every second.
// verify current node which is set by algo or circonstances (failed node).
// verify given time set by algo and start time of current algo.
// will run only if XvB is alive.
// let algo time to start, so no countdown is shown.
update_indicator_algo(
is_algo_started_once,
is_algo_finished,
process,
pub_api,
time_donated,
&last_algorithm,
);
// first_loop is done, but maybe retry will allow the algorithm to retry again.
if first_loop {
first_loop = false;
}
// Sleep (only if 900ms hasn't passed)
let elapsed = start_loop.elapsed().as_millis();
// Since logic goes off if less than 1000, casting should be safe
if elapsed < 999 {
let sleep = (999 - elapsed) as u64;
debug!("XvB Watchdog | END OF LOOP - Sleeping for [{}]s...", sleep);
std::thread::sleep(std::time::Duration::from_millis(sleep))
} else {
debug!("XMRig Watchdog | END OF LOOP - Not sleeping!");
}
}
}
}
//---------------------------------------------------------------------------------------------------- Public XvB API
#[derive(Debug, Clone, Default)]
pub struct PubXvbApi {
pub output: String,
pub uptime: u64,
pub xvb_sent_last_hour_samples: SamplesAverageHour,
pub p2pool_sent_last_hour_samples: SamplesAverageHour,
pub stats_pub: XvbPubStats,
pub stats_priv: XvbPrivStats,
// where xmrig is mining right now (or trying to).
// will be updated by output of xmrig.
// could also be retrieved by fetching current config.
pub current_node: Option<XvbNode>,
}
#[derive(Debug, Clone)]
pub struct SamplesAverageHour(BoundedVecDeque<f32>);
impl Default for SamplesAverageHour {
fn default() -> Self {
let capacity = (3600 / XVB_TIME_ALGO) as usize;
let mut vec = BoundedVecDeque::new(capacity);
for _ in 0..capacity {
vec.push_back(0.0f32);
}
SamplesAverageHour(vec)
}
}
impl PubXvbApi {
pub fn new() -> Self {
Self::default()
}
// The issue with just doing [gui_api = pub_api] is that values get overwritten.
// This doesn't matter for any of the values EXCEPT for the output, so we must
// manually append it instead of overwriting.
// This is used in the "helper" thread.
pub(super) fn combine_gui_pub_api(gui_api: &mut Self, pub_api: &mut Self) {
let mut output = std::mem::take(&mut gui_api.output);
let buf = std::mem::take(&mut pub_api.output);
if !buf.is_empty() {
output.push_str(&buf);
}
let runtime_hero_mode = std::mem::take(&mut gui_api.stats_priv.runtime_hero_mode);
*gui_api = Self {
output,
stats_priv: XvbPrivStats {
runtime_hero_mode,
..pub_api.stats_priv.clone()
},
p2pool_sent_last_hour_samples: std::mem::take(
&mut gui_api.p2pool_sent_last_hour_samples,
),
xvb_sent_last_hour_samples: std::mem::take(&mut gui_api.xvb_sent_last_hour_samples),
..pub_api.clone()
};
}
}
async fn check_conditions_for_start(
client: &Client,
gui_api: &Arc<Mutex<PubXvbApi>>,
process_p2pool: &Arc<Mutex<Process>>,
process_xmrig: &Arc<Mutex<Process>>,
process_xvb: &Arc<Mutex<Process>>,
state_p2pool: &crate::disk::state::P2pool,
state_xvb: &crate::disk::state::Xvb,
) {
let state = if let Err(err) =
XvbPrivStats::request_api(client, &state_p2pool.address, &state_xvb.token).await
{
info!("XvB | verify address and token");
// send to console: token non existent for address on XvB server
warn!("Xvb | Start ... Partially failed because token and associated address are not existent on XvB server: {}\n", err);
output_console(gui_api, &format!("Token and associated address are not valid on XvB API.\nCheck if you are registered.\nError: {}", err));
ProcessState::NotMining
} else if lock!(process_p2pool).state != ProcessState::Alive {
info!("XvB | verify p2pool node");
// send to console: p2pool process is not running
warn!("Xvb | Start ... Partially failed because P2pool instance is not ready.");
let msg = if lock!(process_p2pool).state == ProcessState::Syncing {
"P2pool process is not ready.\nCheck the P2pool Tab"
} else {
"P2pool process is not running.\nCheck the P2pool Tab"
};
output_console(gui_api, msg);
ProcessState::Syncing
} else if lock!(process_xmrig).state != ProcessState::Alive {
// send to console: p2pool process is not running
warn!("Xvb | Start ... Partially failed because Xmrig instance is not running.");
// output the error to console
output_console(
gui_api,
"XMRig process is not running.\nCheck the Xmrig Tab.",
);
ProcessState::Syncing
} else {
// all test passed, so it can be Alive
info!("XvB will ping nodes");
// stay at middle, updateNodes will finish by syncing or offlinenodes and check_status in loop will change state accordingly.
ProcessState::Middle
};
if state != ProcessState::Middle {
// while waiting for xmrig and p2pool or getting right address/token, it can get public stats
info!("XvB | print to console state");
output_console(
gui_api,
&["XvB partially started.\n", XVB_PUBLIC_ONLY].concat(),
);
}
// will update the preferred node for the first loop, even if partially started.
lock!(process_xvb).signal = ProcessSignal::UpdateNodes;
lock!(process_xvb).state = state;
}
#[allow(clippy::too_many_arguments)]
fn check_state_outcauses_xvb(
client: &Client,
gui_api: &Arc<Mutex<PubXvbApi>>,
pub_api: &Arc<Mutex<PubXvbApi>>,
process: &Arc<Mutex<Process>>,
process_xmrig: &Arc<Mutex<Process>>,
process_p2pool: &Arc<Mutex<Process>>,
first_loop: &mut bool,
handle_algo: &Arc<Mutex<Option<JoinHandle<()>>>>,
pub_api_xmrig: &Arc<Mutex<PubXmrigApi>>,
state_p2pool: &crate::disk::state::P2pool,
state_xmrig: &crate::disk::state::Xmrig,
) {
// will check if the state can stay as it is.
// p2pool and xmrig are alive if ready and running (syncing is not alive).
let state = lock!(process).state;
// if state is not alive, the algo should stop if it was running and p2pool should be used by xmrig.
if let Some(handle) = lock!(handle_algo).as_ref() {
if state != ProcessState::Alive && !handle.is_finished() {
handle.abort();
output_console(
gui_api,
"XvB process can not completely continue, algorithm of distribution of HR is stopped.",
);
// only update xmrig if it is alive and wasn't on p2pool already.
if lock!(gui_api).current_node != Some(XvbNode::P2pool)
&& lock!(process_xmrig).state == ProcessState::Alive
{
let token_xmrig = state_xmrig.token.clone();
let address = state_p2pool.address.clone();
spawn(enc!((client, pub_api_xmrig, gui_api) async move {
if let Err(err) = PrivXmrigApi::update_xmrig_config(
&client,
XMRIG_CONFIG_URI,
&token_xmrig,
&XvbNode::P2pool,
&address,
&pub_api_xmrig,
)
.await
{
// show to console error about updating xmrig config
output_console(
&gui_api,
&format!(
"Failure to update xmrig config with HTTP API.\nError: {}",
err
),
);
} else {
output_console(
&gui_api,
&format!("XvB process can not completely continue, falling back to {}", XvbNode::P2pool),
);
}
}));
}
}
}
let is_xmrig_alive = lock!(process_xmrig).state == ProcessState::Alive;
let is_p2pool_alive = lock!(process_p2pool).state == ProcessState::Alive;
let p2pool_xmrig_alive = is_xmrig_alive && is_p2pool_alive;
// if state is middle because start is not finished yet, it will not do anything.
match state {
ProcessState::Alive if !p2pool_xmrig_alive => {
// they are not both alives, so state will be at syncing and data reset, state of loop also.
info!("XvB | stopped partially because XvB Nodes are not reachable.");
// stats must be empty put to default so the UI reflect that XvB private is not running.
reset_data_xvb(pub_api, gui_api);
// request from public API must be executed at next loop, do not wait for 1 minute.
*first_loop = true;
output_console(
gui_api,
"XvB is now partially stopped because p2pool node or xmrig came offline.\nCheck P2pool and Xmrig Tabs",
);
output_console(gui_api, XVB_PUBLIC_ONLY);
lock!(process).state = ProcessState::Syncing;
}
ProcessState::Syncing if p2pool_xmrig_alive => {
info!("XvB | started this time with p2pool and xmrig");
// will put state on middle and update nodes
lock!(process).state = ProcessState::Alive;
reset_data_xvb(pub_api, gui_api);
*first_loop = true;
output_console(
gui_api,
"XvB is now started because p2pool and xmrig came online.",
);
}
// nothing to do, we don't want to change other state
_ => {}
};
}
#[allow(clippy::too_many_arguments)]
fn signal_interrupt(
process: &Arc<Mutex<Process>>,
start: Instant,
client: &Client,
pub_api: &Arc<Mutex<PubXvbApi>>,
gui_api: &Arc<Mutex<PubXvbApi>>,
gui_api_xmrig: &Arc<Mutex<PubXmrigApi>>,
state_p2pool: &crate::disk::state::P2pool,
state_xmrig: &crate::disk::state::Xmrig,
) -> bool {
// Check SIGNAL
// check if STOP or RESTART Signal is given.
// if STOP, will put Signal to None, if Restart to Wait
// in either case, will break from loop.
if lock!(process).signal == ProcessSignal::Stop {
debug!("P2Pool Watchdog | Stop SIGNAL caught");
// Wait to get the exit status
let uptime = start.elapsed();
info!(
"Xvb Watchdog | Stopped ... Uptime was: [{}]",
Uptime::from(uptime)
);
// insert the signal into output of XvB
// This is written directly into the GUI API, because sometimes the 900ms event loop can't catch it.
output_console(gui_api, "\n\n\nXvB stopped\n\n\n");
debug!("XvB Watchdog | Stop SIGNAL done, breaking");
lock!(process).signal = ProcessSignal::None;
lock!(process).state = ProcessState::Dead;
// reset stats
reset_data_xvb(pub_api, gui_api);
return true;
// Check RESTART
} else if lock!(process).signal == ProcessSignal::Restart {
debug!("XvB Watchdog | Restart SIGNAL caught");
let uptime = Uptime::from(start.elapsed());
info!("XvB Watchdog | Stopped ... Uptime was: [{}]", uptime);
// no output to console because service will be started with fresh output.
debug!("XvB Watchdog | Restart SIGNAL done, breaking");
lock!(process).state = ProcessState::Waiting;
reset_data_xvb(pub_api, gui_api);
return true;
// Check UPDATE NODES
} else if lock!(process).signal == ProcessSignal::UpdateNodes
&& lock!(process).state != ProcessState::Waiting
{
info!("XvB Watchdog | Signal has been given to ping and reselect Nodes.");
// if signal is waiting, he is restarting or already updating nodes.
// need to know if it was starting xvb
// A signal has been given to ping the nodes and select the fastest.
let token_xmrig = state_xmrig.token.clone();
let address = state_p2pool.address.clone();
let alive = lock!(process).state == ProcessState::Alive;
// so it won't execute another signal of update nodes if it is already doing it.
lock!(process).state = ProcessState::Waiting;
lock!(process).signal = ProcessSignal::None;
spawn(
enc!((gui_api, pub_api, gui_api_xmrig, client, process) async move {
// if nodes die while being used by xmrig, it needs see which one is capable, send a signal if none is useable and switch xmrig to the good node (or p2pool if none).
// update nodes will make the state Syncing, and the loop of the thread will detected if it can be made alive.
XvbNode::update_fastest_node(&client, &gui_api, &pub_api, &process).await;
// only update xmrig if state is alive.
if alive {
let node = lock!(gui_api).stats_priv.node.clone();
if let Err(err) = PrivXmrigApi::update_xmrig_config(
&client,
XMRIG_CONFIG_URI,
&token_xmrig,
&node,
&address,
&gui_api_xmrig,
)
.await
{
// show to console error about updating xmrig config
output_console(
&gui_api,
&format!(
"Failure to update xmrig config with HTTP API.\nError: {}",
err
),
);
} else {
output_console(
&gui_api,
&format!("XvB node failed, falling back to {}", node),
);
}
}
// verify if nodes were joignable.
if lock!(process).state == ProcessState::OfflineNodesAll {
// if state did not change after updating fastest node, it means all xvb nodes failed.
// we need to spawn a process to verify periodiccly until the nodes are online. The service state will be Alive then.
sleep(Duration::from_secs(10)).await;
info!("node fail, set spawn that will retry nodes and update state.");
while lock!(process).state == ProcessState::OfflineNodesAll {
// this spawn will stay alive until nodes are joignable or XvB process is stopped or failed.
XvbNode::update_fastest_node(&client, &pub_api, &gui_api, &process).await;
sleep(Duration::from_secs(10)).await;
}
}
}),
);
// the state will be Offline or Alive after update_fastest_node is done, meanwhile Signal will be None so not re-treated before update_fastest is done.
// so if a backup was used, it will be alive. If not, the algorithm stop and xmrig is updated to mine on p2pool.
}
false
}
fn reset_data_xvb(pub_api: &Arc<Mutex<PubXvbApi>>, gui_api: &Arc<Mutex<PubXvbApi>>) {
let current_node = mem::take(&mut lock!(pub_api).current_node.clone());
let runtime_hero_mode = mem::take(&mut lock!(gui_api).stats_priv.runtime_hero_mode);
// let output = mem::take(&mut lock!(gui_api).output);
*lock!(pub_api) = PubXvbApi::new();
*lock!(gui_api) = PubXvbApi::new();
// to keep the value modified by xmrig even if xvb is dead.
lock!(pub_api).current_node = current_node;
// to not loose the information of runtime hero mode between restart
lock!(gui_api).stats_priv.runtime_hero_mode = runtime_hero_mode;
// message while starting must be preserved.
// lock!(pub_api).output = output;
}
// print date time to console output in same format than xmrig
use chrono::Local;
fn datetimeonsole() -> String {
format!("[{}] ", Local::now().format("%Y-%m-%d %H:%M:%S%.3f"))
}
pub fn output_console(gui_api: &Arc<Mutex<PubXvbApi>>, msg: &str) {
if let Err(e) = writeln!(lock!(gui_api).output, "{}{msg}", datetimeonsole()) {
error!("XvB Watchdog | GUI status write failed: {}", e);
}
}
pub fn output_console_without_time(gui_api: &Arc<Mutex<PubXvbApi>>, msg: &str) {
if let Err(e) = writeln!(lock!(gui_api).output, "{msg}") {
error!("XvB Watchdog | GUI status write failed: {}", e);
}
}
fn update_indicator_algo(
is_algo_started_once: bool,
is_algo_finished: bool,
process: &Arc<Mutex<Process>>,
pub_api: &Arc<Mutex<PubXvbApi>>,
time_donated: u32,
last_algorithm: &Arc<Mutex<Instant>>,
) {
if is_algo_started_once && !is_algo_finished && lock!(process).state == ProcessState::Alive {
let node = lock!(pub_api).current_node.clone();
let msg_indicator = match node {
Some(XvbNode::P2pool) if time_donated > 0 => {
// algo is mining on p2pool but will switch to XvB after
// show time remaining on p2pool
lock!(pub_api).stats_priv.time_switch_node = XVB_TIME_ALGO
- last_algorithm.lock().unwrap().elapsed().as_secs() as u32
- time_donated;
"time until switch to mining on XvB".to_string()
}
_ => {
// algo is mining on XvB or complelty mining on p2pool.
// show remaining time before next decision of algo
// because time of last algorithm could depass a little bit XVB_TIME_ALGO before next run, check the sub.
lock!(pub_api).stats_priv.time_switch_node = XVB_TIME_ALGO
.checked_sub(last_algorithm.lock().unwrap().elapsed().as_secs() as u32)
.unwrap_or_default();
"time until next decision of algorithm".to_string()
}
};
lock!(pub_api).stats_priv.msg_indicator = msg_indicator;
} else {
// if algo is not running or process not alive
lock!(pub_api).stats_priv.time_switch_node = 0;
lock!(pub_api).stats_priv.msg_indicator = "Algorithm is not running".to_string();
}
}

158
src/helper/xvb/nodes.rs Normal file
View file

@ -0,0 +1,158 @@
use std::{
sync::{Arc, Mutex},
time::{Duration, Instant},
};
use derive_more::Display;
use log::{error, info, warn};
use reqwest::Client;
use tokio::spawn;
use crate::{
components::node::{GetInfo, TIMEOUT_NODE_PING},
helper::{xvb::output_console, Process, ProcessState},
macros::lock,
GUPAX_VERSION_UNDERSCORE, XVB_NODE_EU, XVB_NODE_NA, XVB_NODE_PORT, XVB_NODE_RPC,
};
use super::PubXvbApi;
#[derive(Clone, Debug, Default, PartialEq, Display)]
pub enum XvbNode {
#[display(fmt = "XvB North America Node")]
NorthAmerica,
#[default]
#[display(fmt = "XvB European Node")]
Europe,
#[display(fmt = "Local P2pool")]
P2pool,
}
impl XvbNode {
pub fn url(&self) -> String {
match self {
Self::NorthAmerica => String::from(XVB_NODE_NA),
Self::Europe => String::from(XVB_NODE_EU),
Self::P2pool => String::from("127.0.0.1"),
}
}
pub fn port(&self) -> String {
match self {
Self::NorthAmerica | Self::Europe => String::from(XVB_NODE_PORT),
Self::P2pool => String::from("3333"),
}
}
pub fn user(&self, address: &str) -> String {
match self {
Self::NorthAmerica => address.chars().take(8).collect(),
Self::Europe => address.chars().take(8).collect(),
Self::P2pool => GUPAX_VERSION_UNDERSCORE.to_string(),
}
}
pub fn tls(&self) -> bool {
match self {
Self::NorthAmerica => true,
Self::Europe => true,
Self::P2pool => false,
}
}
pub fn keepalive(&self) -> bool {
match self {
Self::NorthAmerica => true,
Self::Europe => true,
Self::P2pool => false,
}
}
pub async fn update_fastest_node(
client: &Client,
pub_api_xvb: &Arc<Mutex<PubXvbApi>>,
gui_api_xvb: &Arc<Mutex<PubXvbApi>>,
process_xvb: &Arc<Mutex<Process>>,
) {
let client_eu = client.clone();
let client_na = client.clone();
// two spawn to ping the two nodes in parallel and not one after the other.
let ms_eu = spawn(async move { XvbNode::ping(&XvbNode::Europe.url(), &client_eu).await });
let ms_na =
spawn(async move { XvbNode::ping(&XvbNode::NorthAmerica.url(), &client_na).await });
let node = if let Ok(ms_eu) = ms_eu.await {
if let Ok(ms_na) = ms_na.await {
// if two nodes are up, compare ping latency and return fastest.
if ms_na != TIMEOUT_NODE_PING && ms_eu != TIMEOUT_NODE_PING {
if ms_na < ms_eu {
XvbNode::NorthAmerica
} else {
XvbNode::Europe
}
} else if ms_na != TIMEOUT_NODE_PING && ms_eu == TIMEOUT_NODE_PING {
// if only na is online, return it.
XvbNode::NorthAmerica
} else if ms_na == TIMEOUT_NODE_PING && ms_eu != TIMEOUT_NODE_PING {
// if only eu is online, return it.
XvbNode::Europe
} else {
// if P2pool is returned, it means none of the two nodes are available.
XvbNode::P2pool
}
} else {
error!("ping has failed !");
XvbNode::P2pool
}
} else {
error!("ping has failed !");
XvbNode::P2pool
};
if node == XvbNode::P2pool {
// if both nodes are dead, then the state of the process must be NodesOffline
info!("XvB node ping, all offline or ping failed, switching back to local p2pool",);
output_console(
gui_api_xvb,
"XvB node ping, all offline or ping failed, switching back to local p2pool",
);
lock!(process_xvb).state = ProcessState::OfflineNodesAll;
} else {
// if node is up and because update_fastest is used only if token/address is valid, it means XvB process is Alive.
info!("XvB node ping, both online and best is {}", node.url());
output_console(
gui_api_xvb,
&format!("XvB node ping, {} is selected as the fastest.", node),
);
info!("ProcessState to Syncing after finding joignable node");
// could be used by xmrig who signal that a node is not joignable
// or by the start of xvb
// next iteration of the loop of XvB process will verify if all conditions are met to be alive.
lock!(process_xvb).state = ProcessState::Syncing;
}
lock!(pub_api_xvb).stats_priv.node = node;
}
async fn ping(ip: &str, client: &Client) -> u128 {
let request = client
.post("http://".to_string() + ip + ":" + XVB_NODE_RPC + "/json_rpc")
.body(r#"{"jsonrpc":"2.0","id":"0","method":"get_info"}"#);
let ms;
let now = Instant::now();
match tokio::time::timeout(Duration::from_secs(8), request.send()).await {
Ok(Ok(json_rpc)) => {
// Attempt to convert to JSON-RPC.
match json_rpc.bytes().await {
Ok(b) => match serde_json::from_slice::<GetInfo<'_>>(&b) {
Ok(rpc) => {
if rpc.result.mainnet && rpc.result.synchronized {
ms = now.elapsed().as_millis();
} else {
ms = TIMEOUT_NODE_PING;
warn!("Ping | {ip} responded with valid get_info but is not in sync, remove this node!");
}
}
_ => {
ms = TIMEOUT_NODE_PING;
warn!("Ping | {ip} responded but with invalid get_info, remove this node!");
}
},
_ => ms = TIMEOUT_NODE_PING,
};
}
_ => ms = TIMEOUT_NODE_PING,
};
ms
}
}

View file

@ -0,0 +1,97 @@
use std::sync::{Arc, Mutex};
use anyhow::bail;
use log::{debug, error, warn};
use reqwest::{Client, StatusCode};
use serde::Deserialize;
use crate::{
helper::{xvb::output_console, Process, ProcessSignal, ProcessState},
macros::lock,
XVB_URL,
};
use super::{nodes::XvbNode, rounds::XvbRound, PubXvbApi};
#[derive(Debug, Clone, Default, Deserialize)]
pub struct XvbPrivStats {
pub fails: u8,
pub donor_1hr_avg: f32,
pub donor_24hr_avg: f32,
#[serde(skip)]
pub win_current: bool,
#[serde(skip)]
pub round_participate: Option<XvbRound>,
#[serde(skip)]
pub node: XvbNode,
#[serde(skip)]
// it is the time remaining before switching from P2pool to XvB or XvB to P2ool.
// it is not the time remaining of the algo, even if it could be the same if never mining on XvB.
pub time_switch_node: u32,
#[serde(skip)]
pub msg_indicator: String,
#[serde(skip)]
// so the hero mode can change between two decision of algorithm without restarting XvB.
pub runtime_hero_mode: bool,
}
impl XvbPrivStats {
pub async fn request_api(client: &Client, address: &str, token: &str) -> anyhow::Result<Self> {
let resp = client
.get(
[
XVB_URL,
"/cgi-bin/p2pool_bonus_history_api.cgi?address=",
address,
"&token=",
token,
]
.concat(),
)
.send()
.await?;
match resp.status() {
StatusCode::OK => match resp.json::<Self>().await {
Ok(s) => Ok(s),
Err(err) => {
error!("XvB Watchdog | Data provided from private API is not deserializ-able.Error: {}", err);
bail!(
"Data provided from private API is not deserializ-able.Error: {}",
err
);
}
},
StatusCode::UNPROCESSABLE_ENTITY => {
bail!("the token is invalid for this xmr address.")
}
_ => bail!("The status of the response is not expected"),
}
}
pub async fn update_stats(
client: &Client,
address: &str,
token: &str,
pub_api: &Arc<Mutex<PubXvbApi>>,
gui_api: &Arc<Mutex<PubXvbApi>>,
process: &Arc<Mutex<Process>>,
) {
match XvbPrivStats::request_api(client, address, token).await {
Ok(new_data) => {
debug!("XvB Watchdog | HTTP API request OK");
lock!(&pub_api).stats_priv = new_data;
}
Err(err) => {
warn!(
"XvB Watchdog | Could not send HTTP private API request to: {}\n:{}",
XVB_URL, err
);
output_console(
gui_api,
&format!("Failure to retrieve private stats from {}", XVB_URL),
);
lock!(process).state = ProcessState::Failed;
lock!(process).signal = ProcessSignal::Stop;
}
}
}
}

View file

@ -0,0 +1,82 @@
use std::sync::{Arc, Mutex};
use log::{debug, warn};
use reqwest::Client;
use serde::Deserialize;
use serde_this_or_that::as_u64;
use crate::{
helper::{xvb::output_console, Process, ProcessSignal, ProcessState},
macros::lock,
XVB_URL_PUBLIC_API,
};
use super::{rounds::XvbRound, PubXvbApi};
#[derive(Debug, Clone, Default, Deserialize)]
pub struct XvbPubStats {
pub time_remain: u32, // remaining time of round in minutes
pub bonus_hr: f64,
pub donate_hr: f64, // donated hr from all donors
pub donate_miners: u32, // numbers of donors
pub donate_workers: u32, // numbers of workers from donors
pub players: u32,
pub players_round: u32,
pub winner: String,
pub share_effort: String,
pub block_reward: String,
pub round_type: XvbRound,
#[serde(deserialize_with = "as_u64")]
pub block_height: u64,
pub block_hash: String,
#[serde(deserialize_with = "as_u64")]
pub roll_winner: u64,
#[serde(deserialize_with = "as_u64")]
pub roll_round: u64,
pub reward_yearly: Vec<f64>,
}
impl XvbPubStats {
#[inline]
// Send an HTTP request to XvB's API, serialize it into [Self] and return it
pub(in crate::helper) async fn request_api(
client: &Client,
) -> std::result::Result<Self, anyhow::Error> {
Ok(client
.get(XVB_URL_PUBLIC_API)
.send()
.await?
.json::<Self>()
.await?)
}
pub async fn update_stats(
client: &Client,
gui_api: &Arc<Mutex<PubXvbApi>>,
pub_api: &Arc<Mutex<PubXvbApi>>,
process: &Arc<Mutex<Process>>,
) {
debug!("XvB Watchdog | Attempting HTTP public API request...");
match XvbPubStats::request_api(client).await {
Ok(new_data) => {
debug!("XvB Watchdog | HTTP API request OK");
lock!(&pub_api).stats_pub = new_data;
}
Err(err) => {
warn!(
"XvB Watchdog | Could not send HTTP API request to: {}\n:{}",
XVB_URL_PUBLIC_API, err
);
// output the error to console
output_console(
gui_api,
&format!(
"Failure to retrieve public stats from {}",
XVB_URL_PUBLIC_API
),
);
// we stop because we can't make the rest work without public stats. (winner in xvb private stats).
lock!(process).state = ProcessState::Failed;
lock!(process).signal = ProcessSignal::Stop;
}
}
}
}

55
src/helper/xvb/rounds.rs Normal file
View file

@ -0,0 +1,55 @@
use std::sync::{Arc, Mutex};
use derive_more::Display;
use serde::Deserialize;
use crate::{
macros::lock, XVB_ROUND_DONOR_MEGA_MIN_HR, XVB_ROUND_DONOR_MIN_HR, XVB_ROUND_DONOR_VIP_MIN_HR,
XVB_ROUND_DONOR_WHALE_MIN_HR,
};
use super::PubXvbApi;
#[derive(Debug, Clone, Default, Display, Deserialize, PartialEq)]
pub enum XvbRound {
#[default]
#[display(fmt = "VIP")]
#[serde(alias = "vip")]
Vip,
#[serde(alias = "donor")]
Donor,
#[display(fmt = "VIP Donor")]
#[serde(alias = "donor_vip")]
DonorVip,
#[display(fmt = "Whale Donor")]
#[serde(alias = "donor_whale")]
DonorWhale,
#[display(fmt = "Mega Donor")]
#[serde(alias = "donor_mega")]
DonorMega,
}
pub(crate) fn round_type(share: u32, pub_api: &Arc<Mutex<PubXvbApi>>) -> Option<XvbRound> {
if share > 0 {
let stats_priv = &lock!(pub_api).stats_priv;
match (
(stats_priv.donor_1hr_avg * 1000.0) as u32,
(stats_priv.donor_24hr_avg * 1000.0) as u32,
) {
x if x.0 >= XVB_ROUND_DONOR_MEGA_MIN_HR && x.1 >= XVB_ROUND_DONOR_MEGA_MIN_HR => {
Some(XvbRound::DonorMega)
}
x if x.0 >= XVB_ROUND_DONOR_WHALE_MIN_HR && x.1 >= XVB_ROUND_DONOR_WHALE_MIN_HR => {
Some(XvbRound::DonorWhale)
}
x if x.0 >= XVB_ROUND_DONOR_VIP_MIN_HR && x.1 >= XVB_ROUND_DONOR_VIP_MIN_HR => {
Some(XvbRound::DonorVip)
}
x if x.0 >= XVB_ROUND_DONOR_MIN_HR && x.1 >= XVB_ROUND_DONOR_MIN_HR => {
Some(XvbRound::Donor)
}
(_, _) => Some(XvbRound::Vip),
}
} else {
None
}
}

View file

@ -423,13 +423,15 @@ pub const XVB_BUFFER: f32 = 1.05;
pub const XVB_TIME_ALGO: u32 = 600; pub const XVB_TIME_ALGO: u32 = 600;
pub const XVB_TOKEN_LEN: usize = 9; pub const XVB_TOKEN_LEN: usize = 9;
pub const XVB_HERO_SELECT: &str = pub const XVB_HERO_SELECT: &str =
"This mode will donate all available hashrate while keeping a share in the p2pool PPLNS window"; "This mode will donate all available hashrate while keeping a share in the p2pool PPLNS window.\nWhen modified, the algorithm will use the new choice at the next decision.";
pub const XVB_TOKEN_FIELD: &str = "Token"; pub const XVB_TOKEN_FIELD: &str = "Token";
pub const XVB_FAILURE_FIELD: &str = "Failures"; pub const XVB_FAILURE_FIELD: &str = "Failures";
pub const XVB_DONATED_1H_FIELD: &str = "Donated last hour"; pub const XVB_DONATED_1H_FIELD: &str = "Donated last hour";
pub const XVB_DONATED_24H_FIELD: &str = "Donated last 24 hours"; pub const XVB_DONATED_24H_FIELD: &str = "Donated last 24 hours";
pub const XVB_ROUND_TYPE_FIELD: &str = "Round"; pub const XVB_ROUND_TYPE_FIELD: &str = "Round";
pub const XVB_WINNER_FIELD: &str = "Win"; pub const XVB_WINNER_FIELD: &str = "Win";
pub const XVB_MINING_ON_FIELD: &str = "Currently Mining on";
pub const XVB_ROUND_DONOR_MIN_HR: u32 = 1000; pub const XVB_ROUND_DONOR_MIN_HR: u32 = 1000;
pub const XVB_ROUND_DONOR_VIP_MIN_HR: u32 = 10000; pub const XVB_ROUND_DONOR_VIP_MIN_HR: u32 = 10000;
pub const XVB_ROUND_DONOR_WHALE_MIN_HR: u32 = 100000; pub const XVB_ROUND_DONOR_WHALE_MIN_HR: u32 = 100000;

View file

@ -21,6 +21,8 @@ use log::error;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use regex::Regex; use regex::Regex;
use crate::helper::xvb::nodes::XvbNode;
//---------------------------------------------------------------------------------------------------- Lazy //---------------------------------------------------------------------------------------------------- Lazy
pub static REGEXES: Lazy<Regexes> = Lazy::new(Regexes::new); pub static REGEXES: Lazy<Regexes> = Lazy::new(Regexes::new);
pub static P2POOL_REGEX: Lazy<P2poolRegex> = Lazy::new(P2poolRegex::new); pub static P2POOL_REGEX: Lazy<P2poolRegex> = Lazy::new(P2poolRegex::new);
@ -136,19 +138,43 @@ pub fn nb_current_shares(s: &str) -> Option<u32> {
Lazy::new(|| Regex::new(r"Your shares = (?P<nb>\d+) blocks").unwrap()); Lazy::new(|| Regex::new(r"Your shares = (?P<nb>\d+) blocks").unwrap());
if let Some(c) = CURRENT_SHARE.captures(s) { if let Some(c) = CURRENT_SHARE.captures(s) {
if let Some(m) = c.name("nb") { if let Some(m) = c.name("nb") {
return Some( return Some(m.as_str().parse::<u32>().unwrap_or_else(|_| {
m.as_str().parse::<u32>().expect( panic!(
&[ "{}",
[
"the number of shares should have been a unit number but is :\n", "the number of shares should have been a unit number but is :\n",
m.as_str(), m.as_str(),
] ]
.concat(), .concat()
), )
); }));
} }
} }
None None
} }
pub fn detect_new_node_xmrig(s: &str) -> Option<XvbNode> {
static CURRENT_SHARE: Lazy<Regex> =
Lazy::new(|| Regex::new(r"net use pool (?P<pool>.*?) ").unwrap());
if let Some(c) = CURRENT_SHARE.captures(s) {
if let Some(m) = c.name("pool") {
match m.as_str() {
// if user change address of local p2pool, it could create issue ?
"127.0.0.1:3333" => {
return Some(XvbNode::P2pool);
}
"eu.xmrvsbeast.com:4247" => {
return Some(XvbNode::Europe);
}
"na.xmrvsbeast.com:4247" => {
return Some(XvbNode::NorthAmerica);
}
_ => {}
}
}
}
error!("a line on xmrig console was detected as using a new pool but the syntax was not recognized.");
None
}
pub fn estimated_hr(s: &str) -> Option<f32> { pub fn estimated_hr(s: &str) -> Option<f32> {
static CURRENT_SHARE: Lazy<Regex> = Lazy::new(|| { static CURRENT_SHARE: Lazy<Regex> = Lazy::new(|| {
Regex::new(r"(?P<nb>[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?) (?P<unit>.*)H/s").unwrap() Regex::new(r"(?P<nb>[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?) (?P<unit>.*)H/s").unwrap()
@ -169,18 +195,29 @@ pub fn estimated_hr(s: &str) -> Option<f32> {
} as f32; } as f32;
if let Some(m) = c.name("nb") { if let Some(m) = c.name("nb") {
return Some( return Some(
m.as_str().parse::<f32>().expect( m.as_str().parse::<f32>().unwrap_or_else(|_| {
&[ panic!(
"the number of shares should have been a float number but is :\n", "{}",
m.as_str(), [
] "the number of shares should have been a float number but is :\n",
.concat(), m.as_str(),
) * coeff, ]
.concat()
)
}) * coeff,
); );
} }
} }
None None
} }
pub fn contains_connect_error(l: &str) -> bool {
static LINE_SHARE: Lazy<Regex> = Lazy::new(|| Regex::new(r"connect error").unwrap());
LINE_SHARE.is_match(l)
}
pub fn contains_usepool(l: &str) -> bool {
static LINE_SHARE: Lazy<Regex> = Lazy::new(|| Regex::new(r"use pool").unwrap());
LINE_SHARE.is_match(l)
}
pub fn contains_statuscommand(l: &str) -> bool { pub fn contains_statuscommand(l: &str) -> bool {
static LINE_SHARE: Lazy<Regex> = Lazy::new(|| Regex::new(r"^statusfromgupaxx").unwrap()); static LINE_SHARE: Lazy<Regex> = Lazy::new(|| Regex::new(r"^statusfromgupaxx").unwrap());
LINE_SHARE.is_match(l) LINE_SHARE.is_match(l)

View file

@ -4,4 +4,4 @@ extend-exclude = [
"pgp/", "pgp/",
] ]
[default] [default]
extend-ignore-identifiers-re = ["ehr"] extend-ignore-identifiers-re = ["ehr", "PN"]