node + config + ui: fork stratum server to handle state on node start/stop, complete stratum configuration, complete node server config, settings ui refactoring, update i18n lib
This commit is contained in:
parent
ffbe772c27
commit
3b2d3ab202
30 changed files with 2534 additions and 753 deletions
187
Cargo.lock
generated
187
Cargo.lock
generated
|
@ -775,9 +775,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.7"
|
||||
version = "0.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3e4c1eaa2012c47becbbad2ab175484c2a84d1185b566fb2cc5b8707343dfe58"
|
||||
checksum = "03e69e28e9f7f77debdedbaafa2866e1de9ba56df55a8bd7cfc724c25a09987c"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
@ -1542,9 +1542,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "gimli"
|
||||
version = "0.27.2"
|
||||
version = "0.27.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ad0a93d233ebf96623465aad4046a8d3aa4da22d4f4beba5388838c8a434bbb4"
|
||||
checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e"
|
||||
|
||||
[[package]]
|
||||
name = "git2"
|
||||
|
@ -1589,6 +1589,17 @@ dependencies = [
|
|||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "globwalk"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93e3af942408868f6934a7b85134a3230832b9977cf66125df2f9edcfce4ddcc"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"ignore",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glow"
|
||||
version = "0.11.2"
|
||||
|
@ -1603,9 +1614,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "glutin"
|
||||
version = "0.30.8"
|
||||
version = "0.30.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62f9b771a65f0a1e3ddb6aa16f867d87dc73c922411c255e6c4ab7f6d45c7327"
|
||||
checksum = "23b0385782048be65f0a9dd046c469d6a758a53fe1aa63a8111dea394d2ffa2f"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"cfg_aliases",
|
||||
|
@ -1706,9 +1717,12 @@ dependencies = [
|
|||
"egui_extras",
|
||||
"env_logger 0.10.0",
|
||||
"futures 0.3.28",
|
||||
"grin_api",
|
||||
"grin_chain",
|
||||
"grin_config",
|
||||
"grin_core",
|
||||
"grin_keychain",
|
||||
"grin_p2p",
|
||||
"grin_servers",
|
||||
"grin_util",
|
||||
"jni",
|
||||
|
@ -1716,10 +1730,16 @@ dependencies = [
|
|||
"log",
|
||||
"once_cell",
|
||||
"openssl-sys",
|
||||
"pnet",
|
||||
"pollster 0.3.0",
|
||||
"rand 0.6.5",
|
||||
"rust-i18n",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"sys-locale",
|
||||
"tokio",
|
||||
"tokio-util 0.2.0",
|
||||
"toml 0.7.4",
|
||||
"wgpu",
|
||||
"winit",
|
||||
|
@ -1728,7 +1748,6 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "grin_api"
|
||||
version = "5.2.0-beta.1"
|
||||
source = "git+https://github.com/mimblewimble/grin.git#fd1410ebeb39fea6dc7bed5ebd55842466abdc69"
|
||||
dependencies = [
|
||||
"bytes 0.5.6",
|
||||
"easy-jsonrpc-mw",
|
||||
|
@ -1760,7 +1779,6 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "grin_chain"
|
||||
version = "5.2.0-beta.1"
|
||||
source = "git+https://github.com/mimblewimble/grin.git#fd1410ebeb39fea6dc7bed5ebd55842466abdc69"
|
||||
dependencies = [
|
||||
"bit-vec",
|
||||
"bitflags 1.3.2",
|
||||
|
@ -1783,7 +1801,6 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "grin_config"
|
||||
version = "5.2.0-beta.1"
|
||||
source = "git+https://github.com/mimblewimble/grin.git#fd1410ebeb39fea6dc7bed5ebd55842466abdc69"
|
||||
dependencies = [
|
||||
"dirs",
|
||||
"grin_core",
|
||||
|
@ -1799,7 +1816,6 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "grin_core"
|
||||
version = "5.2.0-beta.1"
|
||||
source = "git+https://github.com/mimblewimble/grin.git#fd1410ebeb39fea6dc7bed5ebd55842466abdc69"
|
||||
dependencies = [
|
||||
"blake2-rfc",
|
||||
"byteorder",
|
||||
|
@ -1825,7 +1841,6 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "grin_keychain"
|
||||
version = "5.2.0-beta.1"
|
||||
source = "git+https://github.com/mimblewimble/grin.git#fd1410ebeb39fea6dc7bed5ebd55842466abdc69"
|
||||
dependencies = [
|
||||
"blake2-rfc",
|
||||
"byteorder",
|
||||
|
@ -1847,7 +1862,6 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "grin_p2p"
|
||||
version = "5.2.0-beta.1"
|
||||
source = "git+https://github.com/mimblewimble/grin.git#fd1410ebeb39fea6dc7bed5ebd55842466abdc69"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"bytes 0.5.6",
|
||||
|
@ -1869,7 +1883,6 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "grin_pool"
|
||||
version = "5.2.0-beta.1"
|
||||
source = "git+https://github.com/mimblewimble/grin.git#fd1410ebeb39fea6dc7bed5ebd55842466abdc69"
|
||||
dependencies = [
|
||||
"blake2-rfc",
|
||||
"chrono",
|
||||
|
@ -1902,7 +1915,6 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "grin_servers"
|
||||
version = "5.2.0-beta.1"
|
||||
source = "git+https://github.com/mimblewimble/grin.git#fd1410ebeb39fea6dc7bed5ebd55842466abdc69"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"fs2",
|
||||
|
@ -1932,7 +1944,6 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "grin_store"
|
||||
version = "5.2.0-beta.1"
|
||||
source = "git+https://github.com/mimblewimble/grin.git#fd1410ebeb39fea6dc7bed5ebd55842466abdc69"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"croaring",
|
||||
|
@ -1951,7 +1962,6 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "grin_util"
|
||||
version = "5.2.0-beta.1"
|
||||
source = "git+https://github.com/mimblewimble/grin.git#fd1410ebeb39fea6dc7bed5ebd55842466abdc69"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"base64 0.12.3",
|
||||
|
@ -2256,6 +2266,15 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ipnetwork"
|
||||
version = "0.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf466541e9d546596ee94f9f69590f89473455f88372423e0008fc1a7daf100e"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is-terminal"
|
||||
version = "0.4.7"
|
||||
|
@ -2750,9 +2769,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "net2"
|
||||
version = "0.2.38"
|
||||
version = "0.2.39"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74d0df99cfcd2530b2e694f6e17e7f37b8e26bb23983ac530c0c97408837c631"
|
||||
checksum = "b13b648036a2339d06de780866fbdfda0dde886de7b3af2ddeba8b14f4ee34ac"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.10",
|
||||
"libc",
|
||||
|
@ -2784,6 +2803,12 @@ dependencies = [
|
|||
"memoffset",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "no-std-net"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43794a0ace135be66a25d3ae77d41b91615fb68ae937f904090203e81f755b65"
|
||||
|
||||
[[package]]
|
||||
name = "nodrop"
|
||||
version = "0.1.14"
|
||||
|
@ -3204,6 +3229,97 @@ version = "0.3.27"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"
|
||||
|
||||
[[package]]
|
||||
name = "pnet"
|
||||
version = "0.33.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd959a8268165518e2bf5546ba84c7b3222744435616381df3c456fe8d983576"
|
||||
dependencies = [
|
||||
"ipnetwork",
|
||||
"pnet_base",
|
||||
"pnet_datalink",
|
||||
"pnet_packet",
|
||||
"pnet_sys",
|
||||
"pnet_transport",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pnet_base"
|
||||
version = "0.33.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "872e46346144ebf35219ccaa64b1dffacd9c6f188cd7d012bd6977a2a838f42e"
|
||||
dependencies = [
|
||||
"no-std-net",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pnet_datalink"
|
||||
version = "0.33.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c302da22118d2793c312a35fb3da6846cb0fab6c3ad53fd67e37809b06cdafce"
|
||||
dependencies = [
|
||||
"ipnetwork",
|
||||
"libc",
|
||||
"pnet_base",
|
||||
"pnet_sys",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pnet_macros"
|
||||
version = "0.33.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a780e80005c2e463ec25a6e9f928630049a10b43945fea83207207d4a7606f4"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.60",
|
||||
"quote 1.0.28",
|
||||
"regex",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pnet_macros_support"
|
||||
version = "0.33.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6d932134f32efd7834eb8b16d42418dac87086347d1bc7d142370ef078582bc"
|
||||
dependencies = [
|
||||
"pnet_base",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pnet_packet"
|
||||
version = "0.33.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8bde678bbd85cb1c2d99dc9fc596e57f03aa725f84f3168b0eaf33eeccb41706"
|
||||
dependencies = [
|
||||
"glob",
|
||||
"pnet_base",
|
||||
"pnet_macros",
|
||||
"pnet_macros_support",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pnet_sys"
|
||||
version = "0.33.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "faf7a58b2803d818a374be9278a1fe8f88fce14b936afbe225000cfcd9c73f16"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pnet_transport"
|
||||
version = "0.33.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "813d1c0e4defbe7ee22f6fe1755f122b77bfb5abe77145b1b5baaf463cab9249"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"pnet_base",
|
||||
"pnet_packet",
|
||||
"pnet_sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "png"
|
||||
version = "0.17.9"
|
||||
|
@ -3528,19 +3644,20 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "rust-i18n"
|
||||
version = "1.2.2"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5340b7b546416b54cb3dc2184038b6ed6e45654e7b2f52bb206b52bb86c6d493"
|
||||
checksum = "9a516a7ceb61ddcdad9cf723de82b86f6ed8c78ebe25255c5686a061bf7318a6"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
"glob",
|
||||
"globwalk",
|
||||
"itertools",
|
||||
"once_cell",
|
||||
"quote 1.0.28",
|
||||
"regex",
|
||||
"rust-i18n-extract",
|
||||
"rust-i18n-macro",
|
||||
"rust-i18n-support",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"toml 0.5.11",
|
||||
|
@ -3548,9 +3665,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "rust-i18n-extract"
|
||||
version = "1.1.0"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec44568e2cdf4bfb7a62381bbc6fcdf0a27c60cd503dfa12c59e6c17cf3177fa"
|
||||
checksum = "e89ac25fb50c8d0893ee6436056fb4a0cc6f6e1df99239d7c104421d007d445e"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"ignore",
|
||||
|
@ -3566,9 +3683,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "rust-i18n-macro"
|
||||
version = "1.3.0"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89ef5911f7c8324f62c44151fa7461bbdf6a00cbfa9beb04d963d9d02ec05634"
|
||||
checksum = "e09ef5c1e310112eea3c19c4e18e3e62968b002eb535ff5b242ca1200742f996"
|
||||
dependencies = [
|
||||
"glob",
|
||||
"once_cell",
|
||||
|
@ -3583,16 +3700,17 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "rust-i18n-support"
|
||||
version = "1.1.0"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e6bbf2d058c3558bef952564ceb9afcb19631cde22b47dc44f436e62ecfb916"
|
||||
checksum = "14eb094cd0072c5f09f333eea36fcd8c64961f9eb61dbd09e82eff51c58e8414"
|
||||
dependencies = [
|
||||
"glob",
|
||||
"globwalk",
|
||||
"once_cell",
|
||||
"proc-macro2 1.0.60",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_yaml",
|
||||
"toml 0.7.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3783,9 +3901,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.96"
|
||||
version = "1.0.97"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1"
|
||||
checksum = "bdf3bf93142acad5821c99197022e170842cdbc1c30482b98750c688c640842a"
|
||||
dependencies = [
|
||||
"itoa 1.0.6",
|
||||
"ryu",
|
||||
|
@ -4465,11 +4583,10 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "want"
|
||||
version = "0.3.0"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0"
|
||||
checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
|
||||
dependencies = [
|
||||
"log",
|
||||
"try-lock",
|
||||
]
|
||||
|
||||
|
@ -5088,9 +5205,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.4.6"
|
||||
version = "0.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61de7bac303dc551fe038e2b3cef0f571087a47571ea6e79a87692ac99b99699"
|
||||
checksum = "ca0ace3845f0d96209f0375e6d367e3eb87eb65d27d445bdc9f1843a26f39448"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
|
26
Cargo.toml
26
Cargo.toml
|
@ -13,15 +13,15 @@ build = "src/build/build.rs"
|
|||
[dependencies]
|
||||
log = "0.4"
|
||||
#android-activity = { version = "0.4", features = ["game-activity"] }
|
||||
#grin_api = "5.1.2"
|
||||
grin_chain = { git = "https://github.com/mimblewimble/grin.git" }
|
||||
grin_config = { git = "https://github.com/mimblewimble/grin.git" }
|
||||
grin_core = { git = "https://github.com/mimblewimble/grin.git" }
|
||||
#grin_keychain = "5.1.2"
|
||||
#grin_p2p = "5.1.2"
|
||||
grin_servers = { git = "https://github.com/mimblewimble/grin.git" }
|
||||
grin_api = { path = "../grin/node/api" }
|
||||
grin_chain = { path = "../grin/node/chain" }
|
||||
grin_config = { path = "../grin/node/config" }
|
||||
grin_core = { path = "../grin/node/core" }
|
||||
grin_keychain = { path = "../grin/node/keychain" }
|
||||
grin_p2p = { path = "../grin/node/p2p" }
|
||||
grin_servers = { path = "../grin/node/servers" }
|
||||
#grin_store = "5.1.2"
|
||||
grin_util = { git = "https://github.com/mimblewimble/grin.git" }
|
||||
grin_util = { path = "../grin/node/util" }
|
||||
openssl-sys = { version = "0.9.82", features = ["vendored"] }
|
||||
#grin_wallet_api = "5.1.0"
|
||||
#grin_wallet_libwallet = "5.1.0"
|
||||
|
@ -43,12 +43,20 @@ dirs = "2.0"
|
|||
|
||||
## other
|
||||
once_cell = "1.10.0"
|
||||
rust-i18n = "1.1.4"
|
||||
rust-i18n = "2.0.0"
|
||||
sys-locale = "0.3.0"
|
||||
chrono = "0.4.23"
|
||||
lazy_static = "1.4.0"
|
||||
toml = "0.7.4"
|
||||
serde = "1.0.164"
|
||||
pnet = "0.33.0"
|
||||
|
||||
# stratum server
|
||||
serde_derive = "1"
|
||||
serde_json = "1"
|
||||
tokio = {version = "0.2", features = ["full"] }
|
||||
tokio-util = { version = "0.2", features = ["codec"] }
|
||||
rand = "0.6"
|
||||
|
||||
[patch.crates-io]
|
||||
winit = { git = "https://github.com/rib/winit", branch = "android-activity" }
|
||||
|
|
|
@ -58,8 +58,8 @@ network_mining:
|
|||
loading: Mining will be available after the synchronization
|
||||
server_setup: Stratum server setup
|
||||
enable_server: Enable server
|
||||
server_setting: 'Enable stratum server or change more settings by selecting %{settings} at the bottom of the screen. App restart is required to change settings of the running server.'
|
||||
info: 'Mining server is enabled, you can change its settings by selecting %{settings} at the bottom of the screen. Data is updating when devices are connected.'
|
||||
info_settings: To change the settings of enabled server, you will need to restart the node.
|
||||
rewards_wallet: Wallet for rewards
|
||||
server: Stratum server
|
||||
address: Address
|
||||
|
@ -72,11 +72,9 @@ network_mining:
|
|||
network_settings:
|
||||
ip: IP Address
|
||||
port: Port
|
||||
change_port: Change port
|
||||
change_value: Change value
|
||||
stratum_port: Stratum server port
|
||||
port_unavailable: Specified port is unavailable
|
||||
restart_app_required: App restart is required to apply changes.
|
||||
restart_node_required: Node restart is required to apply changes.
|
||||
enable: Enable
|
||||
disable: Disable
|
||||
|
@ -97,6 +95,11 @@ network_settings:
|
|||
full_validation_description: Whether to run a full chain validation when processing each block (except during synchronization).
|
||||
archive_mode: Archive mode
|
||||
archive_mode_desc: Run the node in full archive mode (more disk space and time will be required for synchronization).
|
||||
attempt_time: Attempt time
|
||||
attempt_time_desc: The amount of time in seconds to attempt to mine on a particular header before stopping and re-collecting transactions from the pool
|
||||
min_share_diff: The minimum acceptable share difficulty
|
||||
reset_settings_desc: Reset integrated node settings to default values
|
||||
reset_settings: Reset settings
|
||||
modal:
|
||||
cancel: Cancel
|
||||
save: Save
|
||||
|
|
|
@ -58,8 +58,8 @@ network_mining:
|
|||
loading: Майнинг будет доступен после синхронизации
|
||||
server_setup: Настройка stratum-сервера
|
||||
enable_server: Включить сервер
|
||||
server_setting: 'Включите stratum-сервер или измените больше настроек, выбрав %{settings} внизу экрана. Для изменения настроек запущенного сервера потребуется перезапуск приложения.'
|
||||
info: 'Сервер майнинга запущен, вы можете изменить его настройки, выбрав %{settings} внизу экрана. Данные обновляются, когда устройства подключены.'
|
||||
info_settings: Для изменения настроек запущенного сервера потребуется перезапуск узла.
|
||||
rewards_wallet: Кошелёк для наград
|
||||
server: Stratum-сервер
|
||||
address: Адрес
|
||||
|
@ -72,11 +72,9 @@ network_mining:
|
|||
network_settings:
|
||||
ip: IP Адрес
|
||||
port: Порт
|
||||
change_port: Изменить порт
|
||||
change_value: Изменить значение
|
||||
stratum_port: Порт Stratum сервера
|
||||
port_unavailable: Указанный порт недоступен
|
||||
restart_app_required: Для применения изменений требуется перезапуск приложения.
|
||||
restart_node_required: Для применения изменений требуется перезапуск узла.
|
||||
enable: Включить
|
||||
disable: Выключить
|
||||
|
@ -97,6 +95,11 @@ network_settings:
|
|||
full_validation_description: Запускать ли полную проверку цепи при обработке каждого блока (за исключением синхронизации).
|
||||
archive_mode: Архивный режим
|
||||
archive_mode_desc: Запустить узел в режиме полного архива (потребуется больше места и времени для синхронизации).
|
||||
attempt_time: Время попытки
|
||||
attempt_time_desc: Количество времени в секундах для попытки майнинга на определённом заголовке перед остановкой и повторным сбором транзакций из пула
|
||||
min_share_diff: Минимальная допустимая сложность шары
|
||||
reset_settings_desc: Сбросить настройки встроенного узла до стандартных значений
|
||||
reset_settings: Сброс настроек
|
||||
modal:
|
||||
cancel: Отмена
|
||||
save: Сохранить
|
||||
|
|
|
@ -89,7 +89,7 @@ fn setup_i18n() {
|
|||
} else {
|
||||
DEFAULT_LOCALE
|
||||
};
|
||||
if crate::available_locales().contains(&locale_str) {
|
||||
if crate::_rust_i18n_available_locales().contains(&locale_str) {
|
||||
rust_i18n::set_locale(locale_str);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -59,7 +59,7 @@ impl PlatformCallbacks for Android {
|
|||
}
|
||||
|
||||
fn get_string_from_buffer(&self) -> String {
|
||||
use jni::objects::{JObject, JValue, JString};
|
||||
use jni::objects::{JObject, JString};
|
||||
|
||||
let vm = unsafe { jni::JavaVM::from_raw(self.android_app.vm_as_ptr() as _) }.unwrap();
|
||||
let mut env = vm.attach_current_thread().unwrap();
|
||||
|
|
|
@ -17,12 +17,12 @@ use egui::RichText;
|
|||
use crate::gui::{App, Colors, Navigator};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::screens::{Account, Accounts, Screen, ScreenId};
|
||||
use crate::gui::views::{ModalContainer, Network, View};
|
||||
use crate::gui::views::{ModalContainer, NetworkContainer, View};
|
||||
use crate::node::Node;
|
||||
|
||||
pub struct Root {
|
||||
screens: Vec<Box<dyn Screen>>,
|
||||
network: Network,
|
||||
network_panel: NetworkContainer,
|
||||
show_exit_progress: bool,
|
||||
allowed_modal_ids: Vec<&'static str>
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ impl Default for Root {
|
|||
Box::new(Accounts::default()),
|
||||
Box::new(Account::default())
|
||||
],
|
||||
network: Network::default(),
|
||||
network_panel: NetworkContainer::default(),
|
||||
show_exit_progress: false,
|
||||
allowed_modal_ids: vec![
|
||||
Navigator::EXIT_MODAL
|
||||
|
@ -65,7 +65,7 @@ impl Root {
|
|||
.exact_width(panel_width)
|
||||
.frame(egui::Frame::default())
|
||||
.show_animated_inside(ui, is_panel_open, |ui| {
|
||||
self.network.ui(ui, frame, cb);
|
||||
self.network_panel.ui(ui, frame, cb);
|
||||
});
|
||||
|
||||
egui::CentralPanel::default()
|
||||
|
|
|
@ -23,10 +23,3 @@ pub use modal::*;
|
|||
|
||||
mod network;
|
||||
pub use network::*;
|
||||
|
||||
mod network_node;
|
||||
mod network_settings;
|
||||
mod network_metrics;
|
||||
mod network_mining;
|
||||
mod settings_stratum;
|
||||
mod settings_node;
|
|
@ -126,7 +126,7 @@ impl Modal {
|
|||
|
||||
// Show main content Window at given position.
|
||||
let (content_align, content_offset) = self.modal_position();
|
||||
let layer_id = egui::Window::new("modal_window")
|
||||
let layer_id = egui::Window::new(format!("modal_window_{}", self.id))
|
||||
.title_bar(false)
|
||||
.resizable(false)
|
||||
.collapsible(false)
|
||||
|
@ -152,12 +152,12 @@ impl Modal {
|
|||
/// Get [`egui::Window`] position based on [`ModalPosition`].
|
||||
fn modal_position(&self) -> (Align2, Vec2) {
|
||||
let align = match self.position {
|
||||
ModalPosition::CenterTop => { Align2::CENTER_TOP }
|
||||
ModalPosition::Center => { Align2::CENTER_CENTER }
|
||||
ModalPosition::CenterTop => Align2::CENTER_TOP,
|
||||
ModalPosition::Center => Align2::CENTER_CENTER
|
||||
};
|
||||
let offset = match self.position {
|
||||
ModalPosition::CenterTop => { Vec2::new(0.0, 20.0) }
|
||||
ModalPosition::Center => { Vec2::new(0.0, 0.0) }
|
||||
ModalPosition::CenterTop => Vec2::new(0.0, 20.0),
|
||||
ModalPosition::Center => Vec2::new(0.0, 0.0)
|
||||
};
|
||||
(align, offset)
|
||||
}
|
||||
|
|
|
@ -12,262 +12,11 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::time::Duration;
|
||||
mod container;
|
||||
mod metrics;
|
||||
mod mining;
|
||||
mod node_settings;
|
||||
mod node;
|
||||
mod settings;
|
||||
|
||||
use egui::{Color32, lerp, Rgba, RichText};
|
||||
use egui::style::Margin;
|
||||
use egui_extras::{Size, StripBuilder};
|
||||
use grin_chain::SyncStatus;
|
||||
use crate::AppConfig;
|
||||
|
||||
use crate::gui::{Colors, Navigator};
|
||||
use crate::gui::icons::{CARDHOLDER, DATABASE, DOTS_THREE_OUTLINE_VERTICAL, FACTORY, FADERS, GAUGE};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::{Modal, ModalContainer, View};
|
||||
use crate::gui::views::network_metrics::NetworkMetrics;
|
||||
use crate::gui::views::network_mining::NetworkMining;
|
||||
use crate::gui::views::network_node::NetworkNode;
|
||||
use crate::gui::views::network_settings::NetworkSettings;
|
||||
use crate::gui::views::settings_node::NodeSetup;
|
||||
use crate::gui::views::settings_stratum::StratumServerSetup;
|
||||
use crate::node::Node;
|
||||
|
||||
pub trait NetworkTab {
|
||||
fn get_type(&self) -> NetworkTabType;
|
||||
fn ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks);
|
||||
fn on_modal_ui(&mut self, ui: &mut egui::Ui, modal: &Modal, cb: &dyn PlatformCallbacks);
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
pub enum NetworkTabType {
|
||||
Node,
|
||||
Metrics,
|
||||
Mining,
|
||||
Settings
|
||||
}
|
||||
|
||||
impl NetworkTabType {
|
||||
pub fn name(&self) -> String {
|
||||
match *self {
|
||||
NetworkTabType::Node => { t!("network.node") }
|
||||
NetworkTabType::Metrics => { t!("network.metrics") }
|
||||
NetworkTabType::Mining => { t!("network.mining") }
|
||||
NetworkTabType::Settings => { t!("network.settings") }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Network {
|
||||
current_tab: Box<dyn NetworkTab>,
|
||||
modal_ids: Vec<&'static str>,
|
||||
}
|
||||
|
||||
impl Default for Network {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
current_tab: Box::new(NetworkNode::default()),
|
||||
modal_ids: vec![
|
||||
NetworkSettings::NODE_RESTART_REQUIRED_MODAL,
|
||||
StratumServerSetup::STRATUM_PORT_MODAL,
|
||||
NodeSetup::API_PORT_MODAL,
|
||||
NodeSetup::API_SECRET_MODAL,
|
||||
NodeSetup::FOREIGN_API_SECRET_MODAL,
|
||||
NodeSetup::FTL_MODAL
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ModalContainer for Network {
|
||||
fn modal_ids(&self) -> &Vec<&'static str> {
|
||||
self.modal_ids.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl Network {
|
||||
pub fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame, cb: &dyn PlatformCallbacks) {
|
||||
// Show modal content if it's opened.
|
||||
let modal_id = Navigator::is_modal_open();
|
||||
if modal_id.is_some() && self.can_show_modal(modal_id.unwrap()) {
|
||||
Navigator::modal_ui(ui, |ui, modal| {
|
||||
self.current_tab.as_mut().on_modal_ui(ui, modal, cb);
|
||||
});
|
||||
}
|
||||
|
||||
egui::TopBottomPanel::top("network_title")
|
||||
.resizable(false)
|
||||
.frame(egui::Frame {
|
||||
fill: Colors::YELLOW,
|
||||
inner_margin: Margin::same(0.0),
|
||||
outer_margin: Margin::same(0.0),
|
||||
..Default::default()
|
||||
})
|
||||
.show_inside(ui, |ui| {
|
||||
self.title_ui(ui, frame);
|
||||
});
|
||||
|
||||
egui::TopBottomPanel::bottom("network_tabs")
|
||||
.frame(egui::Frame {
|
||||
outer_margin: Margin::same(5.0),
|
||||
..Default::default()
|
||||
})
|
||||
.show_inside(ui, |ui| {
|
||||
self.tabs_ui(ui);
|
||||
});
|
||||
|
||||
egui::CentralPanel::default()
|
||||
.frame(egui::Frame {
|
||||
stroke: View::DEFAULT_STROKE,
|
||||
inner_margin: Margin::same(4.0),
|
||||
fill: Colors::WHITE,
|
||||
..Default::default()
|
||||
})
|
||||
.show_inside(ui, |ui| {
|
||||
self.current_tab.ui(ui, cb);
|
||||
});
|
||||
}
|
||||
|
||||
/// Draw tab buttons in the bottom of the screen.
|
||||
fn tabs_ui(&mut self, ui: &mut egui::Ui) {
|
||||
ui.scope(|ui| {
|
||||
// Setup spacing between tabs.
|
||||
ui.style_mut().spacing.item_spacing = egui::vec2(5.0, 0.0);
|
||||
// Setup vertical padding inside tab button.
|
||||
ui.style_mut().spacing.button_padding = egui::vec2(0.0, 3.0);
|
||||
|
||||
ui.columns(4, |columns| {
|
||||
columns[0].vertical_centered_justified(|ui| {
|
||||
View::tab_button(ui, DATABASE, self.is_current_tab(NetworkTabType::Node), || {
|
||||
self.current_tab = Box::new(NetworkNode::default());
|
||||
});
|
||||
});
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
View::tab_button(ui, GAUGE, self.is_current_tab(NetworkTabType::Metrics), || {
|
||||
self.current_tab = Box::new(NetworkMetrics::default());
|
||||
});
|
||||
});
|
||||
columns[2].vertical_centered_justified(|ui| {
|
||||
View::tab_button(ui, FACTORY, self.is_current_tab(NetworkTabType::Mining), || {
|
||||
self.current_tab = Box::new(NetworkMining::default());
|
||||
});
|
||||
});
|
||||
columns[3].vertical_centered_justified(|ui| {
|
||||
View::tab_button(ui, FADERS, self.is_current_tab(NetworkTabType::Settings), || {
|
||||
self.current_tab = Box::new(NetworkSettings::default());
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// Check if current tab equals providing [`NetworkTabType`].
|
||||
fn is_current_tab(&self, tab_type: NetworkTabType) -> bool {
|
||||
self.current_tab.get_type() == tab_type
|
||||
}
|
||||
|
||||
/// Draw title content.
|
||||
fn title_ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) {
|
||||
StripBuilder::new(ui)
|
||||
.size(Size::exact(52.0))
|
||||
.vertical(|mut strip| {
|
||||
strip.strip(|builder| {
|
||||
builder
|
||||
.size(Size::exact(52.0))
|
||||
.size(Size::remainder())
|
||||
.size(Size::exact(52.0))
|
||||
.horizontal(|mut strip| {
|
||||
strip.cell(|ui| {
|
||||
ui.centered_and_justified(|ui| {
|
||||
View::title_button(ui, DOTS_THREE_OUTLINE_VERTICAL, || {
|
||||
//TODO: Actions for node
|
||||
});
|
||||
});
|
||||
});
|
||||
strip.strip(|builder| {
|
||||
self.title_text_ui(builder);
|
||||
});
|
||||
strip.cell(|ui| {
|
||||
if !View::is_dual_panel_mode(frame) {
|
||||
ui.centered_and_justified(|ui| {
|
||||
View::title_button(ui, CARDHOLDER, || {
|
||||
Navigator::toggle_side_panel();
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// Draw title text.
|
||||
fn title_text_ui(&self, builder: StripBuilder) {
|
||||
builder
|
||||
.size(Size::remainder())
|
||||
.size(Size::exact(32.0))
|
||||
.vertical(|mut strip| {
|
||||
strip.cell(|ui| {
|
||||
ui.add_space(2.0);
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.label(RichText::new(self.current_tab.get_type().name().to_uppercase())
|
||||
.size(18.0)
|
||||
.color(Colors::TITLE));
|
||||
});
|
||||
});
|
||||
strip.cell(|ui| {
|
||||
ui.centered_and_justified(|ui| {
|
||||
let sync_status = Node::get_sync_status();
|
||||
|
||||
// Setup text color animation based on sync status
|
||||
let idle = match sync_status {
|
||||
None => !Node::is_starting(),
|
||||
Some(ss) => ss == SyncStatus::NoSync
|
||||
};
|
||||
let (dark, bright) = (0.3, 1.0);
|
||||
let color_factor = if !idle {
|
||||
lerp(dark..=bright, ui.input().time.cos().abs()) as f32
|
||||
} else {
|
||||
bright as f32
|
||||
};
|
||||
|
||||
// Draw sync text
|
||||
let status_color_rgba = Rgba::from(Colors::TEXT) * color_factor;
|
||||
let status_color = Color32::from(status_color_rgba);
|
||||
View::ellipsize_text(ui, Node::get_sync_status_text(), 15.0, status_color);
|
||||
|
||||
// Repaint based on sync status
|
||||
if idle {
|
||||
ui.ctx().request_repaint_after(Duration::from_millis(600));
|
||||
} else {
|
||||
ui.ctx().request_repaint();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// Content to draw when node is disabled.
|
||||
pub fn disabled_node_ui(ui: &mut egui::Ui) {
|
||||
View::center_content(ui, 162.0, |ui| {
|
||||
let text = t!("network.disabled_server", "dots" => DOTS_THREE_OUTLINE_VERTICAL);
|
||||
ui.label(RichText::new(text)
|
||||
.size(16.0)
|
||||
.color(Colors::INACTIVE_TEXT)
|
||||
);
|
||||
ui.add_space(10.0);
|
||||
View::button(ui, t!("network.enable_node"), Colors::GOLD, || {
|
||||
Node::start();
|
||||
});
|
||||
ui.add_space(2.0);
|
||||
Self::autorun_node_ui(ui);
|
||||
});
|
||||
}
|
||||
|
||||
/// Draw checkbox with setting to run node on app launch.
|
||||
pub fn autorun_node_ui(ui: &mut egui::Ui) {
|
||||
let autostart = AppConfig::autostart_node();
|
||||
View::checkbox(ui, autostart, t!("network.autorun"), || {
|
||||
AppConfig::toggle_node_autostart();
|
||||
});
|
||||
}
|
||||
}
|
||||
pub use container::*;
|
275
src/gui/views/network/container.rs
Normal file
275
src/gui/views/network/container.rs
Normal file
|
@ -0,0 +1,275 @@
|
|||
// Copyright 2023 The Grim Developers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
use egui::{Color32, lerp, Rgba, RichText};
|
||||
use egui::style::Margin;
|
||||
use egui_extras::{Size, StripBuilder};
|
||||
use grin_chain::SyncStatus;
|
||||
|
||||
use crate::AppConfig;
|
||||
use crate::gui::{Colors, Navigator};
|
||||
use crate::gui::icons::{CARDHOLDER, DATABASE, DOTS_THREE_OUTLINE_VERTICAL, FACTORY, FADERS, GAUGE};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::{Modal, ModalContainer, View};
|
||||
use crate::gui::views::network::metrics::NetworkMetrics;
|
||||
use crate::gui::views::network::mining::NetworkMining;
|
||||
use crate::gui::views::network::node::NetworkNode;
|
||||
use crate::gui::views::network::node_settings::NetworkNodeSettings;
|
||||
use crate::gui::views::network::settings::server::ServerSetup;
|
||||
use crate::gui::views::network::settings::stratum::StratumServerSetup;
|
||||
use crate::node::Node;
|
||||
|
||||
pub trait NetworkTab {
|
||||
fn get_type(&self) -> NetworkTabType;
|
||||
fn ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks);
|
||||
fn on_modal_ui(&mut self, ui: &mut egui::Ui, modal: &Modal, cb: &dyn PlatformCallbacks);
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
pub enum NetworkTabType {
|
||||
Node,
|
||||
Metrics,
|
||||
Mining,
|
||||
Settings
|
||||
}
|
||||
|
||||
impl NetworkTabType {
|
||||
pub fn name(&self) -> String {
|
||||
match *self {
|
||||
NetworkTabType::Node => { t!("network.node") }
|
||||
NetworkTabType::Metrics => { t!("network.metrics") }
|
||||
NetworkTabType::Mining => { t!("network.mining") }
|
||||
NetworkTabType::Settings => { t!("network.settings") }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NetworkContainer {
|
||||
current_tab: Box<dyn NetworkTab>,
|
||||
modal_ids: Vec<&'static str>,
|
||||
}
|
||||
|
||||
impl Default for NetworkContainer {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
current_tab: Box::new(NetworkNode::default()),
|
||||
modal_ids: vec![
|
||||
NetworkNodeSettings::NODE_RESTART_REQUIRED_MODAL,
|
||||
StratumServerSetup::STRATUM_PORT_MODAL,
|
||||
StratumServerSetup::STRATUM_ATTEMPT_TIME_MODAL,
|
||||
StratumServerSetup::STRATUM_MIN_SHARE_MODAL,
|
||||
ServerSetup::API_PORT_MODAL,
|
||||
ServerSetup::API_SECRET_MODAL,
|
||||
ServerSetup::FOREIGN_API_SECRET_MODAL,
|
||||
ServerSetup::FTL_MODAL
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ModalContainer for NetworkContainer {
|
||||
fn modal_ids(&self) -> &Vec<&'static str> {
|
||||
self.modal_ids.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl NetworkContainer {
|
||||
pub fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame, cb: &dyn PlatformCallbacks) {
|
||||
// Show modal content if it's opened.
|
||||
let modal_id = Navigator::is_modal_open();
|
||||
if modal_id.is_some() && self.can_show_modal(modal_id.unwrap()) {
|
||||
Navigator::modal_ui(ui, |ui, modal| {
|
||||
self.current_tab.as_mut().on_modal_ui(ui, modal, cb);
|
||||
});
|
||||
}
|
||||
|
||||
egui::TopBottomPanel::top("network_title")
|
||||
.resizable(false)
|
||||
.frame(egui::Frame {
|
||||
fill: Colors::YELLOW,
|
||||
inner_margin: Margin::same(0.0),
|
||||
outer_margin: Margin::same(0.0),
|
||||
..Default::default()
|
||||
})
|
||||
.show_inside(ui, |ui| {
|
||||
self.title_ui(ui, frame);
|
||||
});
|
||||
|
||||
egui::TopBottomPanel::bottom("network_tabs")
|
||||
.frame(egui::Frame {
|
||||
outer_margin: Margin::same(5.0),
|
||||
..Default::default()
|
||||
})
|
||||
.show_inside(ui, |ui| {
|
||||
self.tabs_ui(ui);
|
||||
});
|
||||
|
||||
egui::CentralPanel::default()
|
||||
.frame(egui::Frame {
|
||||
stroke: View::DEFAULT_STROKE,
|
||||
inner_margin: Margin::same(4.0),
|
||||
fill: Colors::WHITE,
|
||||
..Default::default()
|
||||
})
|
||||
.show_inside(ui, |ui| {
|
||||
self.current_tab.ui(ui, cb);
|
||||
});
|
||||
}
|
||||
|
||||
/// Draw tab buttons in the bottom of the screen.
|
||||
fn tabs_ui(&mut self, ui: &mut egui::Ui) {
|
||||
ui.scope(|ui| {
|
||||
// Setup spacing between tabs.
|
||||
ui.style_mut().spacing.item_spacing = egui::vec2(5.0, 0.0);
|
||||
// Setup vertical padding inside tab button.
|
||||
ui.style_mut().spacing.button_padding = egui::vec2(0.0, 3.0);
|
||||
|
||||
ui.columns(4, |columns| {
|
||||
columns[0].vertical_centered_justified(|ui| {
|
||||
View::tab_button(ui, DATABASE, self.is_current_tab(NetworkTabType::Node), || {
|
||||
self.current_tab = Box::new(NetworkNode::default());
|
||||
});
|
||||
});
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
View::tab_button(ui, GAUGE, self.is_current_tab(NetworkTabType::Metrics), || {
|
||||
self.current_tab = Box::new(NetworkMetrics::default());
|
||||
});
|
||||
});
|
||||
columns[2].vertical_centered_justified(|ui| {
|
||||
View::tab_button(ui, FACTORY, self.is_current_tab(NetworkTabType::Mining), || {
|
||||
self.current_tab = Box::new(NetworkMining::default());
|
||||
});
|
||||
});
|
||||
columns[3].vertical_centered_justified(|ui| {
|
||||
View::tab_button(ui, FADERS, self.is_current_tab(NetworkTabType::Settings), || {
|
||||
self.current_tab = Box::new(NetworkNodeSettings::default());
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// Check if current tab equals providing [`NetworkTabType`].
|
||||
fn is_current_tab(&self, tab_type: NetworkTabType) -> bool {
|
||||
self.current_tab.get_type() == tab_type
|
||||
}
|
||||
|
||||
/// Draw title content.
|
||||
fn title_ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) {
|
||||
StripBuilder::new(ui)
|
||||
.size(Size::exact(52.0))
|
||||
.vertical(|mut strip| {
|
||||
strip.strip(|builder| {
|
||||
builder
|
||||
.size(Size::exact(52.0))
|
||||
.size(Size::remainder())
|
||||
.size(Size::exact(52.0))
|
||||
.horizontal(|mut strip| {
|
||||
strip.cell(|ui| {
|
||||
ui.centered_and_justified(|ui| {
|
||||
View::title_button(ui, DOTS_THREE_OUTLINE_VERTICAL, || {
|
||||
//TODO: Actions for node
|
||||
});
|
||||
});
|
||||
});
|
||||
strip.strip(|builder| {
|
||||
self.title_text_ui(builder);
|
||||
});
|
||||
strip.cell(|ui| {
|
||||
if !View::is_dual_panel_mode(frame) {
|
||||
ui.centered_and_justified(|ui| {
|
||||
View::title_button(ui, CARDHOLDER, || {
|
||||
Navigator::toggle_side_panel();
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// Draw title text.
|
||||
fn title_text_ui(&self, builder: StripBuilder) {
|
||||
builder
|
||||
.size(Size::remainder())
|
||||
.size(Size::exact(32.0))
|
||||
.vertical(|mut strip| {
|
||||
strip.cell(|ui| {
|
||||
ui.add_space(2.0);
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.label(RichText::new(self.current_tab.get_type().name().to_uppercase())
|
||||
.size(18.0)
|
||||
.color(Colors::TITLE));
|
||||
});
|
||||
});
|
||||
strip.cell(|ui| {
|
||||
ui.centered_and_justified(|ui| {
|
||||
let sync_status = Node::get_sync_status();
|
||||
|
||||
// Setup text color animation based on sync status
|
||||
let idle = match sync_status {
|
||||
None => !Node::is_starting(),
|
||||
Some(ss) => ss == SyncStatus::NoSync
|
||||
};
|
||||
let (dark, bright) = (0.3, 1.0);
|
||||
let color_factor = if !idle {
|
||||
lerp(dark..=bright, ui.input().time.cos().abs()) as f32
|
||||
} else {
|
||||
bright as f32
|
||||
};
|
||||
|
||||
// Draw sync text
|
||||
let status_color_rgba = Rgba::from(Colors::TEXT) * color_factor;
|
||||
let status_color = Color32::from(status_color_rgba);
|
||||
View::ellipsize_text(ui, Node::get_sync_status_text(), 15.0, status_color);
|
||||
|
||||
// Repaint based on sync status
|
||||
if idle {
|
||||
ui.ctx().request_repaint_after(Duration::from_millis(600));
|
||||
} else {
|
||||
ui.ctx().request_repaint();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// Content to draw when node is disabled.
|
||||
pub fn disabled_node_ui(ui: &mut egui::Ui) {
|
||||
View::center_content(ui, 162.0, |ui| {
|
||||
let text = t!("network.disabled_server", "dots" => DOTS_THREE_OUTLINE_VERTICAL);
|
||||
ui.label(RichText::new(text)
|
||||
.size(16.0)
|
||||
.color(Colors::INACTIVE_TEXT)
|
||||
);
|
||||
ui.add_space(10.0);
|
||||
View::button(ui, t!("network.enable_node"), Colors::GOLD, || {
|
||||
Node::start();
|
||||
});
|
||||
ui.add_space(2.0);
|
||||
Self::autorun_node_ui(ui);
|
||||
});
|
||||
}
|
||||
|
||||
/// Draw checkbox with setting to run node on app launch.
|
||||
pub fn autorun_node_ui(ui: &mut egui::Ui) {
|
||||
let autostart = AppConfig::autostart_node();
|
||||
View::checkbox(ui, autostart, t!("network.autorun"), || {
|
||||
AppConfig::toggle_node_autostart();
|
||||
});
|
||||
}
|
||||
}
|
|
@ -19,7 +19,8 @@ use grin_servers::DiffBlock;
|
|||
use crate::gui::Colors;
|
||||
use crate::gui::icons::{AT, COINS, CUBE_TRANSPARENT, HASH, HOURGLASS_LOW, HOURGLASS_MEDIUM, TIMER};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::{Modal, Network, NetworkTab, NetworkTabType, View};
|
||||
use crate::gui::views::network::{NetworkTab, NetworkTabType};
|
||||
use crate::gui::views::{Modal, NetworkContainer, View};
|
||||
use crate::node::Node;
|
||||
|
||||
#[derive(Default)]
|
||||
|
@ -36,23 +37,31 @@ impl NetworkTab for NetworkMetrics {
|
|||
|
||||
fn ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
|
||||
let server_stats = Node::get_stats();
|
||||
// Show message when node is not running or loading spinner when metrics are not available.
|
||||
// Show message to enable node when it's not running.
|
||||
if !Node::is_running() {
|
||||
NetworkContainer::disabled_node_ui(ui);
|
||||
return;
|
||||
}
|
||||
|
||||
// Show loading spinner when node is stopping.
|
||||
if Node::is_stopping() {
|
||||
ui.centered_and_justified(|ui| {
|
||||
View::big_loading_spinner(ui);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Show message when metrics are not available.
|
||||
if server_stats.is_none() || Node::is_restarting()
|
||||
|| server_stats.as_ref().unwrap().diff_stats.height == 0 {
|
||||
if !Node::is_running() {
|
||||
Network::disabled_node_ui(ui);
|
||||
} else {
|
||||
View::center_content(ui, 162.0, |ui| {
|
||||
View::big_loading_spinner(ui);
|
||||
if !Node::is_stopping() {
|
||||
ui.add_space(18.0);
|
||||
ui.label(RichText::new(t!("network_metrics.loading"))
|
||||
.size(16.0)
|
||||
.color(Colors::INACTIVE_TEXT)
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
|
@ -20,8 +20,9 @@ use grin_servers::WorkerStats;
|
|||
use crate::gui::Colors;
|
||||
use crate::gui::icons::{BARBELL, CLOCK_AFTERNOON, COMPUTER_TOWER, CPU, CUBE, FADERS, FOLDER_DASHED, FOLDER_NOTCH_MINUS, FOLDER_NOTCH_PLUS, PLUGS, PLUGS_CONNECTED, POLYGON};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::{Modal, Network, NetworkTab, NetworkTabType, View};
|
||||
use crate::gui::views::settings_stratum::StratumServerSetup;
|
||||
use crate::gui::views::{Modal, NetworkContainer, View};
|
||||
use crate::gui::views::network::{NetworkTab, NetworkTabType};
|
||||
use crate::gui::views::network::settings::stratum::StratumServerSetup;
|
||||
use crate::node::{Node, NodeConfig};
|
||||
|
||||
#[derive(Default)]
|
||||
|
@ -37,65 +38,43 @@ impl NetworkTab for NetworkMining {
|
|||
fn ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
|
||||
let server_stats = Node::get_stats();
|
||||
|
||||
// Show message when node is not running or loading spinner when mining is not available.
|
||||
if !server_stats.is_some() || Node::get_sync_status().unwrap() != SyncStatus::NoSync {
|
||||
// Show message to enable node when it's not running.
|
||||
if !Node::is_running() {
|
||||
Network::disabled_node_ui(ui);
|
||||
} else {
|
||||
NetworkContainer::disabled_node_ui(ui);
|
||||
return;
|
||||
}
|
||||
|
||||
// Show loading spinner when node is stopping or stratum server is starting.
|
||||
if Node::is_stopping() || Node::is_stratum_server_starting() {
|
||||
ui.centered_and_justified(|ui| {
|
||||
View::big_loading_spinner(ui);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Show message when mining is not available.
|
||||
if server_stats.is_none() || Node::is_restarting()
|
||||
|| Node::get_sync_status().unwrap() != SyncStatus::NoSync {
|
||||
View::center_content(ui, 162.0, |ui| {
|
||||
View::big_loading_spinner(ui);
|
||||
if !Node::is_stopping() {
|
||||
ui.add_space(18.0);
|
||||
ui.label(RichText::new(t!("network_mining.loading"))
|
||||
.size(16.0)
|
||||
.color(Colors::INACTIVE_TEXT)
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let stratum_stats = &server_stats.as_ref().unwrap().stratum_stats;
|
||||
let stratum_stats = Node::get_stratum_stats();
|
||||
|
||||
// Show stratum server setup when mining server is not running.
|
||||
if !stratum_stats.is_running && !Node::is_stratum_server_starting() {
|
||||
if !stratum_stats.is_running {
|
||||
ScrollArea::vertical()
|
||||
.id_source("stratum_server_setup")
|
||||
.id_source("stratum_setup_scroll")
|
||||
.auto_shrink([false; 2])
|
||||
.show(ui, |ui| {
|
||||
self.stratum_server_setup.ui(ui, cb);
|
||||
|
||||
ui.vertical_centered(|ui| {
|
||||
// Show message about stratum server config.
|
||||
let text = t!("network_mining.server_setting", "settings" => FADERS);
|
||||
ui.label(RichText::new(text)
|
||||
.size(16.0)
|
||||
.color(Colors::INACTIVE_TEXT)
|
||||
);
|
||||
ui.add_space(4.0);
|
||||
|
||||
// Show button to enable stratum server if port is available.
|
||||
if self.stratum_server_setup.is_port_available {
|
||||
ui.add_space(6.0);
|
||||
View::button(ui, t!("network_mining.enable_server"), Colors::GOLD, || {
|
||||
Node::start_stratum_server();
|
||||
});
|
||||
ui.add_space(2.0);
|
||||
|
||||
// Show stratum server autorun checkbox.
|
||||
let stratum_enabled = NodeConfig::is_stratum_autorun_enabled();
|
||||
View::checkbox(ui, stratum_enabled, t!("network.autorun"), || {
|
||||
NodeConfig::toggle_stratum_autorun();
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
}
|
||||
});
|
||||
});
|
||||
return;
|
||||
} else if Node::is_stratum_server_starting() {
|
||||
ui.centered_and_justified(|ui| {
|
||||
View::big_loading_spinner(ui);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
@ -221,7 +200,7 @@ impl NetworkTab for NetworkMining {
|
|||
fn on_modal_ui(&mut self, ui: &mut egui::Ui, modal: &Modal, cb: &dyn PlatformCallbacks) {
|
||||
match modal.id {
|
||||
StratumServerSetup::STRATUM_PORT_MODAL => {
|
||||
self.stratum_server_setup.stratum_port_modal_ui(ui, modal, cb);
|
||||
self.stratum_server_setup.port_modal(ui, modal, cb);
|
||||
},
|
||||
_ => {}
|
||||
}
|
|
@ -19,7 +19,8 @@ use grin_servers::PeerStats;
|
|||
use crate::gui::Colors;
|
||||
use crate::gui::icons::{AT, CUBE, DEVICES, FLOW_ARROW, HANDSHAKE, PACKAGE, PLUGS_CONNECTED, SHARE_NETWORK};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::{Modal, Network, NetworkTab, NetworkTabType, View};
|
||||
use crate::gui::views::{Modal, View};
|
||||
use crate::gui::views::network::{NetworkContainer, NetworkTab, NetworkTabType};
|
||||
use crate::node::Node;
|
||||
|
||||
#[derive(Default)]
|
||||
|
@ -32,15 +33,17 @@ impl NetworkTab for NetworkNode {
|
|||
|
||||
fn ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
|
||||
let server_stats = Node::get_stats();
|
||||
// Show message when node is not running or loading spinner when stats are not available.
|
||||
if !server_stats.is_some() || Node::is_restarting() {
|
||||
// Show message to enable node when it's not running.
|
||||
if !Node::is_running() {
|
||||
Network::disabled_node_ui(ui);
|
||||
} else {
|
||||
NetworkContainer::disabled_node_ui(ui);
|
||||
return;
|
||||
}
|
||||
|
||||
// Show loading spinner when stats are not available.
|
||||
if server_stats.is_none() || Node::is_restarting() || Node::is_stopping() {
|
||||
ui.centered_and_justified(|ui| {
|
||||
View::big_loading_spinner(ui);
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
|
@ -19,16 +19,19 @@ use egui::{RichText, ScrollArea};
|
|||
|
||||
use crate::gui::{Colors, Navigator};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::{Modal, ModalPosition, NetworkTab, NetworkTabType, View};
|
||||
use crate::gui::views::settings_node::NodeSetup;
|
||||
use crate::gui::views::{Modal, ModalPosition, View};
|
||||
use crate::gui::views::network::{NetworkTab, NetworkTabType};
|
||||
use crate::gui::views::network::settings::server::ServerSetup;
|
||||
use crate::gui::views::network::settings::stratum::StratumServerSetup;
|
||||
use crate::node::Node;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct NetworkSettings {
|
||||
node_setup: NodeSetup
|
||||
pub struct NetworkNodeSettings {
|
||||
server_setup: ServerSetup,
|
||||
stratum_server_setup: StratumServerSetup
|
||||
}
|
||||
|
||||
impl NetworkTab for NetworkSettings {
|
||||
impl NetworkTab for NetworkNodeSettings {
|
||||
fn get_type(&self) -> NetworkTabType {
|
||||
NetworkTabType::Settings
|
||||
}
|
||||
|
@ -38,7 +41,8 @@ impl NetworkTab for NetworkSettings {
|
|||
.id_source("network_settings")
|
||||
.auto_shrink([false; 2])
|
||||
.show(ui, |ui| {
|
||||
self.node_setup.ui(ui, cb);
|
||||
self.server_setup.ui(ui, cb);
|
||||
self.stratum_server_setup.ui(ui, cb);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -47,24 +51,35 @@ impl NetworkTab for NetworkSettings {
|
|||
Self::NODE_RESTART_REQUIRED_MODAL => {
|
||||
self.node_restart_required_modal(ui, modal);
|
||||
}
|
||||
NodeSetup::API_PORT_MODAL => {
|
||||
self.node_setup.api_port_modal(ui, modal, cb);
|
||||
|
||||
ServerSetup::API_PORT_MODAL => {
|
||||
self.server_setup.api_port_modal(ui, modal, cb);
|
||||
},
|
||||
NodeSetup::API_SECRET_MODAL => {
|
||||
self.node_setup.secret_modal(ui, modal, cb);
|
||||
ServerSetup::API_SECRET_MODAL => {
|
||||
self.server_setup.secret_modal(ui, modal, cb);
|
||||
},
|
||||
NodeSetup::FOREIGN_API_SECRET_MODAL => {
|
||||
self.node_setup.secret_modal(ui, modal, cb);
|
||||
ServerSetup::FOREIGN_API_SECRET_MODAL => {
|
||||
self.server_setup.secret_modal(ui, modal, cb);
|
||||
},
|
||||
NodeSetup::FTL_MODAL => {
|
||||
self.node_setup.ftl_modal(ui, modal, cb);
|
||||
ServerSetup::FTL_MODAL => {
|
||||
self.server_setup.ftl_modal(ui, modal, cb);
|
||||
},
|
||||
|
||||
StratumServerSetup::STRATUM_PORT_MODAL => {
|
||||
self.stratum_server_setup.port_modal(ui, modal, cb);
|
||||
}
|
||||
StratumServerSetup::STRATUM_ATTEMPT_TIME_MODAL => {
|
||||
self.stratum_server_setup.attempt_modal(ui, modal, cb);
|
||||
}
|
||||
StratumServerSetup::STRATUM_MIN_SHARE_MODAL => {
|
||||
self.stratum_server_setup.min_diff_modal(ui, modal, cb);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NetworkSettings {
|
||||
impl NetworkNodeSettings {
|
||||
pub const NODE_RESTART_REQUIRED_MODAL: &'static str = "node_restart_required";
|
||||
|
||||
/// Reminder to restart enabled node to show on edit setting at [`Modal`].
|
||||
|
@ -82,7 +97,7 @@ impl NetworkSettings {
|
|||
pub fn show_node_restart_required_modal() {
|
||||
if Node::is_running() {
|
||||
// Show modal to apply changes by node restart.
|
||||
let port_modal = Modal::new(NetworkSettings::NODE_RESTART_REQUIRED_MODAL)
|
||||
let port_modal = Modal::new(NetworkNodeSettings::NODE_RESTART_REQUIRED_MODAL)
|
||||
.position(ModalPosition::Center)
|
||||
.title(t!("network.settings"));
|
||||
Navigator::show_modal(port_modal);
|
||||
|
@ -121,19 +136,6 @@ impl NetworkSettings {
|
|||
});
|
||||
}
|
||||
|
||||
/// List of available IP addresses.
|
||||
pub fn get_ip_addrs() -> Vec<IpAddr> {
|
||||
let mut ip_addrs = Vec::new();
|
||||
for net_if in pnet::datalink::interfaces() {
|
||||
for ip in net_if.ips {
|
||||
if ip.is_ipv4() {
|
||||
ip_addrs.push(ip.ip());
|
||||
}
|
||||
}
|
||||
}
|
||||
ip_addrs
|
||||
}
|
||||
|
||||
/// Draw IP addresses as radio buttons.
|
||||
pub fn ip_addrs_ui(ui: &mut egui::Ui,
|
||||
saved_ip: &String,
|
||||
|
@ -147,6 +149,7 @@ impl NetworkSettings {
|
|||
selected_ip_addr = ip_addrs.get(0).unwrap();
|
||||
}
|
||||
|
||||
ui.add_space(2.0);
|
||||
// Show available IP addresses on the system.
|
||||
let _ = ip_addrs.chunks(2).map(|x| {
|
||||
if x.len() == 2 {
|
19
src/gui/views/network/settings.rs
Normal file
19
src/gui/views/network/settings.rs
Normal file
|
@ -0,0 +1,19 @@
|
|||
// Copyright 2023 The Grim Developers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
pub mod stratum;
|
||||
pub mod server;
|
||||
pub mod p2p;
|
||||
pub mod pool;
|
||||
pub mod dandelion;
|
14
src/gui/views/network/settings/dandelion.rs
Normal file
14
src/gui/views/network/settings/dandelion.rs
Normal file
|
@ -0,0 +1,14 @@
|
|||
// Copyright 2023 The Grim Developers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
14
src/gui/views/network/settings/p2p.rs
Normal file
14
src/gui/views/network/settings/p2p.rs
Normal file
|
@ -0,0 +1,14 @@
|
|||
// Copyright 2023 The Grim Developers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
14
src/gui/views/network/settings/pool.rs
Normal file
14
src/gui/views/network/settings/pool.rs
Normal file
|
@ -0,0 +1,14 @@
|
|||
// Copyright 2023 The Grim Developers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
|
@ -15,16 +15,17 @@
|
|||
use eframe::emath::Align;
|
||||
use egui::{Id, Layout, RichText, TextStyle, Widget};
|
||||
use grin_core::global::ChainTypes;
|
||||
|
||||
use crate::AppConfig;
|
||||
use crate::gui::{Colors, Navigator};
|
||||
use crate::gui::icons::{CLIPBOARD_TEXT, COMPUTER_TOWER, COPY, POWER, SHIELD, SHIELD_SLASH};
|
||||
use crate::gui::icons::{CLIPBOARD_TEXT, CLOCK_CLOCKWISE, COMPUTER_TOWER, COPY, PLUG, POWER, SHIELD, SHIELD_SLASH};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::{Modal, ModalPosition, Network, View};
|
||||
use crate::gui::views::network_settings::NetworkSettings;
|
||||
use crate::gui::views::{Modal, ModalPosition, NetworkContainer, View};
|
||||
use crate::gui::views::network::node_settings::NetworkNodeSettings;
|
||||
use crate::node::{Node, NodeConfig};
|
||||
|
||||
/// Integrated node server setup ui section.
|
||||
pub struct NodeSetup {
|
||||
pub struct ServerSetup {
|
||||
/// API port to be used inside edit modal.
|
||||
api_port_edit: String,
|
||||
/// Flag to check if API port is available inside edit modal.
|
||||
|
@ -43,7 +44,7 @@ pub struct NodeSetup {
|
|||
ftl_edit: String,
|
||||
}
|
||||
|
||||
impl Default for NodeSetup {
|
||||
impl Default for ServerSetup {
|
||||
fn default() -> Self {
|
||||
let (api_ip, api_port) = NodeConfig::get_api_address();
|
||||
let is_api_port_available = NodeConfig::is_api_port_available(&api_ip, &api_port);
|
||||
|
@ -58,7 +59,7 @@ impl Default for NodeSetup {
|
|||
}
|
||||
}
|
||||
|
||||
impl NodeSetup {
|
||||
impl ServerSetup {
|
||||
pub const API_PORT_MODAL: &'static str = "api_port";
|
||||
pub const API_SECRET_MODAL: &'static str = "api_secret";
|
||||
pub const FOREIGN_API_SECRET_MODAL: &'static str = "foreign_api_secret";
|
||||
|
@ -67,7 +68,7 @@ impl NodeSetup {
|
|||
pub fn ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
|
||||
View::sub_title(ui, format!("{} {}", COMPUTER_TOWER, t!("network_settings.server")));
|
||||
View::horizontal_line(ui, Colors::ITEM_STROKE);
|
||||
ui.add_space(8.0);
|
||||
ui.add_space(4.0);
|
||||
|
||||
// Show chain type setup.
|
||||
self.chain_type_ui(ui);
|
||||
|
@ -75,8 +76,9 @@ impl NodeSetup {
|
|||
// Show loading indicator or controls to stop/start/restart node.
|
||||
if Node::is_stopping() || Node::is_restarting() || Node::is_starting() {
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.add_space(6.0);
|
||||
ui.add_space(8.0);
|
||||
View::small_loading_spinner(ui);
|
||||
ui.add_space(2.0);
|
||||
});
|
||||
} else {
|
||||
if Node::is_running() {
|
||||
|
@ -110,21 +112,29 @@ impl NodeSetup {
|
|||
}
|
||||
|
||||
// Autorun node setup.
|
||||
ui.add_space(4.0);
|
||||
ui.vertical_centered(|ui| {
|
||||
Network::autorun_node_ui(ui);
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
NetworkContainer::autorun_node_ui(ui);
|
||||
if Node::is_running() {
|
||||
ui.add_space(2.0);
|
||||
ui.label(RichText::new(t!("network_settings.restart_node_required"))
|
||||
.size(16.0)
|
||||
.color(Colors::INACTIVE_TEXT)
|
||||
);
|
||||
ui.add_space(4.0);
|
||||
}
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
|
||||
let addrs = NetworkSettings::get_ip_addrs();
|
||||
let addrs = NodeConfig::get_ip_addrs();
|
||||
if addrs.is_empty() {
|
||||
// Show message when IP addresses are not available on the system.
|
||||
NetworkSettings::no_ip_address_ui(ui);
|
||||
NetworkNodeSettings::no_ip_address_ui(ui);
|
||||
|
||||
ui.add_space(4.0);
|
||||
} else {
|
||||
View::horizontal_line(ui, Colors::ITEM_STROKE);
|
||||
ui.add_space(4.0);
|
||||
ui.add_space(6.0);
|
||||
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.label(RichText::new(t!("network_settings.api_ip"))
|
||||
|
@ -132,15 +142,13 @@ impl NodeSetup {
|
|||
.color(Colors::GRAY)
|
||||
);
|
||||
ui.add_space(6.0);
|
||||
|
||||
// Show API IP addresses to select.
|
||||
let (api_ip, api_port) = NodeConfig::get_api_address();
|
||||
NetworkSettings::ip_addrs_ui(ui, &api_ip, &addrs, |selected_ip| {
|
||||
NetworkNodeSettings::ip_addrs_ui(ui, &api_ip, &addrs, |selected_ip| {
|
||||
let api_available = NodeConfig::is_api_port_available(selected_ip, &api_port);
|
||||
self.is_api_port_available = api_available;
|
||||
NodeConfig::save_api_address(selected_ip, &api_port);
|
||||
if api_available {
|
||||
NetworkSettings::show_node_restart_required_modal();
|
||||
}
|
||||
});
|
||||
|
||||
ui.label(RichText::new(t!("network_settings.api_port"))
|
||||
|
@ -209,6 +217,7 @@ impl NodeSetup {
|
|||
let saved_chain_type = AppConfig::chain_type();
|
||||
let mut selected_chain_type = saved_chain_type;
|
||||
|
||||
ui.add_space(8.0);
|
||||
ui.columns(2, |columns| {
|
||||
columns[0].vertical_centered(|ui| {
|
||||
let main_type = ChainTypes::Mainnet;
|
||||
|
@ -219,19 +228,18 @@ impl NodeSetup {
|
|||
View::radio_value(ui, &mut selected_chain_type, test_type, "Testnet".to_string());
|
||||
})
|
||||
});
|
||||
ui.add_space(4.0);
|
||||
ui.add_space(8.0);
|
||||
|
||||
if saved_chain_type != selected_chain_type {
|
||||
AppConfig::change_chain_type(&selected_chain_type);
|
||||
NetworkSettings::show_node_restart_required_modal();
|
||||
NetworkNodeSettings::show_node_restart_required_modal();
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw API port setup ui.
|
||||
fn api_port_setup_ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
|
||||
let (_, port) = NodeConfig::get_api_address();
|
||||
// Show button to enter API server port.
|
||||
View::button(ui, port.clone(), Colors::BUTTON, || {
|
||||
View::button(ui, format!("{} {}", PLUG, port.clone()), Colors::BUTTON, || {
|
||||
// Setup values for modal.
|
||||
self.api_port_edit = port;
|
||||
self.api_port_available_edit = self.is_api_port_available;
|
||||
|
@ -285,7 +293,7 @@ impl NodeSetup {
|
|||
.size(16.0)
|
||||
.color(Colors::RED));
|
||||
} else {
|
||||
NetworkSettings::node_restart_required_ui(ui);
|
||||
NetworkNodeSettings::node_restart_required_ui(ui);
|
||||
}
|
||||
ui.add_space(12.0);
|
||||
});
|
||||
|
@ -329,7 +337,6 @@ impl NodeSetup {
|
|||
|
||||
/// Draw API secret token setup ui.
|
||||
fn secret_ui(&mut self, modal_id: &'static str, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
|
||||
// Setup values for modal
|
||||
let secret_value = match modal_id {
|
||||
Self::API_SECRET_MODAL => NodeConfig::get_api_secret(),
|
||||
_ => NodeConfig::get_foreign_api_secret()
|
||||
|
@ -341,7 +348,6 @@ impl NodeSetup {
|
|||
format!("{} {}", SHIELD_SLASH, t!("network_settings.disabled"))
|
||||
};
|
||||
|
||||
// Show button to open secret modal.
|
||||
View::button(ui, secret_text, Colors::BUTTON, || {
|
||||
// Setup values for modal.
|
||||
match modal_id {
|
||||
|
@ -428,7 +434,7 @@ impl NodeSetup {
|
|||
});
|
||||
|
||||
// Show reminder to restart enabled node.
|
||||
NetworkSettings::node_restart_required_ui(ui);
|
||||
NetworkNodeSettings::node_restart_required_ui(ui);
|
||||
|
||||
ui.add_space(12.0);
|
||||
});
|
||||
|
@ -472,8 +478,7 @@ impl NodeSetup {
|
|||
/// Draw FTL setup ui.
|
||||
fn ftl_ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
|
||||
let ftl = NodeConfig::get_ftl();
|
||||
// Show button to enter FTL value.
|
||||
View::button(ui, ftl.clone(), Colors::BUTTON, || {
|
||||
View::button(ui, format!("{} {}", CLOCK_CLOCKWISE, ftl.clone()), Colors::BUTTON, || {
|
||||
// Setup values for modal.
|
||||
self.ftl_edit = ftl;
|
||||
// Show stratum port modal.
|
||||
|
@ -519,7 +524,7 @@ impl NodeSetup {
|
|||
.size(18.0)
|
||||
.color(Colors::RED));
|
||||
} else {
|
||||
NetworkSettings::node_restart_required_ui(ui);
|
||||
NetworkNodeSettings::node_restart_required_ui(ui);
|
||||
}
|
||||
ui.add_space(12.0);
|
||||
});
|
||||
|
@ -559,9 +564,9 @@ impl NodeSetup {
|
|||
let validate = NodeConfig::is_full_chain_validation();
|
||||
View::checkbox(ui, validate, t!("network_settings.full_validation"), || {
|
||||
NodeConfig::toggle_full_chain_validation();
|
||||
NetworkSettings::show_node_restart_required_modal();
|
||||
NetworkNodeSettings::show_node_restart_required_modal();
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
ui.add_space(4.0);
|
||||
ui.label(RichText::new(t!("network_settings.full_validation_description"))
|
||||
.size(16.0)
|
||||
.color(Colors::INACTIVE_TEXT)
|
||||
|
@ -573,9 +578,9 @@ impl NodeSetup {
|
|||
let archive_mode = NodeConfig::is_archive_mode();
|
||||
View::checkbox(ui, archive_mode, t!("network_settings.archive_mode"), || {
|
||||
NodeConfig::toggle_archive_mode();
|
||||
NetworkSettings::show_node_restart_required_modal();
|
||||
NetworkNodeSettings::show_node_restart_required_modal();
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
ui.add_space(4.0);
|
||||
ui.label(RichText::new(t!("network_settings.archive_mode_desc"))
|
||||
.size(16.0)
|
||||
.color(Colors::INACTIVE_TEXT)
|
413
src/gui/views/network/settings/stratum.rs
Normal file
413
src/gui/views/network/settings/stratum.rs
Normal file
|
@ -0,0 +1,413 @@
|
|||
// Copyright 2023 The Grim Developers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use egui::{Id, RichText, TextStyle, Widget};
|
||||
|
||||
use crate::gui::{Colors, Navigator};
|
||||
use crate::gui::icons::{BARBELL, HARD_DRIVES, PLUG, TIMER};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::{Modal, ModalPosition, View};
|
||||
use crate::gui::views::network::node_settings::NetworkNodeSettings;
|
||||
use crate::node::{Node, NodeConfig};
|
||||
|
||||
/// Stratum server setup ui section.
|
||||
pub struct StratumServerSetup {
|
||||
/// Stratum port value to be used inside edit modal.
|
||||
stratum_port_edit: String,
|
||||
/// Flag to check if stratum port is available inside edit modal.
|
||||
stratum_port_available_edit: bool,
|
||||
|
||||
/// Flag to check if stratum port is available from saved config value.
|
||||
pub(crate) is_port_available: bool,
|
||||
|
||||
/// Attempt time value to be used inside edit modal.
|
||||
attempt_time_edit: String,
|
||||
|
||||
/// Minimum share difficulty value to be used inside edit modal.
|
||||
min_share_diff_edit: String
|
||||
}
|
||||
|
||||
impl Default for StratumServerSetup {
|
||||
fn default() -> Self {
|
||||
let (ip, port) = NodeConfig::get_stratum_address();
|
||||
let is_port_available = NodeConfig::is_stratum_port_available(&ip, &port);
|
||||
let attempt_time = NodeConfig::get_stratum_attempt_time();
|
||||
let min_share_diff = NodeConfig::get_stratum_min_share_diff();
|
||||
Self {
|
||||
stratum_port_edit: port,
|
||||
stratum_port_available_edit: is_port_available,
|
||||
is_port_available,
|
||||
attempt_time_edit: attempt_time,
|
||||
min_share_diff_edit: min_share_diff
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl StratumServerSetup {
|
||||
/// Identifier for stratum port [`Modal`].
|
||||
pub const STRATUM_PORT_MODAL: &'static str = "stratum_port";
|
||||
/// Identifier for attempt time [`Modal`].
|
||||
pub const STRATUM_ATTEMPT_TIME_MODAL: &'static str = "stratum_attempt_time";
|
||||
/// Identifier for minimum share difficulty [`Modal`].
|
||||
pub const STRATUM_MIN_SHARE_MODAL: &'static str = "stratum_min_share";
|
||||
|
||||
pub fn ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
|
||||
View::sub_title(ui, format!("{} {}", HARD_DRIVES, t!("network_mining.server_setup")));
|
||||
View::horizontal_line(ui, Colors::ITEM_STROKE);
|
||||
ui.add_space(6.0);
|
||||
|
||||
ui.vertical_centered(|ui| {
|
||||
// Show button to enable stratum server if port is available and server is not running.
|
||||
if self.is_port_available && !Node::is_stratum_server_starting() && Node::is_running()
|
||||
&& !Node::get_stratum_stats().is_running {
|
||||
ui.add_space(6.0);
|
||||
View::button(ui, t!("network_mining.enable_server"), Colors::GOLD, || {
|
||||
Node::start_stratum_server();
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
}
|
||||
|
||||
// Show stratum server autorun checkbox.
|
||||
let stratum_enabled = NodeConfig::is_stratum_autorun_enabled();
|
||||
View::checkbox(ui, stratum_enabled, t!("network.autorun"), || {
|
||||
NodeConfig::toggle_stratum_autorun();
|
||||
});
|
||||
ui.add_space(4.0);
|
||||
|
||||
// Show message to restart node after changing of stratum settings
|
||||
ui.label(RichText::new(t!("network_mining.info_settings"))
|
||||
.size(16.0)
|
||||
.color(Colors::INACTIVE_TEXT)
|
||||
);
|
||||
ui.add_space(8.0);
|
||||
});
|
||||
|
||||
View::horizontal_line(ui, Colors::ITEM_STROKE);
|
||||
ui.add_space(6.0);
|
||||
|
||||
// Show message when IP addresses are not available on the system.
|
||||
let all_ips = NodeConfig::get_ip_addrs();
|
||||
if all_ips.is_empty() {
|
||||
NetworkNodeSettings::no_ip_address_ui(ui);
|
||||
return;
|
||||
}
|
||||
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.label(RichText::new(t!("network_settings.ip"))
|
||||
.size(16.0)
|
||||
.color(Colors::GRAY)
|
||||
);
|
||||
ui.add_space(6.0);
|
||||
// Show stratum IP addresses to select.
|
||||
let (ip, port) = NodeConfig::get_stratum_address();
|
||||
NetworkNodeSettings::ip_addrs_ui(ui, &ip, &all_ips, |selected_ip| {
|
||||
NodeConfig::save_stratum_address(selected_ip, &port);
|
||||
self.is_port_available = NodeConfig::is_stratum_port_available(selected_ip, &port);
|
||||
|
||||
});
|
||||
// Show stratum port setup.
|
||||
self.port_setup_ui(ui, cb);
|
||||
|
||||
View::horizontal_line(ui, Colors::ITEM_STROKE);
|
||||
ui.add_space(6.0);
|
||||
|
||||
// Show attempt time setup.
|
||||
self.attempt_time_ui(ui, cb);
|
||||
|
||||
View::horizontal_line(ui, Colors::ITEM_STROKE);
|
||||
ui.add_space(6.0);
|
||||
|
||||
// Show minimum acceptable share difficulty setup.
|
||||
self.min_diff_ui(ui, cb);
|
||||
});
|
||||
}
|
||||
|
||||
/// Draw stratum port value setup ui.
|
||||
fn port_setup_ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
|
||||
ui.label(RichText::new(t!("network_settings.port"))
|
||||
.size(16.0)
|
||||
.color(Colors::GRAY)
|
||||
);
|
||||
ui.add_space(6.0);
|
||||
|
||||
let (_, port) = NodeConfig::get_stratum_address();
|
||||
View::button(ui, format!("{} {}", PLUG, port.clone()), Colors::BUTTON, || {
|
||||
// Setup values for modal.
|
||||
self.stratum_port_edit = port;
|
||||
self.stratum_port_available_edit = self.is_port_available;
|
||||
// Show stratum port modal.
|
||||
let port_modal = Modal::new(Self::STRATUM_PORT_MODAL)
|
||||
.position(ModalPosition::CenterTop)
|
||||
.title(t!("network_settings.change_value"));
|
||||
Navigator::show_modal(port_modal);
|
||||
cb.show_keyboard();
|
||||
});
|
||||
ui.add_space(12.0);
|
||||
|
||||
// Show error when stratum server port is unavailable.
|
||||
if !self.is_port_available {
|
||||
ui.add_space(6.0);
|
||||
ui.label(RichText::new(t!("network_settings.port_unavailable"))
|
||||
.size(16.0)
|
||||
.color(Colors::RED));
|
||||
ui.add_space(12.0);
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw stratum port [`Modal`] content ui.
|
||||
pub fn port_modal(&mut self, ui: &mut egui::Ui, modal: &Modal, cb: &dyn PlatformCallbacks) {
|
||||
ui.add_space(6.0);
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.label(RichText::new(t!("network_settings.stratum_port"))
|
||||
.size(18.0)
|
||||
.color(Colors::GRAY));
|
||||
ui.add_space(8.0);
|
||||
|
||||
// Draw stratum port text edit.
|
||||
let text_edit_resp = egui::TextEdit::singleline(&mut self.stratum_port_edit)
|
||||
.id(Id::from(modal.id))
|
||||
.font(TextStyle::Heading)
|
||||
.desired_width(58.0)
|
||||
.cursor_at_end(true)
|
||||
.ui(ui);
|
||||
text_edit_resp.request_focus();
|
||||
if text_edit_resp.clicked() {
|
||||
cb.show_keyboard();
|
||||
}
|
||||
|
||||
// Show error when specified port is unavailable.
|
||||
if !self.stratum_port_available_edit {
|
||||
ui.add_space(12.0);
|
||||
ui.label(RichText::new(t!("network_settings.port_unavailable"))
|
||||
.size(18.0)
|
||||
.color(Colors::RED));
|
||||
}
|
||||
|
||||
ui.add_space(12.0);
|
||||
|
||||
// Show modal buttons.
|
||||
ui.scope(|ui| {
|
||||
// Setup spacing between buttons.
|
||||
ui.spacing_mut().item_spacing = egui::Vec2::new(8.0, 0.0);
|
||||
|
||||
// Save button callback
|
||||
let on_save = || {
|
||||
// Check if port is available.
|
||||
let (stratum_ip, _) = NodeConfig::get_stratum_address();
|
||||
let available = NodeConfig::is_stratum_port_available(
|
||||
&stratum_ip,
|
||||
&self.stratum_port_edit
|
||||
);
|
||||
self.stratum_port_available_edit = available;
|
||||
|
||||
// Save port at config if it's available.
|
||||
if available {
|
||||
NodeConfig::save_stratum_address(&stratum_ip, &self.stratum_port_edit);
|
||||
|
||||
self.is_port_available = true;
|
||||
cb.hide_keyboard();
|
||||
modal.close();
|
||||
}
|
||||
};
|
||||
|
||||
ui.columns(2, |columns| {
|
||||
columns[0].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.cancel"), Colors::WHITE, || {
|
||||
// Close modal.
|
||||
cb.hide_keyboard();
|
||||
modal.close();
|
||||
});
|
||||
});
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.save"), Colors::WHITE, on_save);
|
||||
});
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// Draw attempt time value setup ui.
|
||||
fn attempt_time_ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
|
||||
ui.label(RichText::new(t!("network_settings.attempt_time_desc"))
|
||||
.size(16.0)
|
||||
.color(Colors::GRAY)
|
||||
);
|
||||
ui.add_space(6.0);
|
||||
|
||||
let time = NodeConfig::get_stratum_attempt_time();
|
||||
View::button(ui, format!("{} {}", TIMER, time.clone()), Colors::BUTTON, || {
|
||||
// Setup values for modal.
|
||||
self.attempt_time_edit = time;
|
||||
|
||||
// Show attempt time modal.
|
||||
let time_modal = Modal::new(Self::STRATUM_ATTEMPT_TIME_MODAL)
|
||||
.position(ModalPosition::CenterTop)
|
||||
.title(t!("network_settings.change_value"));
|
||||
Navigator::show_modal(time_modal);
|
||||
cb.show_keyboard();
|
||||
});
|
||||
ui.add_space(12.0);
|
||||
}
|
||||
|
||||
/// Draw attempt time [`Modal`] content ui.
|
||||
pub fn attempt_modal(&mut self, ui: &mut egui::Ui, modal: &Modal, cb: &dyn PlatformCallbacks) {
|
||||
ui.add_space(6.0);
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.label(RichText::new(t!("network_settings.attempt_time"))
|
||||
.size(18.0)
|
||||
.color(Colors::GRAY));
|
||||
ui.add_space(8.0);
|
||||
|
||||
// Draw stratum port text edit.
|
||||
let text_edit_resp = egui::TextEdit::singleline(&mut self.attempt_time_edit)
|
||||
.id(Id::from(modal.id))
|
||||
.font(TextStyle::Heading)
|
||||
.desired_width(34.0)
|
||||
.cursor_at_end(true)
|
||||
.ui(ui);
|
||||
text_edit_resp.request_focus();
|
||||
if text_edit_resp.clicked() {
|
||||
cb.show_keyboard();
|
||||
}
|
||||
|
||||
// Show error when specified value is not valid or reminder to restart enabled node.
|
||||
if self.attempt_time_edit.parse::<u32>().is_err() {
|
||||
ui.add_space(12.0);
|
||||
ui.label(RichText::new(t!("network_settings.not_valid_value"))
|
||||
.size(18.0)
|
||||
.color(Colors::RED));
|
||||
} else {
|
||||
NetworkNodeSettings::node_restart_required_ui(ui);
|
||||
}
|
||||
ui.add_space(12.0);
|
||||
});
|
||||
|
||||
// Show modal buttons.
|
||||
ui.scope(|ui| {
|
||||
// Setup spacing between buttons.
|
||||
ui.spacing_mut().item_spacing = egui::Vec2::new(8.0, 0.0);
|
||||
|
||||
// Save button callback
|
||||
let on_save = || {
|
||||
if let Ok(time) = self.attempt_time_edit.parse::<u32>() {
|
||||
NodeConfig::save_stratum_attempt_time(time);
|
||||
cb.hide_keyboard();
|
||||
modal.close();
|
||||
}
|
||||
};
|
||||
|
||||
ui.columns(2, |columns| {
|
||||
columns[0].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.cancel"), Colors::WHITE, || {
|
||||
// Close modal.
|
||||
cb.hide_keyboard();
|
||||
modal.close();
|
||||
});
|
||||
});
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.save"), Colors::WHITE, on_save);
|
||||
});
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
});
|
||||
}
|
||||
|
||||
/// Draw minimum share difficulty value setup ui.
|
||||
fn min_diff_ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
|
||||
ui.label(RichText::new(t!("network_settings.min_share_diff"))
|
||||
.size(16.0)
|
||||
.color(Colors::GRAY)
|
||||
);
|
||||
ui.add_space(6.0);
|
||||
|
||||
let diff = NodeConfig::get_stratum_min_share_diff();
|
||||
View::button(ui, format!("{} {}", BARBELL, diff.clone()), Colors::BUTTON, || {
|
||||
// Setup values for modal.
|
||||
self.min_share_diff_edit = diff;
|
||||
|
||||
// Show attempt time modal.
|
||||
let diff_modal = Modal::new(Self::STRATUM_MIN_SHARE_MODAL)
|
||||
.position(ModalPosition::CenterTop)
|
||||
.title(t!("network_settings.change_value"));
|
||||
Navigator::show_modal(diff_modal);
|
||||
cb.show_keyboard();
|
||||
});
|
||||
ui.add_space(12.0);
|
||||
}
|
||||
|
||||
/// Draw minimum acceptable share difficulty [`Modal`] content ui.
|
||||
pub fn min_diff_modal(&mut self, ui: &mut egui::Ui, modal: &Modal, cb: &dyn PlatformCallbacks) {
|
||||
ui.add_space(6.0);
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.label(RichText::new(t!("network_settings.min_share_diff"))
|
||||
.size(18.0)
|
||||
.color(Colors::GRAY));
|
||||
ui.add_space(8.0);
|
||||
|
||||
// Draw stratum port text edit.
|
||||
let text_edit_resp = egui::TextEdit::singleline(&mut self.min_share_diff_edit)
|
||||
.id(Id::from(modal.id))
|
||||
.font(TextStyle::Heading)
|
||||
.desired_width(34.0)
|
||||
.cursor_at_end(true)
|
||||
.ui(ui);
|
||||
text_edit_resp.request_focus();
|
||||
if text_edit_resp.clicked() {
|
||||
cb.show_keyboard();
|
||||
}
|
||||
|
||||
// Show error when specified value is not valid or reminder to restart enabled node.
|
||||
if self.min_share_diff_edit.parse::<u64>().is_err() {
|
||||
ui.add_space(12.0);
|
||||
ui.label(RichText::new(t!("network_settings.not_valid_value"))
|
||||
.size(18.0)
|
||||
.color(Colors::RED));
|
||||
} else {
|
||||
NetworkNodeSettings::node_restart_required_ui(ui);
|
||||
}
|
||||
ui.add_space(12.0);
|
||||
});
|
||||
|
||||
// Show modal buttons.
|
||||
ui.scope(|ui| {
|
||||
// Setup spacing between buttons.
|
||||
ui.spacing_mut().item_spacing = egui::Vec2::new(8.0, 0.0);
|
||||
|
||||
// Save button callback
|
||||
let on_save = || {
|
||||
if let Ok(diff) = self.min_share_diff_edit.parse::<u64>() {
|
||||
NodeConfig::save_stratum_min_share_diff(diff);
|
||||
cb.hide_keyboard();
|
||||
modal.close();
|
||||
}
|
||||
};
|
||||
|
||||
ui.columns(2, |columns| {
|
||||
columns[0].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.cancel"), Colors::WHITE, || {
|
||||
// Close modal.
|
||||
cb.hide_keyboard();
|
||||
modal.close();
|
||||
});
|
||||
});
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.save"), Colors::WHITE, on_save);
|
||||
});
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,189 +0,0 @@
|
|||
// Copyright 2023 The Grim Developers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use egui::{RichText, TextStyle, Widget};
|
||||
|
||||
use crate::gui::{Colors, Navigator};
|
||||
use crate::gui::icons::WRENCH;
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::{Modal, ModalPosition, View};
|
||||
use crate::gui::views::network_settings::NetworkSettings;
|
||||
use crate::node::NodeConfig;
|
||||
|
||||
/// Stratum server setup ui section.
|
||||
pub struct StratumServerSetup {
|
||||
/// Stratum port to be used inside edit modal.
|
||||
stratum_port_edit: String,
|
||||
/// Flag to check if stratum port is available inside edit modal.
|
||||
stratum_port_available_edit: bool,
|
||||
|
||||
/// Flag to check if stratum port is available from saved config value.
|
||||
pub(crate) is_port_available: bool
|
||||
}
|
||||
|
||||
impl Default for StratumServerSetup {
|
||||
fn default() -> Self {
|
||||
let (ip, port) = NodeConfig::get_stratum_address();
|
||||
let is_port_available = NodeConfig::is_stratum_port_available(&ip, &port);
|
||||
Self {
|
||||
stratum_port_edit: port,
|
||||
stratum_port_available_edit: is_port_available,
|
||||
is_port_available
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl StratumServerSetup {
|
||||
/// Identifier for stratum port [`Modal`].
|
||||
pub const STRATUM_PORT_MODAL: &'static str = "stratum_port";
|
||||
|
||||
pub fn ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
|
||||
View::sub_title(ui, format!("{} {}", WRENCH, t!("network_mining.server_setup")));
|
||||
View::horizontal_line(ui, Colors::ITEM_STROKE);
|
||||
ui.add_space(4.0);
|
||||
|
||||
// Show message when IP addresses are not available on the system.
|
||||
let all_ips = NetworkSettings::get_ip_addrs();
|
||||
if all_ips.is_empty() {
|
||||
NetworkSettings::no_ip_address_ui(ui);
|
||||
return;
|
||||
}
|
||||
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.label(RichText::new(t!("network_settings.ip"))
|
||||
.size(16.0)
|
||||
.color(Colors::GRAY)
|
||||
);
|
||||
ui.add_space(6.0);
|
||||
// Show stratum IP addresses to select.
|
||||
let (ip, port) = NodeConfig::get_stratum_address();
|
||||
NetworkSettings::ip_addrs_ui(ui, &ip, &all_ips, |selected_ip| {
|
||||
self.is_port_available = NodeConfig::is_stratum_port_available(selected_ip, &port);
|
||||
NodeConfig::save_stratum_address(selected_ip, &port);
|
||||
});
|
||||
|
||||
ui.label(RichText::new(t!("network_settings.port"))
|
||||
.size(16.0)
|
||||
.color(Colors::GRAY)
|
||||
);
|
||||
ui.add_space(6.0);
|
||||
// Show stratum port setup.
|
||||
self.port_setup_ui(ui, cb);
|
||||
|
||||
View::horizontal_line(ui, Colors::ITEM_STROKE);
|
||||
ui.add_space(6.0);
|
||||
});
|
||||
}
|
||||
|
||||
/// Draw stratum port setup ui.
|
||||
fn port_setup_ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
|
||||
let (_, port) = NodeConfig::get_stratum_address();
|
||||
// Show button to enter stratum server port.
|
||||
View::button(ui, port.clone(), Colors::BUTTON, || {
|
||||
// Setup values for modal.
|
||||
self.stratum_port_edit = port;
|
||||
self.stratum_port_available_edit = self.is_port_available;
|
||||
// Show stratum port modal.
|
||||
let port_modal = Modal::new(Self::STRATUM_PORT_MODAL)
|
||||
.position(ModalPosition::CenterTop)
|
||||
.title(t!("network_settings.change_value"));
|
||||
Navigator::show_modal(port_modal);
|
||||
cb.show_keyboard();
|
||||
});
|
||||
ui.add_space(14.0);
|
||||
|
||||
// Show error when stratum server port is unavailable.
|
||||
if !self.is_port_available {
|
||||
ui.label(RichText::new(t!("network_settings.port_unavailable"))
|
||||
.size(16.0)
|
||||
.color(Colors::RED));
|
||||
ui.add_space(12.0);
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw stratum port [`Modal`] content ui.
|
||||
pub fn stratum_port_modal_ui(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
modal: &Modal,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
ui.add_space(6.0);
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.label(RichText::new(t!("network_settings.stratum_port"))
|
||||
.size(18.0)
|
||||
.color(Colors::GRAY));
|
||||
ui.add_space(8.0);
|
||||
|
||||
// Draw stratum port text edit.
|
||||
let text_edit_resp = egui::TextEdit::singleline(&mut self.stratum_port_edit)
|
||||
.font(TextStyle::Heading)
|
||||
.desired_width(58.0)
|
||||
.cursor_at_end(true)
|
||||
.ui(ui);
|
||||
text_edit_resp.request_focus();
|
||||
if text_edit_resp.clicked() {
|
||||
cb.show_keyboard();
|
||||
}
|
||||
|
||||
// Show error when specified port is unavailable.
|
||||
if !self.stratum_port_available_edit {
|
||||
ui.add_space(12.0);
|
||||
ui.label(RichText::new(t!("network_settings.port_unavailable"))
|
||||
.size(18.0)
|
||||
.color(Colors::RED));
|
||||
}
|
||||
|
||||
ui.add_space(12.0);
|
||||
|
||||
// Show modal buttons.
|
||||
ui.scope(|ui| {
|
||||
// Setup spacing between buttons.
|
||||
ui.spacing_mut().item_spacing = egui::Vec2::new(8.0, 0.0);
|
||||
|
||||
// Save button callback
|
||||
let on_save = || {
|
||||
// Check if port is available.
|
||||
let (stratum_ip, _) = NodeConfig::get_stratum_address();
|
||||
let available = NodeConfig::is_stratum_port_available(
|
||||
&stratum_ip,
|
||||
&self.stratum_port_edit
|
||||
);
|
||||
self.stratum_port_available_edit = available;
|
||||
|
||||
// Save port at config if it's available.
|
||||
if available {
|
||||
NodeConfig::save_stratum_address(&stratum_ip, &self.stratum_port_edit);
|
||||
|
||||
self.is_port_available = true;
|
||||
cb.hide_keyboard();
|
||||
modal.close();
|
||||
}
|
||||
};
|
||||
|
||||
ui.columns(2, |columns| {
|
||||
columns[0].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.cancel"), Colors::WHITE, || {
|
||||
// Close modal.
|
||||
cb.hide_keyboard();
|
||||
modal.close();
|
||||
});
|
||||
});
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.save"), Colors::WHITE, on_save);
|
||||
});
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
|
@ -18,13 +18,13 @@ use egui_extras::{Size, StripBuilder};
|
|||
use crate::gui::Colors;
|
||||
use crate::gui::views::View;
|
||||
|
||||
pub struct TitlePanelAction<'action> {
|
||||
pub(crate) icon: Box<&'action str>,
|
||||
pub struct TitlePanelAction {
|
||||
pub(crate) icon: Box<&'static str>,
|
||||
pub(crate) on_click: Box<dyn Fn()>,
|
||||
}
|
||||
|
||||
impl<'action> TitlePanelAction<'action> {
|
||||
pub fn new(icon: &'action str, on_click: fn()) -> Option<Self> {
|
||||
impl TitlePanelAction {
|
||||
pub fn new(icon: &'static str, on_click: fn()) -> Option<Self> {
|
||||
Option::from(Self { icon: Box::new(icon), on_click: Box::new(on_click) })
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ use egui::epaint::{Color32, FontId, RectShape, Rounding, Stroke};
|
|||
use egui::epaint::text::TextWrapping;
|
||||
use egui::text::{LayoutJob, TextFormat};
|
||||
|
||||
use crate::gui::{Colors, Navigator};
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::icons::{CHECK_SQUARE, SQUARE};
|
||||
|
||||
pub struct View;
|
||||
|
@ -93,16 +93,16 @@ impl View {
|
|||
/// Tab button with white background fill color, contains only icon.
|
||||
pub fn tab_button(ui: &mut egui::Ui, icon: &str, active: bool, action: impl FnOnce()) {
|
||||
let text_color = match active {
|
||||
true => { Colors::TITLE }
|
||||
false => { Colors::TEXT }
|
||||
true => Colors::TITLE,
|
||||
false => Colors::TEXT
|
||||
};
|
||||
let stroke = match active {
|
||||
true => { Stroke::NONE }
|
||||
false => { Self::DEFAULT_STROKE }
|
||||
true => Stroke::NONE,
|
||||
false => Self::DEFAULT_STROKE
|
||||
};
|
||||
let color = match active {
|
||||
true => { Colors::FILL }
|
||||
false => { Colors::WHITE }
|
||||
true => Colors::FILL,
|
||||
false => Colors::WHITE
|
||||
};
|
||||
let br = Button::new(RichText::new(icon.to_string()).size(24.0).color(text_color))
|
||||
.stroke(stroke)
|
||||
|
@ -115,7 +115,8 @@ impl View {
|
|||
|
||||
/// Draw [`Button`] with specified background fill color.
|
||||
pub fn button(ui: &mut egui::Ui, text: String, fill_color: Color32, action: impl FnOnce()) {
|
||||
let br = Button::new(RichText::new(text.to_uppercase()).size(18.0).color(Colors::TEXT_BUTTON))
|
||||
let button_text = RichText::new(text.to_uppercase()).size(18.0).color(Colors::TEXT_BUTTON);
|
||||
let br = Button::new(button_text)
|
||||
.stroke(Self::DEFAULT_STROKE)
|
||||
.fill(fill_color)
|
||||
.ui(ui);
|
||||
|
@ -201,14 +202,14 @@ impl View {
|
|||
|
||||
/// Draw small gold loading spinner.
|
||||
pub fn small_loading_spinner(ui: &mut egui::Ui) {
|
||||
Spinner::new().size(42.0).color(Colors::GOLD).ui(ui);
|
||||
Spinner::new().size(38.0).color(Colors::GOLD).ui(ui);
|
||||
}
|
||||
|
||||
/// Draw the button that looks like checkbox with callback on check.
|
||||
pub fn checkbox(ui: &mut egui::Ui, checked: bool, text: String, callback: impl FnOnce()) {
|
||||
let (text_value, color) = match checked {
|
||||
true => { (format!("{} {}", CHECK_SQUARE, text), Colors::TEXT_BUTTON) }
|
||||
false => { (format!("{} {}", SQUARE, text), Colors::TEXT) }
|
||||
true => (format!("{} {}", CHECK_SQUARE, text), Colors::TEXT_BUTTON),
|
||||
false => (format!("{} {}", SQUARE, text), Colors::TEXT)
|
||||
};
|
||||
|
||||
let br = Button::new(RichText::new(text_value).size(18.0).color(color))
|
||||
|
|
|
@ -100,6 +100,19 @@ impl NodeConfig {
|
|||
api_secret_path
|
||||
}
|
||||
|
||||
/// List of available IP addresses.
|
||||
pub fn get_ip_addrs() -> Vec<IpAddr> {
|
||||
let mut ip_addrs = Vec::new();
|
||||
for net_if in pnet::datalink::interfaces() {
|
||||
for ip in net_if.ips {
|
||||
if ip.is_ipv4() {
|
||||
ip_addrs.push(ip.ip());
|
||||
}
|
||||
}
|
||||
}
|
||||
ip_addrs
|
||||
}
|
||||
|
||||
/// Check whether a port is available on the provided host.
|
||||
fn is_port_available(host: &String, port: &String) -> bool {
|
||||
if let Ok(p) = port.parse::<u16>() {
|
||||
|
@ -142,6 +155,17 @@ impl NodeConfig {
|
|||
|
||||
/// Check if stratum server port is available across the system and config.
|
||||
pub fn is_stratum_port_available(ip: &String, port: &String) -> bool {
|
||||
if Node::get_stratum_stats().is_running {
|
||||
// Check if Stratum server with same address is running.
|
||||
let (cur_ip, cur_port) = Self::get_stratum_address();
|
||||
let same_running = ip == &cur_ip && port == &cur_port;
|
||||
return same_running || Self::is_not_running_stratum_port_available(ip, port);
|
||||
}
|
||||
Self::is_not_running_stratum_port_available(&ip, &port)
|
||||
}
|
||||
|
||||
/// Check if stratum port is available when server is not running.
|
||||
fn is_not_running_stratum_port_available(ip: &String, port: &String) -> bool {
|
||||
if Self::is_port_available(&ip, &port) {
|
||||
if &Self::get_p2p_port().to_string() != port {
|
||||
let (api_ip, api_port) = Self::get_api_address();
|
||||
|
@ -161,6 +185,54 @@ impl NodeConfig {
|
|||
r_config.members.clone().server.stratum_mining_config.unwrap().wallet_listener_url
|
||||
}
|
||||
|
||||
/// Get the amount of time in seconds to attempt to mine on a particular header.
|
||||
pub fn get_stratum_attempt_time() -> String {
|
||||
let r_config = Settings::node_config_to_read();
|
||||
r_config.members
|
||||
.clone()
|
||||
.server
|
||||
.stratum_mining_config
|
||||
.unwrap()
|
||||
.attempt_time_per_block
|
||||
.to_string()
|
||||
}
|
||||
|
||||
/// Save stratum attempt time value in seconds.
|
||||
pub fn save_stratum_attempt_time(time: u32) {
|
||||
let mut w_node_config = Settings::node_config_to_update();
|
||||
w_node_config.members
|
||||
.clone()
|
||||
.server
|
||||
.stratum_mining_config
|
||||
.unwrap()
|
||||
.attempt_time_per_block = time;
|
||||
w_node_config.save();
|
||||
}
|
||||
|
||||
/// Get minimum acceptable share difficulty to request from miners.
|
||||
pub fn get_stratum_min_share_diff() -> String {
|
||||
let r_config = Settings::node_config_to_read();
|
||||
r_config.members
|
||||
.clone()
|
||||
.server
|
||||
.stratum_mining_config
|
||||
.unwrap()
|
||||
.minimum_share_difficulty
|
||||
.to_string()
|
||||
}
|
||||
|
||||
/// Save minimum acceptable share difficulty.
|
||||
pub fn save_stratum_min_share_diff(diff: u64) {
|
||||
let mut w_node_config = Settings::node_config_to_update();
|
||||
w_node_config.members
|
||||
.clone()
|
||||
.server
|
||||
.stratum_mining_config
|
||||
.unwrap()
|
||||
.minimum_share_difficulty = diff;
|
||||
w_node_config.save();
|
||||
}
|
||||
|
||||
/// Check if stratum mining server autorun is enabled.
|
||||
pub fn is_stratum_autorun_enabled() -> bool {
|
||||
let stratum_config = Settings::node_config_to_read()
|
||||
|
@ -217,12 +289,12 @@ impl NodeConfig {
|
|||
} else {
|
||||
false
|
||||
};
|
||||
if same_running || NodeConfig::is_port_available(&ip, &port) {
|
||||
return &NodeConfig::get_p2p_port().to_string() != port;
|
||||
if same_running || Self::is_port_available(&ip, &port) {
|
||||
return &Self::get_p2p_port().to_string() != port;
|
||||
}
|
||||
return false;
|
||||
} else if NodeConfig::is_port_available(&ip, &port) {
|
||||
return &NodeConfig::get_p2p_port().to_string() != port;
|
||||
} else if Self::is_port_available(&ip, &port) {
|
||||
return &Self::get_p2p_port().to_string() != port;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
@ -472,7 +544,7 @@ impl NodeConfig {
|
|||
}
|
||||
|
||||
/// Set how long a banned peer should stay banned in ms.
|
||||
pub fn set_ban_window(time: i64) {
|
||||
pub fn save_ban_window(time: i64) {
|
||||
let mut w_node_config = Settings::node_config_to_update();
|
||||
w_node_config.members.server.p2p_config.ban_window = Some(time);
|
||||
w_node_config.save();
|
||||
|
@ -484,7 +556,7 @@ impl NodeConfig {
|
|||
}
|
||||
|
||||
/// Set maximum number of inbound peer connections.
|
||||
pub fn set_max_inbound_count(count: u32) {
|
||||
pub fn save_max_inbound_count(count: u32) {
|
||||
let mut w_node_config = Settings::node_config_to_update();
|
||||
w_node_config.members.server.p2p_config.peer_max_inbound_count = Some(count);
|
||||
w_node_config.save();
|
||||
|
@ -496,7 +568,7 @@ impl NodeConfig {
|
|||
}
|
||||
|
||||
/// Set maximum number of outbound peer connections.
|
||||
pub fn set_max_outbound_count(count: u32) {
|
||||
pub fn save_max_outbound_count(count: u32) {
|
||||
let mut w_node_config = Settings::node_config_to_update();
|
||||
w_node_config.members.server.p2p_config.peer_max_outbound_count = Some(count);
|
||||
w_node_config.save();
|
||||
|
@ -512,7 +584,7 @@ impl NodeConfig {
|
|||
}
|
||||
|
||||
/// Set minimum number of outbound peer connections.
|
||||
pub fn set_min_outbound_count(count: u32) {
|
||||
pub fn save_min_outbound_count(count: u32) {
|
||||
let mut w_node_config = Settings::node_config_to_update();
|
||||
w_node_config.members.server.p2p_config.peer_min_preferred_outbound_count = Some(count);
|
||||
w_node_config.save();
|
||||
|
@ -526,7 +598,7 @@ impl NodeConfig {
|
|||
}
|
||||
|
||||
/// Set base fee that's accepted into the pool.
|
||||
pub fn set_base_fee(fee: u64) {
|
||||
pub fn save_base_fee(fee: u64) {
|
||||
let mut w_node_config = Settings::node_config_to_update();
|
||||
w_node_config.members.server.pool_config.accept_fee_base = fee;
|
||||
w_node_config.save();
|
||||
|
@ -538,7 +610,7 @@ impl NodeConfig {
|
|||
}
|
||||
|
||||
/// Set reorg cache retention period in minute.
|
||||
pub fn set_reorg_cache_period(period: u32) {
|
||||
pub fn save_reorg_cache_period(period: u32) {
|
||||
let mut w_node_config = Settings::node_config_to_update();
|
||||
w_node_config.members.server.pool_config.reorg_cache_period = period;
|
||||
w_node_config.save();
|
||||
|
@ -550,7 +622,7 @@ impl NodeConfig {
|
|||
}
|
||||
|
||||
/// Set max amount of transactions at pool.
|
||||
pub fn set_max_pool_size(amount: usize) {
|
||||
pub fn save_max_pool_size(amount: usize) {
|
||||
let mut w_node_config = Settings::node_config_to_update();
|
||||
w_node_config.members.server.pool_config.max_pool_size = amount;
|
||||
w_node_config.save();
|
||||
|
@ -562,7 +634,7 @@ impl NodeConfig {
|
|||
}
|
||||
|
||||
/// Set max amount of transactions at stem pool.
|
||||
pub fn set_max_stempool_size(amount: usize) {
|
||||
pub fn save_max_stempool_size(amount: usize) {
|
||||
let mut w_node_config = Settings::node_config_to_update();
|
||||
w_node_config.members.server.pool_config.max_stempool_size = amount;
|
||||
w_node_config.save();
|
||||
|
@ -574,7 +646,7 @@ impl NodeConfig {
|
|||
}
|
||||
|
||||
/// Set max total weight of transactions that can get selected to build a block.
|
||||
pub fn set_mineable_max_weight(weight: u64) {
|
||||
pub fn save_mineable_max_weight(weight: u64) {
|
||||
let mut w_node_config = Settings::node_config_to_update();
|
||||
w_node_config.members.server.pool_config.mineable_max_weight = weight;
|
||||
w_node_config.save();
|
||||
|
@ -588,7 +660,7 @@ impl NodeConfig {
|
|||
}
|
||||
|
||||
/// Set Dandelion epoch duration in secs.
|
||||
pub fn set_epoch(secs: u16) {
|
||||
pub fn save_epoch(secs: u16) {
|
||||
let mut w_node_config = Settings::node_config_to_update();
|
||||
w_node_config.members.server.dandelion_config.epoch_secs = secs;
|
||||
w_node_config.save();
|
||||
|
@ -601,7 +673,7 @@ impl NodeConfig {
|
|||
}
|
||||
|
||||
/// Set Dandelion embargo timer.
|
||||
pub fn set_embargo(secs: u16) {
|
||||
pub fn save_embargo(secs: u16) {
|
||||
let mut w_node_config = Settings::node_config_to_update();
|
||||
w_node_config.members.server.dandelion_config.embargo_secs = secs;
|
||||
w_node_config.save();
|
||||
|
@ -613,7 +685,7 @@ impl NodeConfig {
|
|||
}
|
||||
|
||||
/// Set Dandelion stem probability.
|
||||
pub fn set_stem_probability(percent: u8) {
|
||||
pub fn save_stem_probability(percent: u8) {
|
||||
let mut w_node_config = Settings::node_config_to_update();
|
||||
w_node_config.members.server.dandelion_config.stem_probability = percent;
|
||||
w_node_config.save();
|
||||
|
|
298
src/node/mine_block.rs
Normal file
298
src/node/mine_block.rs
Normal file
|
@ -0,0 +1,298 @@
|
|||
// Copyright 2023 The Grim Developers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Build a block to mine: gathers transactions from the pool, assembles
|
||||
//! them into a block and returns it.
|
||||
|
||||
use chrono::prelude::{DateTime, NaiveDateTime, Utc};
|
||||
use rand::{thread_rng, Rng};
|
||||
use serde_json::{json, Value};
|
||||
use std::sync::Arc;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
use grin_api;
|
||||
use grin_chain;
|
||||
use grin_servers::common::types::Error;
|
||||
use grin_core::core::{Output, TxKernel};
|
||||
use grin_core::libtx::secp_ser;
|
||||
use grin_core::libtx::ProofBuilder;
|
||||
use grin_core::{consensus, core, global};
|
||||
use grin_keychain::{ExtKeychain, Identifier, Keychain};
|
||||
use grin_servers::ServerTxPool;
|
||||
use log::{debug, error, trace, warn};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
|
||||
/// Fees in block to use for coinbase amount calculation
|
||||
/// (Duplicated from Grin wallet project)
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct BlockFees {
|
||||
/// fees
|
||||
#[serde(with = "secp_ser::string_or_u64")]
|
||||
pub fees: u64,
|
||||
/// height
|
||||
#[serde(with = "secp_ser::string_or_u64")]
|
||||
pub height: u64,
|
||||
/// key id
|
||||
pub key_id: Option<Identifier>,
|
||||
}
|
||||
|
||||
impl BlockFees {
|
||||
/// return key id
|
||||
pub fn key_id(&self) -> Option<Identifier> {
|
||||
self.key_id.clone()
|
||||
}
|
||||
}
|
||||
|
||||
/// Response to build a coinbase output.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct CbData {
|
||||
/// Output
|
||||
pub output: Output,
|
||||
/// Kernel
|
||||
pub kernel: TxKernel,
|
||||
/// Key Id
|
||||
pub key_id: Option<Identifier>,
|
||||
}
|
||||
|
||||
// Ensure a block suitable for mining is built and returned
|
||||
// If a wallet listener URL is not provided the reward will be "burnt"
|
||||
// Warning: This call does not return until/unless a new block can be built
|
||||
pub fn get_block(
|
||||
chain: &Arc<grin_chain::Chain>,
|
||||
tx_pool: &ServerTxPool,
|
||||
key_id: Option<Identifier>,
|
||||
wallet_listener_url: Option<String>,
|
||||
) -> (core::Block, BlockFees) {
|
||||
let wallet_retry_interval = 5;
|
||||
// get the latest chain state and build a block on top of it
|
||||
let mut result = build_block(chain, tx_pool, key_id.clone(), wallet_listener_url.clone());
|
||||
while let Err(e) = result {
|
||||
let mut new_key_id = key_id.to_owned();
|
||||
match e {
|
||||
self::Error::Chain(c) => match c {
|
||||
grin_chain::Error::DuplicateCommitment(_) => {
|
||||
debug!(
|
||||
"Duplicate commit for potential coinbase detected. Trying next derivation."
|
||||
);
|
||||
// use the next available key to generate a different coinbase commitment
|
||||
new_key_id = None;
|
||||
}
|
||||
_ => {
|
||||
error!("Chain Error: {}", c);
|
||||
}
|
||||
},
|
||||
self::Error::WalletComm(_) => {
|
||||
error!(
|
||||
"Error building new block: Can't connect to wallet listener at {:?}; will retry",
|
||||
wallet_listener_url.as_ref().unwrap()
|
||||
);
|
||||
thread::sleep(Duration::from_secs(wallet_retry_interval));
|
||||
}
|
||||
ae => {
|
||||
warn!("Error building new block: {:?}. Retrying.", ae);
|
||||
}
|
||||
}
|
||||
|
||||
// only wait if we are still using the same key: a different coinbase commitment is unlikely
|
||||
// to have duplication
|
||||
if new_key_id.is_some() {
|
||||
thread::sleep(Duration::from_millis(100));
|
||||
}
|
||||
|
||||
result = build_block(chain, tx_pool, new_key_id, wallet_listener_url.clone());
|
||||
}
|
||||
return result.unwrap();
|
||||
}
|
||||
|
||||
/// Builds a new block with the chain head as previous and eligible
|
||||
/// transactions from the pool.
|
||||
fn build_block(
|
||||
chain: &Arc<grin_chain::Chain>,
|
||||
tx_pool: &ServerTxPool,
|
||||
key_id: Option<Identifier>,
|
||||
wallet_listener_url: Option<String>,
|
||||
) -> Result<(core::Block, BlockFees), Error> {
|
||||
let head = chain.head_header()?;
|
||||
|
||||
// prepare the block header timestamp
|
||||
let mut now_sec = Utc::now().timestamp();
|
||||
let head_sec = head.timestamp.timestamp();
|
||||
if now_sec <= head_sec {
|
||||
now_sec = head_sec + 1;
|
||||
}
|
||||
|
||||
// Determine the difficulty our block should be at.
|
||||
// Note: do not keep the difficulty_iter in scope (it has an active batch).
|
||||
let difficulty = consensus::next_difficulty(head.height + 1, chain.difficulty_iter()?);
|
||||
|
||||
// Extract current "mineable" transactions from the pool.
|
||||
// If this fails for *any* reason then fallback to an empty vec of txs.
|
||||
// This will allow us to mine an "empty" block if the txpool is in an
|
||||
// invalid (and unexpected) state.
|
||||
let txs = match tx_pool.read().prepare_mineable_transactions() {
|
||||
Ok(txs) => txs,
|
||||
Err(e) => {
|
||||
error!(
|
||||
"build_block: Failed to prepare mineable txs from txpool: {:?}",
|
||||
e
|
||||
);
|
||||
warn!("build_block: Falling back to mining empty block.");
|
||||
vec![]
|
||||
}
|
||||
};
|
||||
|
||||
// build the coinbase and the block itself
|
||||
let fees = txs.iter().map(|tx| tx.fee()).sum();
|
||||
let height = head.height + 1;
|
||||
let block_fees = BlockFees {
|
||||
fees,
|
||||
key_id,
|
||||
height,
|
||||
};
|
||||
|
||||
let (output, kernel, block_fees) = get_coinbase(wallet_listener_url, block_fees)?;
|
||||
let mut b = core::Block::from_reward(&head, &txs, output, kernel, difficulty.difficulty)?;
|
||||
|
||||
// making sure we're not spending time mining a useless block
|
||||
b.validate(&head.total_kernel_offset)?;
|
||||
|
||||
b.header.pow.nonce = thread_rng().gen();
|
||||
b.header.pow.secondary_scaling = difficulty.secondary_scaling;
|
||||
b.header.timestamp = DateTime::<Utc>::from_utc(NaiveDateTime::from_timestamp(now_sec, 0), Utc);
|
||||
|
||||
debug!(
|
||||
"Built new block with {} inputs and {} outputs, block difficulty: {}, cumulative difficulty {}",
|
||||
b.inputs().len(),
|
||||
b.outputs().len(),
|
||||
difficulty.difficulty,
|
||||
b.header.total_difficulty().to_num(),
|
||||
);
|
||||
|
||||
// Now set txhashset roots and sizes on the header of the block being built.
|
||||
match chain.set_txhashset_roots(&mut b) {
|
||||
Ok(_) => Ok((b, block_fees)),
|
||||
Err(e) => {
|
||||
match e {
|
||||
// If this is a duplicate commitment then likely trying to use
|
||||
// a key that hass already been derived but not in the wallet
|
||||
// for some reason, allow caller to retry.
|
||||
grin_chain::Error::DuplicateCommitment(e) => {
|
||||
Err(Error::Chain(grin_chain::Error::DuplicateCommitment(e)))
|
||||
}
|
||||
|
||||
// Some other issue, possibly duplicate kernel
|
||||
_ => {
|
||||
error!("Error setting txhashset root to build a block: {:?}", e);
|
||||
Err(Error::Chain(grin_chain::Error::Other(format!("{:?}", e))))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// Probably only want to do this when testing.
|
||||
///
|
||||
fn burn_reward(block_fees: BlockFees) -> Result<(Output, TxKernel, BlockFees), Error> {
|
||||
warn!("Burning block fees: {:?}", block_fees);
|
||||
let keychain = ExtKeychain::from_random_seed(global::is_testnet())?;
|
||||
let key_id = ExtKeychain::derive_key_id(1, 1, 0, 0, 0);
|
||||
let (out, kernel) = grin_core::libtx::reward::output(
|
||||
&keychain,
|
||||
&ProofBuilder::new(&keychain),
|
||||
&key_id,
|
||||
block_fees.fees,
|
||||
false,
|
||||
)
|
||||
.unwrap();
|
||||
Ok((out, kernel, block_fees))
|
||||
}
|
||||
|
||||
// Connect to the wallet listener and get coinbase.
|
||||
// Warning: If a wallet listener URL is not provided the reward will be "burnt"
|
||||
fn get_coinbase(
|
||||
wallet_listener_url: Option<String>,
|
||||
block_fees: BlockFees,
|
||||
) -> Result<(core::Output, core::TxKernel, BlockFees), Error> {
|
||||
match wallet_listener_url {
|
||||
None => {
|
||||
// Burn it
|
||||
return burn_reward(block_fees);
|
||||
}
|
||||
Some(wallet_listener_url) => {
|
||||
let res = create_coinbase(&wallet_listener_url, &block_fees)?;
|
||||
let output = res.output;
|
||||
let kernel = res.kernel;
|
||||
let key_id = res.key_id;
|
||||
let block_fees = BlockFees {
|
||||
key_id: key_id,
|
||||
..block_fees
|
||||
};
|
||||
|
||||
debug!("get_coinbase: {:?}", block_fees);
|
||||
return Ok((output, kernel, block_fees));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Call the wallet API to create a coinbase output for the given block_fees.
|
||||
/// Will retry based on default "retry forever with backoff" behavior.
|
||||
fn create_coinbase(dest: &str, block_fees: &BlockFees) -> Result<CbData, Error> {
|
||||
let url = format!("{}/v2/foreign", dest);
|
||||
let req_body = json!({
|
||||
"jsonrpc": "2.0",
|
||||
"method": "build_coinbase",
|
||||
"id": 1,
|
||||
"params": {
|
||||
"block_fees": block_fees
|
||||
}
|
||||
});
|
||||
|
||||
trace!("Sending build_coinbase request: {}", req_body);
|
||||
let req = grin_api::client::create_post_request(url.as_str(), None, &req_body)?;
|
||||
let timeout = grin_api::client::TimeOut::default();
|
||||
let res: String = grin_api::client::send_request(req, timeout).map_err(|e| {
|
||||
let report = format!(
|
||||
"Failed to get coinbase from {}. Is the wallet listening? {}",
|
||||
dest, e
|
||||
);
|
||||
error!("{}", report);
|
||||
Error::WalletComm(report)
|
||||
})?;
|
||||
|
||||
let res: Value = serde_json::from_str(&res).unwrap();
|
||||
trace!("Response: {}", res);
|
||||
if res["error"] != json!(null) {
|
||||
let report = format!(
|
||||
"Failed to get coinbase from {}: Error: {}, Message: {}",
|
||||
dest, res["error"]["code"], res["error"]["message"]
|
||||
);
|
||||
error!("{}", report);
|
||||
return Err(Error::WalletComm(report));
|
||||
}
|
||||
|
||||
let cb_data = res["result"]["Ok"].clone();
|
||||
trace!("cb_data: {}", cb_data);
|
||||
let ret_val = match serde_json::from_value::<CbData>(cb_data) {
|
||||
Ok(r) => r,
|
||||
Err(e) => {
|
||||
let report = format!("Couldn't deserialize CbData: {}", e);
|
||||
error!("{}", report);
|
||||
return Err(Error::WalletComm(report));
|
||||
}
|
||||
};
|
||||
|
||||
Ok(ret_val)
|
||||
}
|
|
@ -16,4 +16,8 @@ mod node;
|
|||
pub use node::Node;
|
||||
|
||||
mod config;
|
||||
|
||||
mod stratum;
|
||||
mod mine_block;
|
||||
|
||||
pub use config::NodeConfig;
|
134
src/node/node.rs
134
src/node/node.rs
|
@ -22,11 +22,12 @@ use futures::channel::oneshot;
|
|||
use grin_chain::SyncStatus;
|
||||
use grin_core::global;
|
||||
use grin_core::global::ChainTypes;
|
||||
use grin_servers::{Server, ServerStats};
|
||||
use grin_servers::{Server, ServerStats, StratumServerConfig, StratumStats};
|
||||
use grin_servers::common::types::Error;
|
||||
use jni::sys::{jboolean, jstring};
|
||||
use lazy_static::lazy_static;
|
||||
use crate::node::NodeConfig;
|
||||
use crate::node::stratum::StratumServer;
|
||||
|
||||
lazy_static! {
|
||||
/// Static thread-aware state of [`Node`] to be updated from another thread.
|
||||
|
@ -35,8 +36,10 @@ lazy_static! {
|
|||
|
||||
/// Provides [`Server`] control, holds current status and statistics.
|
||||
pub struct Node {
|
||||
/// Statistics data for UI.
|
||||
/// The node [`Server`] statistics for UI.
|
||||
stats: Arc<RwLock<Option<ServerStats>>>,
|
||||
/// Stratum server statistics.
|
||||
stratum_stats: Arc<grin_util::RwLock<StratumStats>>,
|
||||
/// Running API server address.
|
||||
api_addr: Arc<RwLock<Option<String>>>,
|
||||
/// Running P2P server port.
|
||||
|
@ -49,8 +52,8 @@ pub struct Node {
|
|||
stop_needed: AtomicBool,
|
||||
/// Flag to check if app exit is needed after server stop.
|
||||
exit_after_stop: AtomicBool,
|
||||
/// Thread flag to start stratum server at separate.
|
||||
start_stratum_server: AtomicBool,
|
||||
/// Thread flag to start stratum server.
|
||||
start_stratum_needed: AtomicBool,
|
||||
/// Error on [`Server`] start.
|
||||
init_error: Option<Error>
|
||||
}
|
||||
|
@ -59,13 +62,14 @@ impl Default for Node {
|
|||
fn default() -> Self {
|
||||
Self {
|
||||
stats: Arc::new(RwLock::new(None)),
|
||||
stratum_stats: Arc::new(grin_util::RwLock::new(StratumStats::default())),
|
||||
api_addr: Arc::new(RwLock::new(None)),
|
||||
p2p_port: Arc::new(RwLock::new(None)),
|
||||
starting: AtomicBool::new(false),
|
||||
restart_needed: AtomicBool::new(false),
|
||||
stop_needed: AtomicBool::new(false),
|
||||
exit_after_stop: AtomicBool::new(false),
|
||||
start_stratum_server: AtomicBool::new(false),
|
||||
start_stratum_needed: AtomicBool::new(false),
|
||||
init_error: None
|
||||
}
|
||||
}
|
||||
|
@ -114,14 +118,14 @@ impl Node {
|
|||
}
|
||||
}
|
||||
|
||||
/// Start stratum server.
|
||||
/// Request to start stratum server.
|
||||
pub fn start_stratum_server() {
|
||||
NODE_STATE.start_stratum_server.store(true, Ordering::Relaxed);
|
||||
NODE_STATE.start_stratum_needed.store(true, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
/// Check if stratum server is starting.
|
||||
pub fn is_stratum_server_starting() -> bool {
|
||||
NODE_STATE.start_stratum_server.load(Ordering::Relaxed)
|
||||
NODE_STATE.start_stratum_needed.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
/// Check if node is starting.
|
||||
|
@ -149,6 +153,11 @@ impl Node {
|
|||
NODE_STATE.stats.read().unwrap()
|
||||
}
|
||||
|
||||
/// Get stratum server [`Server`] statistics.
|
||||
pub fn get_stratum_stats() -> grin_util::RwLockReadGuard<'static, StratumStats> {
|
||||
NODE_STATE.stratum_stats.read()
|
||||
}
|
||||
|
||||
/// Get synchronization status, empty when [`Server`] is not running.
|
||||
pub fn get_sync_status() -> Option<SyncStatus> {
|
||||
// Return Shutdown status when node is stopping.
|
||||
|
@ -169,13 +178,13 @@ impl Node {
|
|||
None
|
||||
}
|
||||
|
||||
/// Start node [`Server`] at separate thread to update [`NODE_STATE`] with [`ServerStats`].
|
||||
/// Start the [`Server`] at separate thread to update state with stats and handle statuses.
|
||||
fn start_server_thread() {
|
||||
thread::spawn(move || {
|
||||
NODE_STATE.starting.store(true, Ordering::Relaxed);
|
||||
|
||||
// Start the server.
|
||||
match start_server() {
|
||||
match start_node_server() {
|
||||
Ok(mut server) => {
|
||||
let mut first_start = true;
|
||||
loop {
|
||||
|
@ -183,63 +192,51 @@ impl Node {
|
|||
// Stop the server.
|
||||
server.stop();
|
||||
|
||||
// Reset stratum stats
|
||||
{
|
||||
let mut w_stratum_stats = NODE_STATE.stratum_stats.write();
|
||||
*w_stratum_stats = StratumStats::default();
|
||||
}
|
||||
|
||||
// Create new server.
|
||||
match start_server() {
|
||||
match start_node_server() {
|
||||
Ok(s) => {
|
||||
server = s;
|
||||
NODE_STATE.restart_needed.store(false, Ordering::Relaxed);
|
||||
}
|
||||
Err(e) => {
|
||||
NODE_STATE.restart_needed.store(false, Ordering::Relaxed);
|
||||
Self::on_start_error(&e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if Self::is_stopping() {
|
||||
// Clean server stats.
|
||||
{
|
||||
let mut w_stats = NODE_STATE.stats.write().unwrap();
|
||||
*w_stats = None;
|
||||
}
|
||||
|
||||
// Stop the server.
|
||||
server.stop();
|
||||
|
||||
NODE_STATE.starting.store(false, Ordering::Relaxed);
|
||||
NODE_STATE.stop_needed.store(false, Ordering::Relaxed);
|
||||
NODE_STATE.start_stratum_server.store(false, Ordering::Relaxed);
|
||||
|
||||
// Clean launched API server address.
|
||||
{
|
||||
let mut w_api_addr = NODE_STATE.api_addr.write().unwrap();
|
||||
*w_api_addr = None;
|
||||
}
|
||||
// Clean launched P2P server port.
|
||||
{
|
||||
let mut w_p2p_port = NODE_STATE.p2p_port.write().unwrap();
|
||||
*w_p2p_port = None;
|
||||
}
|
||||
// Clean stats and statuses.
|
||||
Self::on_thread_stop();
|
||||
// Exit thread loop.
|
||||
break;
|
||||
} else {
|
||||
// Start stratum mining server.
|
||||
if Self::is_stratum_server_starting() {
|
||||
}
|
||||
|
||||
// Start stratum mining server if requested.
|
||||
let stratum_start_requested = Self::is_stratum_server_starting();
|
||||
if stratum_start_requested {
|
||||
let (s_ip, s_port) = NodeConfig::get_stratum_address();
|
||||
if NodeConfig::is_stratum_port_available(&s_ip, &s_port) {
|
||||
let stratum_config = server
|
||||
.config
|
||||
.stratum_mining_config
|
||||
.clone()
|
||||
.unwrap();
|
||||
server.start_stratum_server(stratum_config);
|
||||
|
||||
// Wait for mining server to start and update status.
|
||||
thread::sleep(Duration::from_millis(100));
|
||||
NODE_STATE.start_stratum_server.store(false, Ordering::Relaxed);
|
||||
start_stratum_mining_server(&server, stratum_config);
|
||||
}
|
||||
}
|
||||
|
||||
// Update server stats.
|
||||
if let Ok(stats) = server.get_server_stats() {
|
||||
{
|
||||
let mut w_stats = NODE_STATE.stats.write().unwrap();
|
||||
*w_stats = Some(stats);
|
||||
*w_stats = Some(stats.clone());
|
||||
}
|
||||
|
||||
if first_start {
|
||||
|
@ -247,20 +244,33 @@ impl Node {
|
|||
first_start = false;
|
||||
}
|
||||
}
|
||||
|
||||
if stratum_start_requested {
|
||||
NODE_STATE.start_stratum_needed.store(false, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
thread::sleep(Duration::from_millis(250));
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
NODE_STATE.starting.store(false, Ordering::Relaxed);
|
||||
Self::on_start_error(&e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Handle node [`Server`] error on start.
|
||||
fn on_start_error(e: &Error) {
|
||||
/// Reset stats and statuses on [`Server`] thread stop.
|
||||
fn on_thread_stop() {
|
||||
NODE_STATE.starting.store(false, Ordering::Relaxed);
|
||||
NODE_STATE.restart_needed.store(false, Ordering::Relaxed);
|
||||
NODE_STATE.start_stratum_needed.store(false, Ordering::Relaxed);
|
||||
NODE_STATE.stop_needed.store(false, Ordering::Relaxed);
|
||||
|
||||
// Reset stratum stats.
|
||||
{
|
||||
let mut w_stratum_stats = NODE_STATE.stratum_stats.write();
|
||||
*w_stratum_stats = StratumStats::default();
|
||||
}
|
||||
// Clean server stats.
|
||||
{
|
||||
let mut w_stats = NODE_STATE.stats.write().unwrap();
|
||||
|
@ -276,6 +286,12 @@ impl Node {
|
|||
let mut w_p2p_port = NODE_STATE.p2p_port.write().unwrap();
|
||||
*w_p2p_port = None;
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle node [`Server`] error on start.
|
||||
fn on_start_error(e: &Error) {
|
||||
Self::on_thread_stop();
|
||||
|
||||
//TODO: Create error
|
||||
// NODE_STATE.init_error = Some(e);
|
||||
|
||||
|
@ -464,8 +480,8 @@ impl Node {
|
|||
}
|
||||
}
|
||||
|
||||
/// Start the [`Server`] for node.
|
||||
fn start_server() -> Result<Server, Error> {
|
||||
/// Start the node [`Server`].
|
||||
fn start_node_server() -> Result<Server, Error> {
|
||||
// Get current global config
|
||||
let config = NodeConfig::get_members();
|
||||
let server_config = config.server.clone();
|
||||
|
@ -545,10 +561,34 @@ fn start_server() -> Result<Server, Error> {
|
|||
*w_p2p_port = Some(config.server.p2p_config.port);
|
||||
}
|
||||
|
||||
// Put flag to start stratum server if autorun is available.
|
||||
if NodeConfig::is_stratum_autorun_enabled() {
|
||||
NODE_STATE.start_stratum_needed.store(true, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
let server_result = Server::new(server_config.clone(), None, api_chan);
|
||||
server_result
|
||||
}
|
||||
|
||||
/// Start stratum mining server on a separate thread.
|
||||
pub fn start_stratum_mining_server(server: &Server, config: StratumServerConfig) {
|
||||
let proof_size = global::proofsize();
|
||||
let sync_state = server.sync_state.clone();
|
||||
|
||||
let mut stratum_server = StratumServer::new(
|
||||
config,
|
||||
server.chain.clone(),
|
||||
server.tx_pool.clone(),
|
||||
NODE_STATE.stratum_stats.clone(),
|
||||
);
|
||||
let stop_state = server.stop_state.clone();
|
||||
let _ = thread::Builder::new()
|
||||
.name("stratum_server".to_string())
|
||||
.spawn(move || {
|
||||
stratum_server.run_loop(proof_size, sync_state, stop_state);
|
||||
});
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[cfg(target_os = "android")]
|
||||
#[allow(non_snake_case)]
|
||||
|
|
934
src/node/stratum.rs
Normal file
934
src/node/stratum.rs
Normal file
|
@ -0,0 +1,934 @@
|
|||
// Copyright 2023 The Grim Developers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Mining Stratum Server
|
||||
|
||||
|
||||
|
||||
use futures::channel::mpsc;
|
||||
use futures::pin_mut;
|
||||
use futures::{SinkExt, StreamExt, TryStreamExt};
|
||||
use tokio::net::TcpListener;
|
||||
use tokio::runtime::Runtime;
|
||||
use tokio_util::codec::{Framed, LinesCodec};
|
||||
|
||||
use grin_util::{RwLock, StopState};
|
||||
use chrono::prelude::Utc;
|
||||
use serde_json::Value;
|
||||
use std::collections::HashMap;
|
||||
use std::net::{SocketAddr, TcpStream};
|
||||
use std::panic::panic_any;
|
||||
use std::sync::Arc;
|
||||
use std::thread;
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
use grin_chain::{self, SyncState};
|
||||
use grin_servers::common::stats::{StratumStats, WorkerStats};
|
||||
use grin_servers::common::types::StratumServerConfig;
|
||||
use grin_core::consensus::graph_weight;
|
||||
use grin_core::core::hash::Hashed;
|
||||
use grin_core::core::Block;
|
||||
use grin_core::global;
|
||||
use grin_core::{pow, ser};
|
||||
use crate::node::mine_block;
|
||||
use grin_util::ToHex;
|
||||
use grin_servers::ServerTxPool;
|
||||
use log::{debug, error, info, warn};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
|
||||
type Tx = mpsc::UnboundedSender<String>;
|
||||
|
||||
|
||||
// ----------------------------------------
|
||||
// http://www.jsonrpc.org/specification
|
||||
// RPC Methods
|
||||
|
||||
/// Represents a compliant JSON RPC 2.0 id.
|
||||
/// Valid id: Integer, String.
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
||||
#[serde(untagged)]
|
||||
enum JsonId {
|
||||
IntId(u32),
|
||||
StrId(String),
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
||||
struct RpcRequest {
|
||||
id: JsonId,
|
||||
jsonrpc: String,
|
||||
method: String,
|
||||
params: Option<Value>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
||||
struct RpcResponse {
|
||||
id: JsonId,
|
||||
jsonrpc: String,
|
||||
method: String,
|
||||
result: Option<Value>,
|
||||
error: Option<Value>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
struct RpcError {
|
||||
code: i32,
|
||||
message: String,
|
||||
}
|
||||
|
||||
impl RpcError {
|
||||
pub fn internal_error() -> Self {
|
||||
RpcError {
|
||||
code: 32603,
|
||||
message: "Internal error".to_owned(),
|
||||
}
|
||||
}
|
||||
pub fn node_is_syncing() -> Self {
|
||||
RpcError {
|
||||
code: -32000,
|
||||
message: "Node is syncing - Please wait".to_owned(),
|
||||
}
|
||||
}
|
||||
pub fn method_not_found() -> Self {
|
||||
RpcError {
|
||||
code: -32601,
|
||||
message: "Method not found".to_owned(),
|
||||
}
|
||||
}
|
||||
pub fn too_late() -> Self {
|
||||
RpcError {
|
||||
code: -32503,
|
||||
message: "Solution submitted too late".to_string(),
|
||||
}
|
||||
}
|
||||
pub fn cannot_validate() -> Self {
|
||||
RpcError {
|
||||
code: -32502,
|
||||
message: "Failed to validate solution".to_string(),
|
||||
}
|
||||
}
|
||||
pub fn too_low_difficulty() -> Self {
|
||||
RpcError {
|
||||
code: -32501,
|
||||
message: "Share rejected due to low difficulty".to_string(),
|
||||
}
|
||||
}
|
||||
pub fn invalid_request() -> Self {
|
||||
RpcError {
|
||||
code: -32600,
|
||||
message: "Invalid Request".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RpcError> for Value {
|
||||
fn from(e: RpcError) -> Self {
|
||||
serde_json::to_value(e).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<T> for RpcError
|
||||
where
|
||||
T: std::error::Error,
|
||||
{
|
||||
fn from(e: T) -> Self {
|
||||
error!("Received unhandled error: {}", e);
|
||||
RpcError::internal_error()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
struct LoginParams {
|
||||
login: String,
|
||||
pass: String,
|
||||
agent: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
struct SubmitParams {
|
||||
height: u64,
|
||||
job_id: u64,
|
||||
nonce: u64,
|
||||
edge_bits: u32,
|
||||
pow: Vec<u64>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct JobTemplate {
|
||||
height: u64,
|
||||
job_id: u64,
|
||||
difficulty: u64,
|
||||
pre_pow: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct WorkerStatus {
|
||||
id: String,
|
||||
height: u64,
|
||||
difficulty: u64,
|
||||
accepted: u64,
|
||||
rejected: u64,
|
||||
stale: u64,
|
||||
}
|
||||
|
||||
struct State {
|
||||
current_block_versions: Vec<Block>,
|
||||
// to prevent the wallet from generating a new HD key derivation for each
|
||||
// iteration, we keep the returned derivation to provide it back when
|
||||
// nothing has changed. We only want to create a key_id for each new block,
|
||||
// and reuse it when we rebuild the current block to add new tx.
|
||||
current_key_id: Option<grin_keychain::Identifier>,
|
||||
current_difficulty: u64, // scaled
|
||||
minimum_share_difficulty: u64, // unscaled
|
||||
}
|
||||
|
||||
impl State {
|
||||
pub fn new(minimum_share_difficulty: u64) -> Self {
|
||||
let blocks = vec![Block::default()];
|
||||
State {
|
||||
current_block_versions: blocks,
|
||||
current_key_id: None,
|
||||
current_difficulty: <u64>::max_value(),
|
||||
minimum_share_difficulty: minimum_share_difficulty,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Handler {
|
||||
id: String,
|
||||
workers: Arc<WorkersList>,
|
||||
sync_state: Arc<SyncState>,
|
||||
chain: Arc<grin_chain::Chain>,
|
||||
current_state: Arc<RwLock<State>>,
|
||||
}
|
||||
|
||||
impl Handler {
|
||||
pub fn new(
|
||||
id: String,
|
||||
stratum_stats: Arc<RwLock<StratumStats>>,
|
||||
sync_state: Arc<SyncState>,
|
||||
minimum_share_difficulty: u64,
|
||||
chain: Arc<grin_chain::Chain>,
|
||||
) -> Self {
|
||||
Handler {
|
||||
id: id,
|
||||
workers: Arc::new(WorkersList::new(stratum_stats)),
|
||||
sync_state: sync_state,
|
||||
chain: chain,
|
||||
current_state: Arc::new(RwLock::new(State::new(minimum_share_difficulty))),
|
||||
}
|
||||
}
|
||||
pub fn from_stratum(stratum: &StratumServer) -> Self {
|
||||
Handler::new(
|
||||
stratum.id.clone(),
|
||||
stratum.stratum_stats.clone(),
|
||||
stratum.sync_state.clone(),
|
||||
stratum.config.minimum_share_difficulty,
|
||||
stratum.chain.clone(),
|
||||
)
|
||||
}
|
||||
fn handle_rpc_requests(&self, request: RpcRequest, worker_id: usize) -> String {
|
||||
self.workers.last_seen(worker_id);
|
||||
|
||||
// Call the handler function for requested method
|
||||
let response = match request.method.as_str() {
|
||||
"login" => self.handle_login(request.params, worker_id),
|
||||
"submit" => {
|
||||
let res = self.handle_submit(request.params, worker_id);
|
||||
// this key_id has been used now, reset
|
||||
if let Ok((_, true)) = res {
|
||||
self.current_state.write().current_key_id = None;
|
||||
}
|
||||
res.map(|(v, _)| v)
|
||||
}
|
||||
"keepalive" => self.handle_keepalive(),
|
||||
"getjobtemplate" => {
|
||||
if self.sync_state.is_syncing() {
|
||||
Err(RpcError::node_is_syncing())
|
||||
} else {
|
||||
self.handle_getjobtemplate()
|
||||
}
|
||||
}
|
||||
"status" => self.handle_status(worker_id),
|
||||
_ => {
|
||||
// Called undefined method
|
||||
Err(RpcError::method_not_found())
|
||||
}
|
||||
};
|
||||
|
||||
// Package the reply as RpcResponse json
|
||||
let resp = match response {
|
||||
Err(rpc_error) => RpcResponse {
|
||||
id: request.id,
|
||||
jsonrpc: String::from("2.0"),
|
||||
method: request.method,
|
||||
result: None,
|
||||
error: Some(rpc_error.into()),
|
||||
},
|
||||
Ok(response) => RpcResponse {
|
||||
id: request.id,
|
||||
jsonrpc: String::from("2.0"),
|
||||
method: request.method,
|
||||
result: Some(response),
|
||||
error: None,
|
||||
},
|
||||
};
|
||||
serde_json::to_string(&resp).unwrap()
|
||||
}
|
||||
fn handle_login(&self, params: Option<Value>, worker_id: usize) -> Result<Value, RpcError> {
|
||||
let params: LoginParams = parse_params(params)?;
|
||||
self.workers.login(worker_id, params.login, params.agent)?;
|
||||
return Ok("ok".into());
|
||||
}
|
||||
|
||||
// Handle KEEPALIVE message
|
||||
fn handle_keepalive(&self) -> Result<Value, RpcError> {
|
||||
return Ok("ok".into());
|
||||
}
|
||||
|
||||
fn handle_status(&self, worker_id: usize) -> Result<Value, RpcError> {
|
||||
// Return worker status in json for use by a dashboard or healthcheck.
|
||||
let stats = self.workers.get_stats(worker_id)?;
|
||||
let status = WorkerStatus {
|
||||
id: stats.id.clone(),
|
||||
height: self
|
||||
.current_state
|
||||
.read()
|
||||
.current_block_versions
|
||||
.last()
|
||||
.unwrap()
|
||||
.header
|
||||
.height,
|
||||
difficulty: stats.pow_difficulty,
|
||||
accepted: stats.num_accepted,
|
||||
rejected: stats.num_rejected,
|
||||
stale: stats.num_stale,
|
||||
};
|
||||
let response = serde_json::to_value(&status).unwrap();
|
||||
return Ok(response);
|
||||
}
|
||||
// Handle GETJOBTEMPLATE message
|
||||
fn handle_getjobtemplate(&self) -> Result<Value, RpcError> {
|
||||
// Build a JobTemplate from a BlockHeader and return JSON
|
||||
let job_template = self.build_block_template();
|
||||
let response = serde_json::to_value(&job_template).unwrap();
|
||||
debug!(
|
||||
"(Server ID: {}) sending block {} with id {} to single worker",
|
||||
self.id, job_template.height, job_template.job_id,
|
||||
);
|
||||
return Ok(response);
|
||||
}
|
||||
|
||||
// Build and return a JobTemplate for mining the current block
|
||||
fn build_block_template(&self) -> JobTemplate {
|
||||
let bh = self
|
||||
.current_state
|
||||
.read()
|
||||
.current_block_versions
|
||||
.last()
|
||||
.unwrap()
|
||||
.header
|
||||
.clone();
|
||||
// Serialize the block header into pre and post nonce strings
|
||||
let mut header_buf = vec![];
|
||||
{
|
||||
let mut writer = ser::BinWriter::default(&mut header_buf);
|
||||
bh.write_pre_pow(&mut writer).unwrap();
|
||||
bh.pow.write_pre_pow(&mut writer).unwrap();
|
||||
}
|
||||
let pre_pow = header_buf.to_hex();
|
||||
let current_state = self.current_state.read();
|
||||
let job_template = JobTemplate {
|
||||
height: bh.height,
|
||||
job_id: (current_state.current_block_versions.len() - 1) as u64,
|
||||
difficulty: current_state.minimum_share_difficulty,
|
||||
pre_pow,
|
||||
};
|
||||
return job_template;
|
||||
}
|
||||
// Handle SUBMIT message
|
||||
// params contains a solved block header
|
||||
// We accept and log valid shares of all difficulty above configured minimum
|
||||
// Accepted shares that are full solutions will also be submitted to the
|
||||
// network
|
||||
fn handle_submit(
|
||||
&self,
|
||||
params: Option<Value>,
|
||||
worker_id: usize,
|
||||
) -> Result<(Value, bool), RpcError> {
|
||||
// Validate parameters
|
||||
let params: SubmitParams = parse_params(params)?;
|
||||
|
||||
let state = self.current_state.read();
|
||||
// Find the correct version of the block to match this header
|
||||
let b: Option<&Block> = state.current_block_versions.get(params.job_id as usize);
|
||||
if params.height != state.current_block_versions.last().unwrap().header.height
|
||||
|| b.is_none()
|
||||
{
|
||||
// Return error status
|
||||
error!(
|
||||
"(Server ID: {}) Share at height {}, edge_bits {}, nonce {}, job_id {} submitted too late",
|
||||
self.id, params.height, params.edge_bits, params.nonce, params.job_id,
|
||||
);
|
||||
self.workers.update_stats(worker_id, |ws| ws.num_stale += 1);
|
||||
return Err(RpcError::too_late());
|
||||
}
|
||||
|
||||
let scaled_share_difficulty: u64;
|
||||
let unscaled_share_difficulty: u64;
|
||||
let mut share_is_block = false;
|
||||
|
||||
let mut b: Block = b.unwrap().clone();
|
||||
// Reconstruct the blocks header with this nonce and pow added
|
||||
b.header.pow.proof.edge_bits = params.edge_bits as u8;
|
||||
b.header.pow.nonce = params.nonce;
|
||||
b.header.pow.proof.nonces = params.pow;
|
||||
|
||||
if !b.header.pow.is_primary() && !b.header.pow.is_secondary() {
|
||||
// Return error status
|
||||
error!(
|
||||
"(Server ID: {}) Failed to validate solution at height {}, hash {}, edge_bits {}, nonce {}, job_id {}: cuckoo size too small",
|
||||
self.id, params.height, b.hash(), params.edge_bits, params.nonce, params.job_id,
|
||||
);
|
||||
self.workers
|
||||
.update_stats(worker_id, |worker_stats| worker_stats.num_rejected += 1);
|
||||
return Err(RpcError::cannot_validate());
|
||||
}
|
||||
|
||||
// Get share difficulty values
|
||||
scaled_share_difficulty = b.header.pow.to_difficulty(b.header.height).to_num();
|
||||
unscaled_share_difficulty = b.header.pow.to_unscaled_difficulty().to_num();
|
||||
// Note: state.minimum_share_difficulty is unscaled
|
||||
// state.current_difficulty is scaled
|
||||
// If the difficulty is too low its an error
|
||||
if unscaled_share_difficulty < state.minimum_share_difficulty {
|
||||
// Return error status
|
||||
error!(
|
||||
"(Server ID: {}) Share at height {}, hash {}, edge_bits {}, nonce {}, job_id {} rejected due to low difficulty: {}/{}",
|
||||
self.id, params.height, b.hash(), params.edge_bits, params.nonce, params.job_id, unscaled_share_difficulty, state.minimum_share_difficulty,
|
||||
);
|
||||
self.workers
|
||||
.update_stats(worker_id, |worker_stats| worker_stats.num_rejected += 1);
|
||||
return Err(RpcError::too_low_difficulty());
|
||||
}
|
||||
|
||||
// If the difficulty is high enough, submit it (which also validates it)
|
||||
if scaled_share_difficulty >= state.current_difficulty {
|
||||
// This is a full solution, submit it to the network
|
||||
let res = self.chain.process_block(b.clone(), grin_chain::Options::MINE);
|
||||
if let Err(e) = res {
|
||||
// Return error status
|
||||
error!(
|
||||
"(Server ID: {}) Failed to validate solution at height {}, hash {}, edge_bits {}, nonce {}, job_id {}, {}",
|
||||
self.id,
|
||||
params.height,
|
||||
b.hash(),
|
||||
params.edge_bits,
|
||||
params.nonce,
|
||||
params.job_id,
|
||||
e,
|
||||
);
|
||||
self.workers
|
||||
.update_stats(worker_id, |worker_stats| worker_stats.num_rejected += 1);
|
||||
return Err(RpcError::cannot_validate());
|
||||
}
|
||||
share_is_block = true;
|
||||
self.workers
|
||||
.update_stats(worker_id, |worker_stats| worker_stats.num_blocks_found += 1);
|
||||
self.workers.stratum_stats.write().blocks_found += 1;
|
||||
// Log message to make it obvious we found a block
|
||||
let stats = self.workers.get_stats(worker_id)?;
|
||||
warn!(
|
||||
"(Server ID: {}) Solution Found for block {}, hash {} - Yay!!! Worker ID: {}, blocks found: {}, shares: {}",
|
||||
self.id, params.height,
|
||||
b.hash(),
|
||||
stats.id,
|
||||
stats.num_blocks_found,
|
||||
stats.num_accepted,
|
||||
);
|
||||
} else {
|
||||
// Do some validation but dont submit
|
||||
let res = pow::verify_size(&b.header);
|
||||
if res.is_err() {
|
||||
// Return error status
|
||||
error!(
|
||||
"(Server ID: {}) Failed to validate share at height {}, hash {}, edge_bits {}, nonce {}, job_id {}. {:?}",
|
||||
self.id,
|
||||
params.height,
|
||||
b.hash(),
|
||||
params.edge_bits,
|
||||
b.header.pow.nonce,
|
||||
params.job_id,
|
||||
res,
|
||||
);
|
||||
self.workers
|
||||
.update_stats(worker_id, |worker_stats| worker_stats.num_rejected += 1);
|
||||
return Err(RpcError::cannot_validate());
|
||||
}
|
||||
}
|
||||
// Log this as a valid share
|
||||
self.workers.update_edge_bits(params.edge_bits as u16);
|
||||
let worker = self.workers.get_worker(worker_id)?;
|
||||
let submitted_by = match worker.login {
|
||||
None => worker.id.to_string(),
|
||||
Some(login) => login,
|
||||
};
|
||||
|
||||
info!(
|
||||
"(Server ID: {}) Got share at height {}, hash {}, edge_bits {}, nonce {}, job_id {}, difficulty {}/{}, submitted by {}",
|
||||
self.id,
|
||||
b.header.height,
|
||||
b.hash(),
|
||||
b.header.pow.proof.edge_bits,
|
||||
b.header.pow.nonce,
|
||||
params.job_id,
|
||||
scaled_share_difficulty,
|
||||
state.current_difficulty,
|
||||
submitted_by,
|
||||
);
|
||||
self.workers
|
||||
.update_stats(worker_id, |worker_stats| worker_stats.num_accepted += 1);
|
||||
let submit_response = if share_is_block {
|
||||
format!("blockfound - {}", b.hash().to_hex())
|
||||
} else {
|
||||
"ok".to_string()
|
||||
};
|
||||
return Ok((
|
||||
serde_json::to_value(submit_response).unwrap(),
|
||||
share_is_block,
|
||||
));
|
||||
} // handle submit a solution
|
||||
|
||||
fn broadcast_job(&self) {
|
||||
debug!("broadcast job");
|
||||
// Package new block into RpcRequest
|
||||
let job_template = self.build_block_template();
|
||||
let job_template_json = serde_json::to_string(&job_template).unwrap();
|
||||
// Issue #1159 - use a serde_json Value type to avoid extra quoting
|
||||
let job_template_value: Value = serde_json::from_str(&job_template_json).unwrap();
|
||||
let job_request = RpcRequest {
|
||||
id: JsonId::StrId(String::from("Stratum")),
|
||||
jsonrpc: String::from("2.0"),
|
||||
method: String::from("job"),
|
||||
params: Some(job_template_value),
|
||||
};
|
||||
let job_request_json = serde_json::to_string(&job_request).unwrap();
|
||||
debug!(
|
||||
"(Server ID: {}) sending block {} with id {} to stratum clients",
|
||||
self.id, job_template.height, job_template.job_id,
|
||||
);
|
||||
self.workers.broadcast(job_request_json);
|
||||
}
|
||||
|
||||
pub fn run(&self, config: &StratumServerConfig, tx_pool: &ServerTxPool, stop_state: Arc<StopState>) {
|
||||
debug!("Run main loop");
|
||||
let mut deadline: i64 = 0;
|
||||
let mut head = self.chain.head().unwrap();
|
||||
let mut current_hash = head.prev_block_h;
|
||||
loop {
|
||||
// Ping stratum socket on stop to handle TcpListener unbind.
|
||||
if stop_state.is_stopped() {
|
||||
let listen_addr: SocketAddr = config
|
||||
.stratum_server_addr
|
||||
.clone()
|
||||
.unwrap()
|
||||
.parse()
|
||||
.expect("Stratum: Incorrect address ");
|
||||
thread::spawn(move || {
|
||||
let _ = TcpStream::connect(listen_addr).unwrap();
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
// get the latest chain state
|
||||
head = self.chain.head().unwrap();
|
||||
let latest_hash = head.last_block_h;
|
||||
|
||||
// Build a new block if there is at least one worker and
|
||||
// There is a new block on the chain or its time to rebuild
|
||||
// the current one to include new transactions
|
||||
if (current_hash != latest_hash || Utc::now().timestamp() >= deadline)
|
||||
&& self.workers.count() > 0
|
||||
{
|
||||
{
|
||||
debug!("resend updated block");
|
||||
let mut state = self.current_state.write();
|
||||
let wallet_listener_url = if !config.burn_reward {
|
||||
Some(config.wallet_listener_url.clone())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
// If this is a new block we will clear the current_block version history
|
||||
let clear_blocks = current_hash != latest_hash;
|
||||
|
||||
// Build the new block (version)
|
||||
let (new_block, block_fees) = mine_block::get_block(
|
||||
&self.chain,
|
||||
tx_pool,
|
||||
state.current_key_id.clone(),
|
||||
wallet_listener_url,
|
||||
);
|
||||
|
||||
// scaled difficulty
|
||||
state.current_difficulty =
|
||||
(new_block.header.total_difficulty() - head.total_difficulty).to_num();
|
||||
|
||||
state.current_key_id = block_fees.key_id();
|
||||
|
||||
current_hash = latest_hash;
|
||||
// set the minimum acceptable share unscaled difficulty for this block
|
||||
state.minimum_share_difficulty = config.minimum_share_difficulty;
|
||||
|
||||
// set a new deadline for rebuilding with fresh transactions
|
||||
deadline = Utc::now().timestamp() + config.attempt_time_per_block as i64;
|
||||
|
||||
// If this is a new block we will clear the current_block version history
|
||||
if clear_blocks {
|
||||
state.current_block_versions.clear();
|
||||
}
|
||||
|
||||
// Update the mining stats
|
||||
self.workers.update_block_height(new_block.header.height);
|
||||
let difficulty = new_block.header.total_difficulty() - head.total_difficulty;
|
||||
self.workers.update_network_difficulty(difficulty.to_num());
|
||||
self.workers.update_network_hashrate();
|
||||
|
||||
// Add this new block candidate onto our list of block versions for this height
|
||||
state.current_block_versions.push(new_block);
|
||||
}
|
||||
// Send this job to all connected workers
|
||||
self.broadcast_job();
|
||||
}
|
||||
|
||||
// sleep before restarting loop
|
||||
thread::sleep(Duration::from_millis(5));
|
||||
} // Main Loop
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------
|
||||
// Worker Factory Thread Function
|
||||
fn accept_connections(listen_addr: SocketAddr, handler: Arc<Handler>, stop_state: Arc<StopState>) {
|
||||
info!("Start tokio stratum server");
|
||||
|
||||
let task = async move {
|
||||
let mut listener = TcpListener::bind(&listen_addr).await.unwrap_or_else(|_| {
|
||||
panic!("Stratum: Failed to bind to listen address {}", listen_addr)
|
||||
});
|
||||
let state_socket = &stop_state.clone();
|
||||
let server = listener
|
||||
.incoming()
|
||||
.filter_map(|s| async { s.map_err(|e| error!("accept error = {:?}", e)).ok() })
|
||||
.for_each(move |socket| {
|
||||
let handler = handler.clone();
|
||||
async move {
|
||||
// Stop listener on node server stop.
|
||||
if state_socket.is_stopped() {
|
||||
panic_any("Stopped");
|
||||
}
|
||||
// Spawn a task to process the connection
|
||||
let (tx, mut rx) = mpsc::unbounded();
|
||||
|
||||
let worker_id = handler.workers.add_worker(tx);
|
||||
info!("Worker {} connected", worker_id);
|
||||
|
||||
let framed = Framed::new(socket, LinesCodec::new());
|
||||
let (mut writer, mut reader) = framed.split();
|
||||
|
||||
let h = handler.clone();
|
||||
let read = async move {
|
||||
while let Some(line) = reader
|
||||
.try_next()
|
||||
.await
|
||||
.map_err(|e| error!("error reading line: {}", e))?
|
||||
{
|
||||
let request = serde_json::from_str(&line)
|
||||
.map_err(|e| error!("error serializing line: {}", e))?;
|
||||
let resp = h.handle_rpc_requests(request, worker_id);
|
||||
h.workers.send_to(worker_id, resp);
|
||||
}
|
||||
|
||||
Result::<_, ()>::Ok(())
|
||||
};
|
||||
|
||||
let write = async move {
|
||||
while let Some(line) = rx.next().await {
|
||||
writer
|
||||
.send(line)
|
||||
.await
|
||||
.map_err(|e| error!("error writing line: {}", e))?;
|
||||
}
|
||||
|
||||
Result::<_, ()>::Ok(())
|
||||
};
|
||||
|
||||
let task = async move {
|
||||
pin_mut!(read, write);
|
||||
futures::future::select(read, write).await;
|
||||
handler.workers.remove_worker(worker_id);
|
||||
info!("Worker {} disconnected", worker_id);
|
||||
};
|
||||
tokio::spawn(task);
|
||||
}
|
||||
});
|
||||
server.await
|
||||
};
|
||||
let mut rt = Runtime::new().unwrap();
|
||||
rt.block_on(task);
|
||||
}
|
||||
|
||||
// ----------------------------------------
|
||||
// Worker Object - a connected stratum client - a miner, pool, proxy, etc...
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Worker {
|
||||
id: usize,
|
||||
agent: String,
|
||||
login: Option<String>,
|
||||
authenticated: bool,
|
||||
tx: Tx,
|
||||
}
|
||||
|
||||
impl Worker {
|
||||
/// Creates a new Stratum Worker.
|
||||
pub fn new(id: usize, tx: Tx) -> Worker {
|
||||
Worker {
|
||||
id: id,
|
||||
agent: String::from(""),
|
||||
login: None,
|
||||
authenticated: false,
|
||||
tx: tx,
|
||||
}
|
||||
}
|
||||
} // impl Worker
|
||||
|
||||
struct WorkersList {
|
||||
workers_list: Arc<RwLock<HashMap<usize, Worker>>>,
|
||||
stratum_stats: Arc<RwLock<StratumStats>>,
|
||||
}
|
||||
|
||||
impl WorkersList {
|
||||
pub fn new(stratum_stats: Arc<RwLock<StratumStats>>) -> Self {
|
||||
WorkersList {
|
||||
workers_list: Arc::new(RwLock::new(HashMap::new())),
|
||||
stratum_stats: stratum_stats,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_worker(&self, tx: Tx) -> usize {
|
||||
let mut stratum_stats = self.stratum_stats.write();
|
||||
let worker_id = stratum_stats.worker_stats.len();
|
||||
let worker = Worker::new(worker_id, tx);
|
||||
let mut workers_list = self.workers_list.write();
|
||||
workers_list.insert(worker_id, worker);
|
||||
|
||||
let mut worker_stats = WorkerStats::default();
|
||||
worker_stats.is_connected = true;
|
||||
worker_stats.id = worker_id.to_string();
|
||||
worker_stats.pow_difficulty = stratum_stats.minimum_share_difficulty;
|
||||
stratum_stats.worker_stats.push(worker_stats);
|
||||
stratum_stats.num_workers = workers_list.len();
|
||||
worker_id
|
||||
}
|
||||
pub fn remove_worker(&self, worker_id: usize) {
|
||||
self.update_stats(worker_id, |ws| ws.is_connected = false);
|
||||
let mut stratum_stats = self.stratum_stats.write();
|
||||
let mut workers_list = self.workers_list.write();
|
||||
workers_list
|
||||
.remove(&worker_id)
|
||||
.expect("Stratum: no such addr in map");
|
||||
|
||||
stratum_stats.num_workers = workers_list.len();
|
||||
}
|
||||
|
||||
pub fn login(&self, worker_id: usize, login: String, agent: String) -> Result<(), RpcError> {
|
||||
let mut wl = self.workers_list.write();
|
||||
let mut worker = wl
|
||||
.get_mut(&worker_id)
|
||||
.ok_or_else(RpcError::internal_error)?;
|
||||
worker.login = Some(login);
|
||||
// XXX TODO Future - Validate password?
|
||||
worker.agent = agent;
|
||||
worker.authenticated = true;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_worker(&self, worker_id: usize) -> Result<Worker, RpcError> {
|
||||
self.workers_list
|
||||
.read()
|
||||
.get(&worker_id)
|
||||
.ok_or_else(|| {
|
||||
error!("Worker {} not found", worker_id);
|
||||
RpcError::internal_error()
|
||||
})
|
||||
.map(|w| w.clone())
|
||||
}
|
||||
|
||||
pub fn get_stats(&self, worker_id: usize) -> Result<WorkerStats, RpcError> {
|
||||
self.stratum_stats
|
||||
.read()
|
||||
.worker_stats
|
||||
.get(worker_id)
|
||||
.ok_or_else(RpcError::internal_error)
|
||||
.map(|ws| ws.clone())
|
||||
}
|
||||
|
||||
pub fn last_seen(&self, worker_id: usize) {
|
||||
//self.stratum_stats.write().worker_stats[worker_id].last_seen = SystemTime::now();
|
||||
self.update_stats(worker_id, |ws| ws.last_seen = SystemTime::now());
|
||||
}
|
||||
|
||||
pub fn update_stats(&self, worker_id: usize, f: impl FnOnce(&mut WorkerStats) -> ()) {
|
||||
let mut stratum_stats = self.stratum_stats.write();
|
||||
f(&mut stratum_stats.worker_stats[worker_id]);
|
||||
}
|
||||
|
||||
pub fn send_to(&self, worker_id: usize, msg: String) {
|
||||
let _ = self
|
||||
.workers_list
|
||||
.read()
|
||||
.get(&worker_id)
|
||||
.unwrap()
|
||||
.tx
|
||||
.unbounded_send(msg);
|
||||
}
|
||||
|
||||
pub fn broadcast(&self, msg: String) {
|
||||
for worker in self.workers_list.read().values() {
|
||||
let _ = worker.tx.unbounded_send(msg.clone());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn count(&self) -> usize {
|
||||
self.workers_list.read().len()
|
||||
}
|
||||
|
||||
pub fn update_edge_bits(&self, edge_bits: u16) {
|
||||
{
|
||||
let mut stratum_stats = self.stratum_stats.write();
|
||||
stratum_stats.edge_bits = edge_bits;
|
||||
}
|
||||
self.update_network_hashrate();
|
||||
}
|
||||
|
||||
pub fn update_block_height(&self, height: u64) {
|
||||
let mut stratum_stats = self.stratum_stats.write();
|
||||
stratum_stats.block_height = height;
|
||||
}
|
||||
|
||||
pub fn update_network_difficulty(&self, difficulty: u64) {
|
||||
let mut stratum_stats = self.stratum_stats.write();
|
||||
stratum_stats.network_difficulty = difficulty;
|
||||
}
|
||||
|
||||
pub fn update_network_hashrate(&self) {
|
||||
let mut stratum_stats = self.stratum_stats.write();
|
||||
stratum_stats.network_hashrate = 42.0
|
||||
* (stratum_stats.network_difficulty as f64
|
||||
/ graph_weight(stratum_stats.block_height, stratum_stats.edge_bits as u8) as f64)
|
||||
/ 60.0;
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------
|
||||
// Grin Stratum Server
|
||||
|
||||
pub struct StratumServer {
|
||||
id: String,
|
||||
config: StratumServerConfig,
|
||||
chain: Arc<grin_chain::Chain>,
|
||||
pub tx_pool: ServerTxPool,
|
||||
sync_state: Arc<SyncState>,
|
||||
stratum_stats: Arc<RwLock<StratumStats>>,
|
||||
}
|
||||
|
||||
impl StratumServer {
|
||||
/// Creates a new Stratum Server.
|
||||
pub fn new(
|
||||
config: StratumServerConfig,
|
||||
chain: Arc<grin_chain::Chain>,
|
||||
tx_pool: ServerTxPool,
|
||||
stratum_stats: Arc<RwLock<StratumStats>>,
|
||||
) -> StratumServer {
|
||||
StratumServer {
|
||||
id: String::from("0"),
|
||||
config,
|
||||
chain,
|
||||
tx_pool,
|
||||
sync_state: Arc::new(SyncState::new()),
|
||||
stratum_stats: stratum_stats,
|
||||
}
|
||||
}
|
||||
|
||||
/// "main()" - Starts the stratum-server. Creates a thread to Listens for
|
||||
/// a connection, then enters a loop, building a new block on top of the
|
||||
/// existing chain anytime required and sending that to the connected
|
||||
/// stratum miner, proxy, or pool, and accepts full solutions to
|
||||
/// be submitted.
|
||||
pub fn run_loop(&mut self, proof_size: usize, sync_state: Arc<SyncState>, stop_state: Arc<StopState>) {
|
||||
info!(
|
||||
"(Server ID: {}) Starting stratum server with proof_size = {}",
|
||||
self.id, proof_size
|
||||
);
|
||||
|
||||
self.sync_state = sync_state;
|
||||
|
||||
let listen_addr = self
|
||||
.config
|
||||
.stratum_server_addr
|
||||
.clone()
|
||||
.unwrap()
|
||||
.parse()
|
||||
.expect("Stratum: Incorrect address ");
|
||||
|
||||
let handler = Arc::new(Handler::from_stratum(&self));
|
||||
let h = handler.clone();
|
||||
|
||||
let s_state = stop_state.clone();
|
||||
|
||||
let _listener_th = thread::spawn(move || {
|
||||
accept_connections(listen_addr, h, s_state);
|
||||
});
|
||||
|
||||
// We have started
|
||||
{
|
||||
let mut stratum_stats = self.stratum_stats.write();
|
||||
stratum_stats.is_running = true;
|
||||
stratum_stats.edge_bits = (global::min_edge_bits() + 1) as u16;
|
||||
stratum_stats.minimum_share_difficulty = self.config.minimum_share_difficulty;
|
||||
}
|
||||
|
||||
warn!(
|
||||
"Stratum server started on {}",
|
||||
self.config.stratum_server_addr.clone().unwrap()
|
||||
);
|
||||
|
||||
// Initial Loop. Waiting node complete syncing
|
||||
while self.sync_state.is_syncing() {
|
||||
thread::sleep(Duration::from_millis(50));
|
||||
}
|
||||
|
||||
handler.run(&self.config, &self.tx_pool, stop_state.clone());
|
||||
} // fn run_loop()
|
||||
} // StratumServer
|
||||
|
||||
// Utility function to parse a JSON RPC parameter object, returning a proper
|
||||
// error if things go wrong.
|
||||
fn parse_params<T>(params: Option<Value>) -> Result<T, RpcError>
|
||||
where
|
||||
for<'de> T: serde::Deserialize<'de>,
|
||||
{
|
||||
params
|
||||
.and_then(|v| serde_json::from_value(v).ok())
|
||||
.ok_or_else(RpcError::invalid_request)
|
||||
}
|
Loading…
Reference in a new issue