From 8bd46cd758fd6f87b300e2714799b7006b747b92 Mon Sep 17 00:00:00 2001 From: striderDM <51991544+StriderDM@users.noreply.github.com> Date: Thu, 10 Jun 2021 01:24:59 +0200 Subject: [PATCH] Miningcore Transcoder Update proxy.rs WIP Added stratum configuration to miner. Mostly implemented stratum. Mostly implemented stratum controller. Partially implemented stratum miner. Rebased to latest dev Import PR#3006 Rebased to latest dev and updated version to 0.9.0 Fixed tari_stratum_ffi tests Clippy and cargo-fmt Bug fixes Return blockheader as json object. Retrieve recipients from params instead of directly from body of request. Fix bug in GetHeaderByHeight Update stratum miner to receive blockheader instead of block clippy update Update Implemented keepalive Bug fix for transfer results Implemented stratum error code response handling in tari_mining_node Rebase fix Update stratum.rs Update stratum.rs Review Comments Update and Fixes Added ResumeJob to MinerMessage. Fixed disconnection bug where miner would not resume solves on reconnect. Added transcoder_host_address config variable to stop using proxy_host_address as it is already used by mm_proxy, this enables them both to be run simultaneously. Update cucumber config variables --- Cargo.lock | 317 +++++++-- Cargo.toml | 2 + applications/tari_mining_node/Cargo.toml | 9 +- applications/tari_mining_node/src/config.rs | 4 + applications/tari_mining_node/src/main.rs | 158 +++-- .../src/stratum/controller.rs | 385 +++++++++++ .../tari_mining_node/src/stratum/error.rs | 48 ++ .../tari_mining_node/src/stratum/mod.rs | 28 + .../stratum/stratum_controller/controller.rs | 117 ++++ .../src/stratum/stratum_controller/mod.rs | 23 + .../stratum/stratum_miner/control_message.rs | 29 + .../stratum/stratum_miner/job_shared_data.rs | 59 ++ .../src/stratum/stratum_miner/miner.rs | 268 ++++++++ .../src/stratum/stratum_miner/mod.rs | 27 + .../src/stratum/stratum_miner/solution.rs | 42 ++ .../stratum/stratum_miner/solver_instance.rs | 39 ++ .../stratum/stratum_types/client_message.rs | 31 + .../src/stratum/stratum_types/job.rs | 32 + .../src/stratum/stratum_types/job_params.rs | 31 + .../src/stratum/stratum_types/login_params.rs | 30 + .../stratum/stratum_types/login_response.rs | 30 + .../stratum/stratum_types/miner_message.rs | 32 + .../src/stratum/stratum_types/mod.rs | 34 + .../src/stratum/stratum_types/rpc_error.rs | 29 + .../src/stratum/stratum_types/rpc_request.rs | 32 + .../src/stratum/stratum_types/rpc_response.rs | 32 + .../stratum/stratum_types/submit_params.rs | 31 + .../stratum_types/worker_identifier.rs | 28 + .../stratum/stratum_types/worker_status.rs | 33 + .../tari_mining_node/src/stratum/stream.rs | 133 ++++ .../tari_stratum_transcoder/Cargo.toml | 49 ++ .../src/common/json_rpc.rs | 121 ++++ .../src/common/mining.rs | 54 ++ .../tari_stratum_transcoder/src/common/mod.rs | 25 + .../src/common/proxy.rs | 64 ++ .../tari_stratum_transcoder/src/error.rs | 77 +++ .../tari_stratum_transcoder/src/main.rs | 100 +++ .../tari_stratum_transcoder/src/proxy.rs | 616 ++++++++++++++++++ base_layer/tari_stratum_ffi/Cargo.toml | 23 + base_layer/tari_stratum_ffi/src/error.rs | 87 +++ base_layer/tari_stratum_ffi/src/lib.rs | 409 ++++++++++++ .../config/presets/tari_config_example.toml | 1 + common/src/configuration/bootstrap.rs | 15 + common/src/configuration/global.rs | 20 + common/src/configuration/utils.rs | 4 + common/src/lib.rs | 1 + common/src/logging.rs | 11 + integration_tests/helpers/baseNodeProcess.js | 1 + integration_tests/helpers/config.js | 4 + .../helpers/mergeMiningProxyProcess.js | 2 + .../helpers/miningNodeProcess.js | 1 + integration_tests/helpers/walletProcess.js | 1 + 52 files changed, 3686 insertions(+), 93 deletions(-) create mode 100644 applications/tari_mining_node/src/stratum/controller.rs create mode 100644 applications/tari_mining_node/src/stratum/error.rs create mode 100644 applications/tari_mining_node/src/stratum/mod.rs create mode 100644 applications/tari_mining_node/src/stratum/stratum_controller/controller.rs create mode 100644 applications/tari_mining_node/src/stratum/stratum_controller/mod.rs create mode 100644 applications/tari_mining_node/src/stratum/stratum_miner/control_message.rs create mode 100644 applications/tari_mining_node/src/stratum/stratum_miner/job_shared_data.rs create mode 100644 applications/tari_mining_node/src/stratum/stratum_miner/miner.rs create mode 100644 applications/tari_mining_node/src/stratum/stratum_miner/mod.rs create mode 100644 applications/tari_mining_node/src/stratum/stratum_miner/solution.rs create mode 100644 applications/tari_mining_node/src/stratum/stratum_miner/solver_instance.rs create mode 100644 applications/tari_mining_node/src/stratum/stratum_types/client_message.rs create mode 100644 applications/tari_mining_node/src/stratum/stratum_types/job.rs create mode 100644 applications/tari_mining_node/src/stratum/stratum_types/job_params.rs create mode 100644 applications/tari_mining_node/src/stratum/stratum_types/login_params.rs create mode 100644 applications/tari_mining_node/src/stratum/stratum_types/login_response.rs create mode 100644 applications/tari_mining_node/src/stratum/stratum_types/miner_message.rs create mode 100644 applications/tari_mining_node/src/stratum/stratum_types/mod.rs create mode 100644 applications/tari_mining_node/src/stratum/stratum_types/rpc_error.rs create mode 100644 applications/tari_mining_node/src/stratum/stratum_types/rpc_request.rs create mode 100644 applications/tari_mining_node/src/stratum/stratum_types/rpc_response.rs create mode 100644 applications/tari_mining_node/src/stratum/stratum_types/submit_params.rs create mode 100644 applications/tari_mining_node/src/stratum/stratum_types/worker_identifier.rs create mode 100644 applications/tari_mining_node/src/stratum/stratum_types/worker_status.rs create mode 100644 applications/tari_mining_node/src/stratum/stream.rs create mode 100644 applications/tari_stratum_transcoder/Cargo.toml create mode 100644 applications/tari_stratum_transcoder/src/common/json_rpc.rs create mode 100644 applications/tari_stratum_transcoder/src/common/mining.rs create mode 100644 applications/tari_stratum_transcoder/src/common/mod.rs create mode 100644 applications/tari_stratum_transcoder/src/common/proxy.rs create mode 100644 applications/tari_stratum_transcoder/src/error.rs create mode 100644 applications/tari_stratum_transcoder/src/main.rs create mode 100644 applications/tari_stratum_transcoder/src/proxy.rs create mode 100644 base_layer/tari_stratum_ffi/Cargo.toml create mode 100644 base_layer/tari_stratum_ffi/src/error.rs create mode 100644 base_layer/tari_stratum_ffi/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 0f8e21f0a7..2a35b17385 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -466,6 +466,12 @@ dependencies = [ "safemem", ] +[[package]] +name = "bufstream" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40e38929add23cdf8a366df9b0e088953150724bcbe5fc330b0d8eb3b328eec8" + [[package]] name = "bumpalo" version = "3.7.0" @@ -1757,12 +1763,31 @@ dependencies = [ "http", "indexmap", "slab", - "tokio", + "tokio 0.2.25", "tokio-util 0.3.1", "tracing", "tracing-futures", ] +[[package]] +name = "h2" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "825343c4eef0b63f541f8903f395dc5beb362a979b5799a84062527ef1e37726" +dependencies = [ + "bytes 1.0.1", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio 1.9.0", + "tokio-util 0.6.7", + "tracing", +] + [[package]] name = "hashbrown" version = "0.11.2" @@ -1820,6 +1845,17 @@ dependencies = [ "http", ] +[[package]] +name = "http-body" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60daa14be0e0786db0f03a9e57cb404c9d756eed2b6c62b9ea98ec5743ec75a9" +dependencies = [ + "bytes 1.0.1", + "http", + "pin-project-lite 0.2.7", +] + [[package]] name = "httparse" version = "1.4.1" @@ -1832,6 +1868,12 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "494b4d60369511e7dea41cf646832512a94e542f68bb9c49e54518e0f468eb47" +[[package]] +name = "httpdate" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6456b8a6c8f33fee7d958fcd1b60d55b11940a79e63ae87013e6d22e26034440" + [[package]] name = "humantime" version = "1.3.0" @@ -1876,15 +1918,39 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "h2", + "h2 0.2.7", "http", - "http-body", + "http-body 0.3.1", "httparse", - "httpdate", + "httpdate 0.3.2", "itoa", "pin-project 1.0.7", - "socket2", - "tokio", + "socket2 0.3.19", + "tokio 0.2.25", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper" +version = "0.14.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b61cf2d1aebcf6e6352c97b81dc2244ca29194be1b276f5d8ad5c6330fffb11" +dependencies = [ + "bytes 1.0.1", + "futures-channel", + "futures-core", + "futures-util", + "h2 0.3.3", + "http", + "http-body 0.4.2", + "httparse", + "httpdate 1.0.1", + "itoa", + "pin-project-lite 0.2.7", + "socket2 0.4.0", + "tokio 1.9.0", "tower-service", "tracing", "want", @@ -1899,10 +1965,23 @@ dependencies = [ "bytes 0.5.6", "hyper 0.13.10", "native-tls", - "tokio", + "tokio 0.2.25", "tokio-tls", ] +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes 1.0.1", + "hyper 0.14.11", + "native-tls", + "tokio 1.9.0", + "tokio-native-tls", +] + [[package]] name = "ident_case" version = "1.0.1" @@ -3476,9 +3555,9 @@ dependencies = [ "futures-core", "futures-util", "http", - "http-body", + "http-body 0.3.1", "hyper 0.13.10", - "hyper-tls", + "hyper-tls 0.4.3", "ipnet", "js-sys", "lazy_static 1.4.0", @@ -3491,7 +3570,7 @@ dependencies = [ "serde 1.0.126", "serde_json", "serde_urlencoded", - "tokio", + "tokio 0.2.25", "tokio-tls", "url 2.2.2", "wasm-bindgen", @@ -3500,6 +3579,41 @@ dependencies = [ "winreg", ] +[[package]] +name = "reqwest" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "246e9f61b9bb77df069a947682be06e31ac43ea37862e244a69f177694ea6d22" +dependencies = [ + "base64 0.13.0", + "bytes 1.0.1", + "encoding_rs", + "futures-core", + "futures-util", + "http", + "http-body 0.4.2", + "hyper 0.14.11", + "hyper-tls 0.5.0", + "ipnet", + "js-sys", + "lazy_static 1.4.0", + "log 0.4.14", + "mime 0.3.16", + "native-tls", + "percent-encoding 2.1.0", + "pin-project-lite 0.2.7", + "serde 1.0.126", + "serde_json", + "serde_urlencoded", + "tokio 1.9.0", + "tokio-native-tls", + "url 2.2.2", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", +] + [[package]] name = "ring" version = "0.16.20" @@ -4022,6 +4136,16 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "socket2" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e3dfc207c526015c632472a77be09cf1b6e46866581aecae5cc38fb4235dea2" +dependencies = [ + "libc", + "winapi 0.3.9", +] + [[package]] name = "spin" version = "0.5.2" @@ -4240,7 +4364,7 @@ dependencies = [ "tari_p2p", "tari_wallet", "thiserror", - "tokio", + "tokio 0.2.25", "tonic", ] @@ -4273,7 +4397,7 @@ dependencies = [ "tari_shutdown", "tari_wallet", "thiserror", - "tokio", + "tokio 0.2.25", "tonic", ] @@ -4329,7 +4453,7 @@ dependencies = [ "rand 0.8.4", "serde 1.0.126", "tari_crypto", - "tokio", + "tokio 0.2.25", ] [[package]] @@ -4368,7 +4492,7 @@ dependencies = [ "tari_test_utils", "tempfile", "thiserror", - "tokio", + "tokio 0.2.25", "tokio-macros", "tokio-util 0.2.0", "tower", @@ -4415,7 +4539,7 @@ dependencies = [ "tari_utilities", "tempfile", "thiserror", - "tokio", + "tokio 0.2.25", "tokio-macros", "tokio-test", "tower", @@ -4434,7 +4558,7 @@ dependencies = [ "syn 1.0.73", "tari_comms", "tari_test_utils", - "tokio", + "tokio 0.2.25", "tokio-macros", "tower-service", ] @@ -4467,7 +4591,7 @@ dependencies = [ "tari_shutdown", "tari_wallet", "thiserror", - "tokio", + "tokio 0.2.25", "tonic", "tui", "unicode-segmentation", @@ -4518,7 +4642,7 @@ dependencies = [ "tari_test_utils", "tempfile", "thiserror", - "tokio", + "tokio 0.2.25", "tokio-macros", "ttl_cache", "uint", @@ -4591,7 +4715,7 @@ dependencies = [ "jsonrpc", "log 0.4.14", "rand 0.8.4", - "reqwest", + "reqwest 0.10.10", "serde 1.0.126", "serde_json", "structopt", @@ -4602,7 +4726,7 @@ dependencies = [ "tari_crypto", "tari_utilities", "thiserror", - "tokio", + "tokio 0.2.25", "tokio-macros", "tonic", "tracing", @@ -4615,14 +4739,20 @@ dependencies = [ name = "tari_mining_node" version = "0.9.1" dependencies = [ + "bufstream", "chrono", "crossbeam", "futures 0.3.15", + "hex", + "jsonrpc", "log 0.4.14", + "native-tls", "num_cpus", "prost-types", "rand 0.8.4", + "reqwest 0.11.4", "serde 1.0.126", + "serde_json", "sha3", "tari_app_grpc", "tari_app_utilities", @@ -4630,7 +4760,8 @@ dependencies = [ "tari_core", "tari_crypto", "thiserror", - "tokio", + "time", + "tokio 0.2.25", "tonic", ] @@ -4672,7 +4803,7 @@ dependencies = [ "pgp", "prost", "rand 0.8.4", - "reqwest", + "reqwest 0.10.10", "semver 1.0.3", "serde 1.0.126", "serde_derive", @@ -4688,7 +4819,7 @@ dependencies = [ "tari_utilities", "tempfile", "thiserror", - "tokio", + "tokio 0.2.25", "tokio-macros", "tower", "tower-service", @@ -4707,7 +4838,7 @@ dependencies = [ "tari_shutdown", "tari_test_utils", "thiserror", - "tokio", + "tokio 0.2.25", "tokio-macros", "tower", "tower-service", @@ -4718,7 +4849,7 @@ name = "tari_shutdown" version = "0.9.1" dependencies = [ "futures 0.3.15", - "tokio", + "tokio 0.2.25", ] [[package]] @@ -4739,6 +4870,60 @@ dependencies = [ "thiserror", ] +[[package]] +name = "tari_stratum_ffi" +version = "0.0.1" +dependencies = [ + "hex", + "libc", + "serde 1.0.126", + "serde_json", + "tari_app_grpc", + "tari_common", + "tari_comms", + "tari_core", + "tari_crypto", + "tari_utilities", + "thiserror", +] + +[[package]] +name = "tari_stratum_transcoder" +version = "0.9.0" +dependencies = [ + "bincode", + "bytes 0.5.6", + "chrono", + "config", + "derive-error", + "env_logger 0.7.1", + "futures 0.3.15", + "futures-test", + "hex", + "hyper 0.13.10", + "jsonrpc", + "log 0.4.14", + "rand 0.7.3", + "reqwest 0.10.10", + "serde 1.0.126", + "serde_json", + "structopt", + "tari_app_grpc", + "tari_common", + "tari_core", + "tari_crypto", + "tari_utilities", + "thiserror", + "tokio 0.2.25", + "tokio-macros", + "tonic", + "tonic-build", + "tracing", + "tracing-futures", + "tracing-subscriber", + "url 2.2.2", +] + [[package]] name = "tari_test_utils" version = "0.9.1" @@ -4748,7 +4933,7 @@ dependencies = [ "lazy_static 1.4.0", "rand 0.8.4", "tempfile", - "tokio", + "tokio 0.2.25", ] [[package]] @@ -4807,7 +4992,7 @@ dependencies = [ "tempfile", "thiserror", "time", - "tokio", + "tokio 0.2.25", "tokio-macros", "tower", ] @@ -4837,7 +5022,7 @@ dependencies = [ "tari_wallet", "tempfile", "thiserror", - "tokio", + "tokio 0.2.25", ] [[package]] @@ -4873,7 +5058,7 @@ dependencies = [ "tari_core", "tari_crypto", "tari_utilities", - "tokio", + "tokio 0.2.25", ] [[package]] @@ -4993,6 +5178,22 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "tokio" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b7b349f11a7047e6d1276853e612d152f5e8a352c61917887cc2169e2366b4c" +dependencies = [ + "autocfg 1.0.1", + "bytes 1.0.1", + "libc", + "memchr", + "mio 0.7.13", + "num_cpus", + "pin-project-lite 0.2.7", + "winapi 0.3.9", +] + [[package]] name = "tokio-macros" version = "0.2.6" @@ -5004,6 +5205,16 @@ dependencies = [ "syn 1.0.73", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" +dependencies = [ + "native-tls", + "tokio 1.9.0", +] + [[package]] name = "tokio-test" version = "0.2.1" @@ -5012,7 +5223,7 @@ checksum = "ed0049c119b6d505c4447f5c64873636c7af6c75ab0d45fd9f618d82acb8016d" dependencies = [ "bytes 0.5.6", "futures-core", - "tokio", + "tokio 0.2.25", ] [[package]] @@ -5022,7 +5233,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a70f4fcd7b3b24fb194f837560168208f669ca8cb70d0c4b862944452396343" dependencies = [ "native-tls", - "tokio", + "tokio 0.2.25", ] [[package]] @@ -5036,7 +5247,7 @@ dependencies = [ "futures-sink", "log 0.4.14", "pin-project-lite 0.1.12", - "tokio", + "tokio 0.2.25", ] [[package]] @@ -5050,7 +5261,21 @@ dependencies = [ "futures-sink", "log 0.4.14", "pin-project-lite 0.1.12", - "tokio", + "tokio 0.2.25", +] + +[[package]] +name = "tokio-util" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1caa0b0c8d94a049db56b5acf8cba99dc0623aab1b26d5b5f5e2d945846b3592" +dependencies = [ + "bytes 1.0.1", + "futures-core", + "futures-sink", + "log 0.4.14", + "pin-project-lite 0.2.7", + "tokio 1.9.0", ] [[package]] @@ -5084,13 +5309,13 @@ dependencies = [ "futures-core", "futures-util", "http", - "http-body", + "http-body 0.3.1", "hyper 0.13.10", "percent-encoding 2.1.0", "pin-project 0.4.28", "prost", "prost-derive", - "tokio", + "tokio 0.2.25", "tokio-util 0.3.1", "tower", "tower-balance", @@ -5143,7 +5368,7 @@ dependencies = [ "pin-project 0.4.28", "rand 0.7.3", "slab", - "tokio", + "tokio 0.2.25", "tower-discover", "tower-layer", "tower-load", @@ -5161,7 +5386,7 @@ checksum = "c4887dc2a65d464c8b9b66e0e4d51c2fd6cf5b3373afc72805b0a60bce00446a" dependencies = [ "futures-core", "pin-project 0.4.28", - "tokio", + "tokio 0.2.25", "tower-layer", "tower-service", "tracing", @@ -5192,7 +5417,7 @@ checksum = "92c3040c5dbed68abffaa0d4517ac1a454cd741044f33ab0eefab6b8d1361404" dependencies = [ "futures-core", "pin-project 0.4.28", - "tokio", + "tokio 0.2.25", "tower-layer", "tower-load", "tower-service", @@ -5207,7 +5432,7 @@ dependencies = [ "futures-core", "log 0.4.14", "pin-project 0.4.28", - "tokio", + "tokio 0.2.25", "tower-discover", "tower-service", ] @@ -5230,7 +5455,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce50370d644a0364bf4877ffd4f76404156a248d104e2cc234cd391ea5cdc965" dependencies = [ - "tokio", + "tokio 0.2.25", "tower-service", ] @@ -5244,7 +5469,7 @@ dependencies = [ "futures-util", "indexmap", "log 0.4.14", - "tokio", + "tokio 0.2.25", "tower-service", ] @@ -5256,7 +5481,7 @@ checksum = "e6727956aaa2f8957d4d9232b308fe8e4e65d99db30f42b225646e86c9b6a952" dependencies = [ "futures-core", "pin-project 0.4.28", - "tokio", + "tokio 0.2.25", "tower-layer", "tower-service", ] @@ -5275,7 +5500,7 @@ checksum = "9ba4bbc2c1e4a8543c30d4c13a4c8314ed72d6e07581910f665aa13fde0153c8" dependencies = [ "futures-util", "pin-project 0.4.28", - "tokio", + "tokio 0.2.25", "tokio-test", "tower-layer", "tower-service", @@ -5288,7 +5513,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "127b8924b357be938823eaaec0608c482d40add25609481027b96198b2e4b31e" dependencies = [ "pin-project 0.4.28", - "tokio", + "tokio 0.2.25", "tower-layer", "tower-service", ] @@ -5414,7 +5639,7 @@ dependencies = [ "ring", "rustls", "thiserror", - "tokio", + "tokio 0.2.25", "trust-dns-proto", "webpki", ] @@ -5438,7 +5663,7 @@ dependencies = [ "ring", "smallvec", "thiserror", - "tokio", + "tokio 0.2.25", "url 2.2.2", ] diff --git a/Cargo.toml b/Cargo.toml index e8e58f668a..cb33a1102d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ members = [ "base_layer/service_framework", "base_layer/wallet", "base_layer/wallet_ffi", + "base_layer/tari_stratum_ffi", "comms", "comms/dht", "comms/rpc_macros", @@ -20,5 +21,6 @@ members = [ "applications/test_faucet", "applications/tari_app_utilities", "applications/tari_merge_mining_proxy", + "applications/tari_stratum_transcoder", "applications/tari_mining_node", ] diff --git a/applications/tari_mining_node/Cargo.toml b/applications/tari_mining_node/Cargo.toml index 48892a1d06..af4ba8ba4e 100644 --- a/applications/tari_mining_node/Cargo.toml +++ b/applications/tari_mining_node/Cargo.toml @@ -24,7 +24,14 @@ serde = { version = "1.0", default_features = false, features = ["derive"] } tonic = { version = "0.2", features = ["transport"] } tokio = { version = "0.2", default_features = false, features = ["rt-core"] } thiserror = "1.0" - +jsonrpc = "0.11.0" +reqwest = { version = "0.11", features = ["blocking", "json"] } +serde_json = "1.0.57" +native-tls = "0.2" +bufstream = "0.1" +time = "0.1" +chrono = "0.4" +hex = "0.4.2" [dev-dependencies] tari_crypto = "0.11.1" diff --git a/applications/tari_mining_node/src/config.rs b/applications/tari_mining_node/src/config.rs index a4e4693c48..45df9bf38b 100644 --- a/applications/tari_mining_node/src/config.rs +++ b/applications/tari_mining_node/src/config.rs @@ -49,6 +49,8 @@ pub struct MinerConfig { pub mine_on_tip_only: bool, pub proof_of_work_algo: ProofOfWork, pub validate_tip_timeout_sec: u64, + pub mining_pool_address: String, + pub mining_wallet_address: String, } #[derive(Serialize, Deserialize, Debug)] @@ -71,6 +73,8 @@ impl Default for MinerConfig { mine_on_tip_only: true, proof_of_work_algo: ProofOfWork::Sha3, validate_tip_timeout_sec: 30, + mining_pool_address: "".to_string(), + mining_wallet_address: "".to_string(), } } } diff --git a/applications/tari_mining_node/src/main.rs b/applications/tari_mining_node/src/main.rs index bf20227311..d5f47d3c1a 100644 --- a/applications/tari_mining_node/src/main.rs +++ b/applications/tari_mining_node/src/main.rs @@ -35,12 +35,24 @@ mod config; mod difficulty; mod errors; mod miner; +mod stratum; mod utils; -use crate::miner::MiningReport; +use crate::{ + miner::MiningReport, + stratum::{stratum_controller::controller::Controller, stratum_miner::miner::StratumMiner}, +}; use errors::{err_empty, MinerError}; use miner::Miner; -use std::{convert::TryFrom, time::Instant}; +use std::{ + convert::TryFrom, + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, + }, + thread, + time::Instant, +}; /// Application entry point fn main() { @@ -48,8 +60,8 @@ fn main() { match rt.block_on(main_inner()) { Ok(_) => std::process::exit(0), Err(exit_code) => { - eprintln!("Fatal error: {}", exit_code); - error!("Exiting with code: {}", exit_code); + eprintln!("Fatal error: {:?}", exit_code); + error!("Exiting with code: {:?}", exit_code); std::process::exit(exit_code.as_i32()) }, } @@ -64,53 +76,107 @@ async fn main_inner() -> Result<(), ExitCodes> { debug!("{:?}", bootstrap); debug!("{:?}", config); - let (mut node_conn, mut wallet_conn) = connect(&config, &global).await.map_err(ExitCodes::grpc)?; + if !config.mining_wallet_address.is_empty() && !config.mining_pool_address.is_empty() { + let url = config.mining_pool_address.clone(); + let miner_address = config.mining_wallet_address.clone(); + let mut mc = Controller::new().unwrap_or_else(|e| { + panic!("Error loading mining controller: {}", e); + }); + let cc = stratum::controller::Controller::new(&url, Some(miner_address), None, None, mc.tx.clone()) + .unwrap_or_else(|e| { + panic!("Error loading stratum client controller: {:?}", e); + }); + let miner_stopped = Arc::new(AtomicBool::new(false)); + let client_stopped = Arc::new(AtomicBool::new(false)); - let mut blocks_found: u64 = 0; - loop { - debug!("Starting new mining cycle"); - match mining_cycle(&mut node_conn, &mut wallet_conn, &config, &bootstrap).await { - err @ Err(MinerError::GrpcConnection(_)) | err @ Err(MinerError::GrpcStatus(_)) => { - // Any GRPC error we will try to reconnect with a standard delay - error!("Connection error: {:?}", err); - loop { + mc.set_client_tx(cc.tx.clone()); + let mut miner = StratumMiner::new(config); + if let Err(e) = miner.start_solvers() { + println!("Error. Please check logs for further info."); + println!("Error details:"); + println!("{:?}", e); + println!("Exiting"); + } + + let miner_stopped_internal = miner_stopped.clone(); + let _ = thread::Builder::new() + .name("mining_controller".to_string()) + .spawn(move || { + if let Err(e) = mc.run(miner) { + error!("Error. Please check logs for further info: {:?}", e); + return; + } + miner_stopped_internal.store(true, Ordering::Relaxed); + }); + + let client_stopped_internal = client_stopped.clone(); + let _ = thread::Builder::new() + .name("client_controller".to_string()) + .spawn(move || { + cc.run(); + client_stopped_internal.store(true, Ordering::Relaxed); + }); + + loop { + if miner_stopped.load(Ordering::Relaxed) && client_stopped.load(Ordering::Relaxed) { + thread::sleep(std::time::Duration::from_millis(100)); + break; + } + thread::sleep(std::time::Duration::from_millis(100)); + } + Ok(()) + } else { + config.mine_on_tip_only = global.mine_on_tip_only; + debug!("mine_on_tip_only is {}", config.mine_on_tip_only); + + let (mut node_conn, mut wallet_conn) = connect(&config, &global).await.map_err(ExitCodes::grpc)?; + + let mut blocks_found: u64 = 0; + loop { + debug!("Starting new mining cycle"); + match mining_cycle(&mut node_conn, &mut wallet_conn, &config, &bootstrap).await { + err @ Err(MinerError::GrpcConnection(_)) | err @ Err(MinerError::GrpcStatus(_)) => { + // Any GRPC error we will try to reconnect with a standard delay + error!("Connection error: {:?}", err); + loop { + debug!("Holding for {:?}", config.wait_timeout()); + delay_for(config.wait_timeout()).await; + match connect(&config, &global).await { + Ok((nc, wc)) => { + node_conn = nc; + wallet_conn = wc; + break; + }, + Err(err) => { + error!("Connection error: {:?}", err); + continue; + }, + } + } + }, + Err(MinerError::MineUntilHeightReached(h)) => { + info!("Prescribed blockchain height {} reached. Aborting ...", h); + return Ok(()); + }, + Err(MinerError::MinerLostBlock(h)) => { + info!("Height {} already mined by other node. Restarting ...", h); + }, + Err(err) => { + error!("Error: {:?}", err); debug!("Holding for {:?}", config.wait_timeout()); delay_for(config.wait_timeout()).await; - match connect(&config, &global).await { - Ok((nc, wc)) => { - node_conn = nc; - wallet_conn = wc; - break; - }, - Err(err) => { - error!("Connection error: {:?}", err); - continue; - }, + }, + Ok(submitted) => { + if submitted { + blocks_found += 1; } - } - }, - Err(MinerError::MineUntilHeightReached(h)) => { - info!("Prescribed blockchain height {} reached. Aborting ...", h); - return Ok(()); - }, - Err(MinerError::MinerLostBlock(h)) => { - info!("Height {} already mined by other node. Restarting ...", h); - }, - Err(err) => { - error!("Error: {:?}", err); - debug!("Holding for {:?}", config.wait_timeout()); - delay_for(config.wait_timeout()).await; - }, - Ok(submitted) => { - if submitted { - blocks_found += 1; - } - if let Some(max_blocks) = bootstrap.miner_max_blocks { - if blocks_found >= max_blocks { - return Ok(()); + if let Some(max_blocks) = bootstrap.miner_max_blocks { + if blocks_found >= max_blocks { + return Ok(()); + } } - } - }, + }, + } } } } diff --git a/applications/tari_mining_node/src/stratum/controller.rs b/applications/tari_mining_node/src/stratum/controller.rs new file mode 100644 index 0000000000..0d30e176f9 --- /dev/null +++ b/applications/tari_mining_node/src/stratum/controller.rs @@ -0,0 +1,385 @@ +// Copyright 2021. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +use crate::stratum::{error::Error, stratum_types as types, stream::Stream}; + +use chrono::Local; +use log::*; +use std::{ + self, + io::{BufRead, ErrorKind, Write}, + sync::mpsc, + thread, +}; + +pub struct Controller { + server_url: String, + server_login: Option, + server_password: Option, + server_tls_enabled: Option, + stream: Option, + rx: mpsc::Receiver, + pub tx: mpsc::Sender, + miner_tx: mpsc::Sender, + last_request_id: String, +} + +// fn invalid_error_response() -> types::RpcError { +// types::RpcError { +// code: 0, +// message: "Invalid error response received".to_owned(), +// } +// } + +impl Controller { + pub fn new( + server_url: &str, + server_login: Option, + server_password: Option, + server_tls_enabled: Option, + miner_tx: mpsc::Sender, + ) -> Result { + let (tx, rx) = mpsc::channel::(); + Ok(Controller { + server_url: server_url.to_string(), + server_login, + server_password, + server_tls_enabled, + stream: None, + tx, + rx, + miner_tx, + last_request_id: "".to_string(), + }) + } + + pub fn try_connect(&mut self) -> Result<(), Error> { + self.stream = Some(Stream::new()); + self.stream + .as_mut() + .unwrap() + .try_connect(&self.server_url, self.server_tls_enabled)?; + Ok(()) + } + + fn read_message(&mut self) -> Result, Error> { + if self.stream.is_none() { + return Err(Error::ConnectionError("broken pipe".to_string())); + } + let mut line = String::new(); + match self.stream.as_mut().unwrap().read_line(&mut line) { + Ok(_) => { + // stream is not returning a proper error on disconnect + if line.is_empty() { + return Err(Error::ConnectionError("broken pipe".to_string())); + } + Ok(Some(line)) + }, + Err(ref e) if e.kind() == ErrorKind::BrokenPipe => Err(Error::ConnectionError("broken pipe".to_string())), + Err(ref e) if e.kind() == ErrorKind::WouldBlock => Ok(None), + Err(e) => { + error!("Communication error with stratum server: {}", e); + Err(Error::ConnectionError("broken pipe".to_string())) + }, + } + } + + fn send_message(&mut self, message: &str) -> Result<(), Error> { + if self.stream.is_none() { + return Err(Error::ConnectionError(String::from("No server connection"))); + } + debug!("sending request: {}", message); + let _ = self.stream.as_mut().unwrap().write(message.as_bytes()); + let _ = self.stream.as_mut().unwrap().write(b"\n"); + let _ = self.stream.as_mut().unwrap().flush(); + Ok(()) + } + + fn send_message_get_job_template(&mut self) -> Result<(), Error> { + let params = types::worker_identifier::WorkerIdentifier { + id: self.last_request_id.clone(), + }; + let req = types::rpc_request::RpcRequest { + id: Some(self.last_request_id.clone()), + jsonrpc: "2.0".to_string(), + method: "getjob".to_string(), + params: Some(serde_json::to_value(params)?), + }; + let req_str = serde_json::to_string(&req)?; + self.send_message(&req_str) + } + + fn send_login(&mut self) -> Result<(), Error> { + // only send the login request if a login string is configured + let login_str = match self.server_login.clone() { + None => "".to_string(), + Some(server_login) => server_login, + }; + if login_str.is_empty() { + return Ok(()); + } + let password_str = match self.server_password.clone() { + None => "".to_string(), + Some(server_password) => server_password, + }; + let params = types::login_params::LoginParams { + login: login_str, + pass: password_str, + agent: "tari-miner".to_string(), + }; + let req_id = self.last_request_id.to_string(); + let req = types::rpc_request::RpcRequest { + id: if req_id.is_empty() { + Some("0".to_string()) + } else { + Some(req_id) + }, + jsonrpc: "2.0".to_string(), + method: "login".to_string(), + params: Some(serde_json::to_value(params)?), + }; + let req_str = serde_json::to_string(&req)?; + self.send_message(&req_str) + } + + fn send_keepalive(&mut self) -> Result<(), Error> { + let req = types::rpc_request::RpcRequest { + id: Some(self.last_request_id.to_string()), + jsonrpc: "2.0".to_string(), + method: "keepalive".to_string(), + params: None, + }; + let req_str = serde_json::to_string(&req)?; + self.send_message(&req_str) + } + + fn send_message_submit(&mut self, job_id: u64, hash: String, nonce: u64) -> Result<(), Error> { + info!("Submitting Solution"); + let params_in = types::submit_params::SubmitParams { + id: self.last_request_id.to_string(), + job_id, + hash, + nonce, + }; + let params = serde_json::to_string(¶ms_in)?; + let req = types::rpc_request::RpcRequest { + id: Some(self.last_request_id.to_string()), + jsonrpc: "2.0".to_string(), + method: "submit".to_string(), + params: Some(serde_json::from_str(¶ms)?), + }; + let req_str = serde_json::to_string(&req)?; + self.send_message(&req_str) + } + + fn send_miner_job(&mut self, job: types::job_params::JobParams) -> Result<(), Error> { + let miner_message = types::miner_message::MinerMessage::ReceivedJob( + job.height, + job.job_id.parse::().unwrap(), + job.target.parse::().unwrap(), + job.blob, + ); + self.miner_tx.send(miner_message).map_err(|e| e.into()) + } + + fn send_miner_stop(&mut self) -> Result<(), Error> { + let miner_message = types::miner_message::MinerMessage::StopJob; + self.miner_tx.send(miner_message).map_err(|e| e.into()) + } + + fn send_miner_resume(&mut self) -> Result<(), Error> { + let miner_message = types::miner_message::MinerMessage::ResumeJob; + self.miner_tx.send(miner_message).map_err(|e| e.into()) + } + + pub fn handle_request(&mut self, req: types::rpc_request::RpcRequest) -> Result<(), Error> { + debug!("Received request type: {}", req.method); + match req.method.as_str() { + "job" => match req.params { + None => Err(Error::RequestError("No params in job request".to_owned())), + Some(params) => { + let job = serde_json::from_value::(params)?; + info!("Got a new job: {:?}", job); + self.send_miner_job(job) + }, + }, + _ => Err(Error::RequestError("Unknown method".to_owned())), + } + } + + pub fn handle_response(&mut self, res: types::rpc_response::RpcResponse) -> Result<(), Error> { + debug!("Received response with id: {}", res.id); + match res.result { + Some(result) => { + let login_response = serde_json::from_value::(result.clone()); + if let Ok(st) = login_response { + println!("{:?}", st); + let date = Local::now(); + println!("\r\n{}", date.format("[%Y-%m-%d][%H:%M:%S]")); + println!("\r\n\r\n"); + self.last_request_id = st.id; + let _ = self.send_miner_job(st.job); + return Ok(()); + }; + let job_response = serde_json::from_value::(result.clone()); + if let Ok(st) = job_response { + println!("{:?}", st); + let date = Local::now(); + println!("\r\n{}", date.format("[%Y-%m-%d][%H:%M:%S]")); + println!("\r\n\r\n"); + let _ = self.send_miner_job(st); + return Ok(()); + }; + let rpc_response = serde_json::from_value::(result); + if let Ok(st) = rpc_response { + let error = st.error; + if let Some(error) = error { + if vec![-1, 24].contains(&error.code) { + // unauthorized + let _ = self.send_login(); + } else if vec![21, 20, 22, 23, 25].contains(&error.code) { + // problem with template + let _ = self.send_message_get_job_template(); + } + } + return Ok(()); + }; + }, + None => { + println!("{:?}", res); + }, + } + Ok(()) + } + + #[allow(clippy::cognitive_complexity)] + pub fn run(mut self) { + let server_read_interval = 1; + let server_retry_interval = 5; + let mut next_server_read = time::get_time().sec + server_read_interval; + let mut next_server_retry = time::get_time().sec; + // Request the first job template + thread::sleep(std::time::Duration::from_secs(1)); + let mut was_disconnected = true; + loop { + // Check our connection status, and try to correct if possible + if self.stream.is_none() { + if !was_disconnected { + let _ = self.send_miner_stop(); + } + was_disconnected = true; + if time::get_time().sec > next_server_retry { + if self.try_connect().is_err() { + let status = format!( + "Connection Status: Can't establish server connection to {}. Will retry every {} seconds", + self.server_url, server_retry_interval + ); + warn!("{}", status); + self.stream = None; + } else { + let status = format!("Connection Status: Connected to server at {}.", self.server_url); + warn!("{}", status); + } + next_server_retry = time::get_time().sec + server_retry_interval; + if self.stream.is_none() { + thread::sleep(std::time::Duration::from_secs(1)); + continue; + } + } + } else { + // get new job template + if was_disconnected { + was_disconnected = false; + let _ = self.send_login(); + let _ = self.send_miner_resume(); + } + // read messages from server + if time::get_time().sec > next_server_read { + match self.read_message() { + Ok(message) => { + if let Some(m) = message { + // figure out what kind of message, + // and dispatch appropriately + debug!("Received message: {}", m); + // Deserialize to see what type of object it is + if let Ok(v) = serde_json::from_str::(&m) { + // Is this a response or request? + if v["method"] == "job" { + // this is a request + match serde_json::from_str::(&m) { + Err(e) => error!("Error parsing request {} : {:?}", m, e), + Ok(request) => { + if let Err(err) = self.handle_request(request) { + error!("Error handling request {} : :{:?}", m, err) + } + }, + } + } else { + // this is a response + match serde_json::from_str::(&m) { + Err(e) => error!("Error parsing response {} : {:?}", m, e), + Ok(response) => { + println!("{:?}", response); + if let Err(err) = self.handle_response(response) { + error!("Error handling response {} : :{:?}", m, err) + } + }, + } + } + continue; + } else { + error!("Error parsing message: {}", m) + } + } + }, + Err(e) => { + error!("Error reading message: {:?}", e); + self.stream = None; + continue; + }, + } + next_server_read = time::get_time().sec + server_read_interval; + } + } + + // Talk to the miner algorithm + while let Some(message) = self.rx.try_iter().next() { + debug!("Client received message: {:?}", message); + let result = match message { + types::client_message::ClientMessage::FoundSolution(job_id, hash, nonce) => { + self.send_message_submit(job_id, hash, nonce) + }, + types::client_message::ClientMessage::KeepAlive => self.send_keepalive(), + types::client_message::ClientMessage::Shutdown => { + debug!("Shutting down client controller"); + return; + }, + }; + if let Err(e) = result { + error!("Mining Controller Error {:?}", e); + self.stream = None; + } + } + thread::sleep(std::time::Duration::from_millis(10)); + } // loop + } +} diff --git a/applications/tari_mining_node/src/stratum/error.rs b/applications/tari_mining_node/src/stratum/error.rs new file mode 100644 index 0000000000..ff2bba63c2 --- /dev/null +++ b/applications/tari_mining_node/src/stratum/error.rs @@ -0,0 +1,48 @@ +// Copyright 2021. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +#[derive(Debug)] +pub enum Error { + ConnectionError(String), + RequestError(String), + // ResponseError(String), + JsonError(String), + GeneralError(String), +} + +impl From for Error { + fn from(error: serde_json::error::Error) -> Self { + Error::JsonError(format!("Failed to parse JSON: {:?}", error)) + } +} + +impl From> for Error { + fn from(error: std::sync::PoisonError) -> Self { + Error::GeneralError(format!("Failed to get lock: {:?}", error)) + } +} + +impl From> for Error { + fn from(error: std::sync::mpsc::SendError) -> Self { + Error::GeneralError(format!("Failed to send to a channel: {:?}", error)) + } +} diff --git a/applications/tari_mining_node/src/stratum/mod.rs b/applications/tari_mining_node/src/stratum/mod.rs new file mode 100644 index 0000000000..b426a7c4c5 --- /dev/null +++ b/applications/tari_mining_node/src/stratum/mod.rs @@ -0,0 +1,28 @@ +// Copyright 2021. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +pub mod controller; +pub mod error; +pub mod stratum_controller; +pub mod stratum_miner; +pub mod stratum_types; +pub mod stream; diff --git a/applications/tari_mining_node/src/stratum/stratum_controller/controller.rs b/applications/tari_mining_node/src/stratum/stratum_controller/controller.rs new file mode 100644 index 0000000000..f40e432e6f --- /dev/null +++ b/applications/tari_mining_node/src/stratum/stratum_controller/controller.rs @@ -0,0 +1,117 @@ +// Copyright 2021. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +use crate::{ + stratum, + stratum::{stratum_miner::miner::StratumMiner, stratum_types as types}, +}; +use log::*; +use std::{self, sync::mpsc, thread, time::SystemTime}; + +pub struct Controller { + rx: mpsc::Receiver, + pub tx: mpsc::Sender, + client_tx: Option>, + current_height: u64, + current_job_id: u64, + current_blob: String, + keep_alive_time: SystemTime, +} + +impl Controller { + pub fn new() -> Result { + let (tx, rx) = mpsc::channel::(); + Ok(Controller { + rx, + tx, + client_tx: None, + current_height: 0, + current_job_id: 0, + current_blob: "".to_string(), + keep_alive_time: SystemTime::now(), + }) + } + + pub fn set_client_tx(&mut self, client_tx: mpsc::Sender) { + self.client_tx = Some(client_tx); + } + + pub fn run(&mut self, mut miner: StratumMiner) -> Result<(), stratum::error::Error> { + loop { + while let Some(message) = self.rx.try_iter().next() { + debug!("Miner received message: {:?}", message); + let result: Result<(), stratum::error::Error> = match message { + types::miner_message::MinerMessage::ReceivedJob(height, job_id, diff, blob) => { + self.current_height = height; + self.current_job_id = job_id; + self.current_blob = blob; + miner.notify( + self.current_job_id, + self.current_height, + self.current_blob.clone(), + diff, + ) + }, + types::miner_message::MinerMessage::StopJob => { + debug!("Stopping jobs"); + miner.pause_solvers(); + Ok(()) + }, + types::miner_message::MinerMessage::ResumeJob => { + debug!("Resuming jobs"); + miner.resume_solvers(); + Ok(()) + }, + types::miner_message::MinerMessage::Shutdown => { + debug!("Stopping jobs and Shutting down mining controller"); + miner.stop_solvers(); + miner.wait_for_solver_shutdown(); + Ok(()) + }, + }; + if let Err(e) = result { + error!("Mining Controller Error {:?}", e); + } + } + + let solutions = miner.get_solutions(); + if let Some(ss) = solutions { + let _ = self + .client_tx + .as_mut() + .unwrap() + .send(types::client_message::ClientMessage::FoundSolution( + ss.job_id, ss.hash, ss.nonce, + )); + self.keep_alive_time = SystemTime::now(); + } else if self.keep_alive_time.elapsed().unwrap().as_secs() >= 30 { + self.keep_alive_time = SystemTime::now(); + let _ = self + .client_tx + .as_mut() + .unwrap() + .send(types::client_message::ClientMessage::KeepAlive); + } + thread::sleep(std::time::Duration::from_millis(100)); + } + } +} diff --git a/applications/tari_mining_node/src/stratum/stratum_controller/mod.rs b/applications/tari_mining_node/src/stratum/stratum_controller/mod.rs new file mode 100644 index 0000000000..ac9690447b --- /dev/null +++ b/applications/tari_mining_node/src/stratum/stratum_controller/mod.rs @@ -0,0 +1,23 @@ +// Copyright 2021. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +pub(crate) mod controller; diff --git a/applications/tari_mining_node/src/stratum/stratum_miner/control_message.rs b/applications/tari_mining_node/src/stratum/stratum_miner/control_message.rs new file mode 100644 index 0000000000..bd5256c489 --- /dev/null +++ b/applications/tari_mining_node/src/stratum/stratum_miner/control_message.rs @@ -0,0 +1,29 @@ +// Copyright 2021. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +#[derive(Debug)] +pub(crate) enum ControlMessage { + Stop, + Pause, + Resume, + SolverStopped(usize), +} diff --git a/applications/tari_mining_node/src/stratum/stratum_miner/job_shared_data.rs b/applications/tari_mining_node/src/stratum/stratum_miner/job_shared_data.rs new file mode 100644 index 0000000000..64987389d3 --- /dev/null +++ b/applications/tari_mining_node/src/stratum/stratum_miner/job_shared_data.rs @@ -0,0 +1,59 @@ +// Copyright 2021. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +use crate::stratum::stratum_miner::solution::Solution; +use std::sync::{Arc, RwLock}; +use tari_core::blocks::BlockHeader; + +pub type JobSharedDataType = Arc>; + +pub struct JobSharedData { + pub job_id: u64, + pub height: u64, + pub header: Option, + pub difficulty: u64, + pub solutions: Vec, +} + +impl Default for JobSharedData { + fn default() -> JobSharedData { + JobSharedData { + job_id: 0, + height: 0, + header: None, + difficulty: 0, + solutions: Vec::new(), + } + } +} + +impl JobSharedData { + pub fn new(_num_solvers: usize) -> JobSharedData { + JobSharedData { + job_id: 0, + height: 0, + header: None, + difficulty: 1, + solutions: Vec::new(), + } + } +} diff --git a/applications/tari_mining_node/src/stratum/stratum_miner/miner.rs b/applications/tari_mining_node/src/stratum/stratum_miner/miner.rs new file mode 100644 index 0000000000..f95da12b27 --- /dev/null +++ b/applications/tari_mining_node/src/stratum/stratum_miner/miner.rs @@ -0,0 +1,268 @@ +// Copyright 2021. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +use crate::{ + config::MinerConfig, + difficulty::BlockHeaderSha3, + stratum, + stratum::stratum_miner::{ + control_message::ControlMessage, + job_shared_data::{JobSharedData, JobSharedDataType}, + solution::Solution, + solver_instance::SolverInstance, + }, +}; +use log::*; +use std::{ + convert::TryFrom, + sync::{mpsc, Arc, RwLock}, + thread, + time, +}; +use tari_core::{ + blocks::BlockHeader, + crypto::tari_utilities::{hex::Hex, Hashable}, +}; + +pub struct StratumMiner { + config: MinerConfig, + pub shared_data: Arc>, + control_txs: Vec>, + solver_loop_txs: Vec>, + solver_stopped_rxs: Vec>, +} + +impl StratumMiner { + pub fn new(config: MinerConfig) -> StratumMiner { + let threads = config.num_mining_threads; + StratumMiner { + config, + shared_data: Arc::new(RwLock::new(JobSharedData::new(threads))), + control_txs: vec![], + solver_loop_txs: vec![], + solver_stopped_rxs: vec![], + } + } + + fn solver_thread( + mut solver: SolverInstance, + instance: usize, + shared_data: JobSharedDataType, + control_rx: mpsc::Receiver, + solver_loop_rx: mpsc::Receiver, + solver_stopped_tx: mpsc::Sender, + ) { + let stop_handle = thread::spawn(move || loop { + while let Some(message) = control_rx.iter().next() { + match message { + ControlMessage::Stop => { + info!("Stopping Solvers"); + return; + }, + ControlMessage::Pause => { + info!("Pausing Solvers"); + }, + ControlMessage::Resume => { + info!("Resuming Solvers"); + }, + _ => {}, + }; + } + }); + + let mut paused = true; + loop { + if let Some(message) = solver_loop_rx.try_iter().next() { + debug!("solver_thread - solver_loop_rx got msg: {:?}", message); + match message { + ControlMessage::Stop => break, + ControlMessage::Pause => { + paused = true; + solver.solver_reset = true; + }, + ControlMessage::Resume => paused = false, + _ => {}, + } + } + + if paused { + thread::sleep(time::Duration::from_micros(100)); + continue; + } + + let header = { shared_data.read().unwrap().header.clone() }; + match header { + Some(header) => { + let height = { shared_data.read().unwrap().height }; + let job_id = { shared_data.read().unwrap().job_id }; + let target_difficulty = { shared_data.read().unwrap().difficulty }; + + let mut hasher = BlockHeaderSha3::new(tari_app_grpc::tari_rpc::BlockHeader::from(header)).unwrap(); + + if solver.solver_reset { + hasher.random_nonce(); + solver.current_nonce = hasher.nonce; + solver.solver_reset = false; + } else { + hasher.nonce = solver.current_nonce; + hasher.inc_nonce(); + solver.current_nonce = hasher.nonce; + } + + let difficulty = hasher.difficulty(); + if difficulty >= target_difficulty { + let block_header: BlockHeader = BlockHeader::try_from(hasher.into_header()).unwrap(); + info!( + "Miner found block header {} with difficulty {:?}", + block_header, difficulty, + ); + + let still_valid = { height == shared_data.read().unwrap().height }; + if still_valid { + let mut s = shared_data.write().unwrap(); + s.solutions.push(Solution { + height, + job_id, + difficulty: target_difficulty, + hash: block_header.hash().to_hex(), + nonce: block_header.nonce, + }); + } + } + solver.solutions = Solution::default(); + thread::sleep(time::Duration::from_micros(100)); + }, + None => { + continue; + }, + } + } + + let _ = stop_handle.join(); + let _ = solver_stopped_tx.send(ControlMessage::SolverStopped(instance)); + } + + pub fn start_solvers(&mut self) -> Result<(), stratum::error::Error> { + let num_solvers = self.config.num_mining_threads; + info!("Spawning {} solvers", num_solvers); + let mut solvers = Vec::with_capacity(num_solvers); + while solvers.len() < solvers.capacity() { + solvers.push(SolverInstance::new()?); + } + for (i, s) in solvers.into_iter().enumerate() { + let sd = self.shared_data.clone(); + let (control_tx, control_rx) = mpsc::channel::(); + let (solver_tx, solver_rx) = mpsc::channel::(); + let (solver_stopped_tx, solver_stopped_rx) = mpsc::channel::(); + self.control_txs.push(control_tx); + self.solver_loop_txs.push(solver_tx); + self.solver_stopped_rxs.push(solver_stopped_rx); + thread::spawn(move || { + StratumMiner::solver_thread(s, i, sd, control_rx, solver_rx, solver_stopped_tx); + }); + } + Ok(()) + } + + pub fn notify( + &mut self, + job_id: u64, + height: u64, + blob: String, + difficulty: u64, + ) -> Result<(), stratum::error::Error> { + let header_hex = hex::decode(blob) + .map_err(|_| stratum::error::Error::JsonError("Blob is not a valid hex value".to_string()))?; + let header: BlockHeader = serde_json::from_str(&String::from_utf8_lossy(&header_hex).to_string())?; + + let mut sd = self.shared_data.write().unwrap(); + let paused = if height != sd.height { + // stop/pause any existing jobs if job is for a new + // height + self.pause_solvers(); + true + } else { + false + }; + + sd.job_id = job_id; + sd.height = height; + sd.difficulty = difficulty; + sd.header = Some(header); + if paused { + self.resume_solvers(); + } + Ok(()) + } + + pub fn get_solutions(&self) -> Option { + { + let mut s = self.shared_data.write().unwrap(); + if !s.solutions.is_empty() { + let sol = s.solutions.pop().unwrap(); + return Some(sol); + } + } + None + } + + pub fn stop_solvers(&self) { + for t in self.control_txs.iter() { + let _ = t.send(ControlMessage::Stop); + } + for t in self.solver_loop_txs.iter() { + let _ = t.send(ControlMessage::Stop); + } + debug!("Stop message sent"); + } + + pub fn pause_solvers(&self) { + for t in self.control_txs.iter() { + let _ = t.send(ControlMessage::Pause); + } + for t in self.solver_loop_txs.iter() { + let _ = t.send(ControlMessage::Pause); + } + debug!("Pause message sent"); + } + + pub fn resume_solvers(&self) { + for t in self.control_txs.iter() { + let _ = t.send(ControlMessage::Resume); + } + for t in self.solver_loop_txs.iter() { + let _ = t.send(ControlMessage::Resume); + } + debug!("Resume message sent"); + } + + pub fn wait_for_solver_shutdown(&self) { + for r in self.solver_stopped_rxs.iter() { + while let Some(message) = r.iter().next() { + if let ControlMessage::SolverStopped(i) = message { + debug!("Solver stopped: {}", i); + break; + } + } + } + } +} diff --git a/applications/tari_mining_node/src/stratum/stratum_miner/mod.rs b/applications/tari_mining_node/src/stratum/stratum_miner/mod.rs new file mode 100644 index 0000000000..6bc9a9b8bc --- /dev/null +++ b/applications/tari_mining_node/src/stratum/stratum_miner/mod.rs @@ -0,0 +1,27 @@ +// Copyright 2021. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +mod control_message; +mod job_shared_data; +pub(crate) mod miner; +mod solution; +mod solver_instance; diff --git a/applications/tari_mining_node/src/stratum/stratum_miner/solution.rs b/applications/tari_mining_node/src/stratum/stratum_miner/solution.rs new file mode 100644 index 0000000000..74b13431f9 --- /dev/null +++ b/applications/tari_mining_node/src/stratum/stratum_miner/solution.rs @@ -0,0 +1,42 @@ +// Copyright 2021. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +#[derive(Clone)] +pub struct Solution { + pub height: u64, + pub job_id: u64, + pub difficulty: u64, + pub hash: String, + pub nonce: u64, +} + +impl Default for Solution { + fn default() -> Solution { + Solution { + height: 0, + job_id: 0, + difficulty: 0, + hash: "".to_string(), + nonce: 0, + } + } +} diff --git a/applications/tari_mining_node/src/stratum/stratum_miner/solver_instance.rs b/applications/tari_mining_node/src/stratum/stratum_miner/solver_instance.rs new file mode 100644 index 0000000000..926399e1d6 --- /dev/null +++ b/applications/tari_mining_node/src/stratum/stratum_miner/solver_instance.rs @@ -0,0 +1,39 @@ +// Copyright 2021. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +use crate::{stratum, stratum::stratum_miner::solution::Solution}; + +pub struct SolverInstance { + pub solutions: Solution, + pub current_nonce: u64, + pub solver_reset: bool, +} + +impl SolverInstance { + pub fn new() -> Result { + Ok(SolverInstance { + solutions: Solution::default(), + current_nonce: u64::default(), + solver_reset: true, + }) + } +} diff --git a/applications/tari_mining_node/src/stratum/stratum_types/client_message.rs b/applications/tari_mining_node/src/stratum/stratum_types/client_message.rs new file mode 100644 index 0000000000..417156e900 --- /dev/null +++ b/applications/tari_mining_node/src/stratum/stratum_types/client_message.rs @@ -0,0 +1,31 @@ +// Copyright 2021. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug)] +pub enum ClientMessage { + // job_id, hash, nonce + FoundSolution(u64, String, u64), + KeepAlive, + Shutdown, +} diff --git a/applications/tari_mining_node/src/stratum/stratum_types/job.rs b/applications/tari_mining_node/src/stratum/stratum_types/job.rs new file mode 100644 index 0000000000..17582c3891 --- /dev/null +++ b/applications/tari_mining_node/src/stratum/stratum_types/job.rs @@ -0,0 +1,32 @@ +// Copyright 2021. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +use serde::{Deserialize, Serialize}; +use tari_core::blocks::Block; + +#[derive(Serialize, Deserialize, Debug)] +pub struct Job { + pub job_id: u64, + pub block: Option, + pub target: u64, + pub height: u64, +} diff --git a/applications/tari_mining_node/src/stratum/stratum_types/job_params.rs b/applications/tari_mining_node/src/stratum/stratum_types/job_params.rs new file mode 100644 index 0000000000..3074086252 --- /dev/null +++ b/applications/tari_mining_node/src/stratum/stratum_types/job_params.rs @@ -0,0 +1,31 @@ +// Copyright 2021. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug)] +pub struct JobParams { + pub job_id: String, + pub blob: String, + pub target: String, + pub height: u64, +} diff --git a/applications/tari_mining_node/src/stratum/stratum_types/login_params.rs b/applications/tari_mining_node/src/stratum/stratum_types/login_params.rs new file mode 100644 index 0000000000..0203f5dec2 --- /dev/null +++ b/applications/tari_mining_node/src/stratum/stratum_types/login_params.rs @@ -0,0 +1,30 @@ +// Copyright 2021. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug)] +pub struct LoginParams { + pub login: String, + pub pass: String, + pub agent: String, +} diff --git a/applications/tari_mining_node/src/stratum/stratum_types/login_response.rs b/applications/tari_mining_node/src/stratum/stratum_types/login_response.rs new file mode 100644 index 0000000000..c53b8f7337 --- /dev/null +++ b/applications/tari_mining_node/src/stratum/stratum_types/login_response.rs @@ -0,0 +1,30 @@ +// Copyright 2021. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +use crate::stratum::stratum_types::job_params::JobParams; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug)] +pub struct LoginResponse { + pub id: String, + pub job: JobParams, +} diff --git a/applications/tari_mining_node/src/stratum/stratum_types/miner_message.rs b/applications/tari_mining_node/src/stratum/stratum_types/miner_message.rs new file mode 100644 index 0000000000..7ce55714bb --- /dev/null +++ b/applications/tari_mining_node/src/stratum/stratum_types/miner_message.rs @@ -0,0 +1,32 @@ +// Copyright 2021. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug)] +pub enum MinerMessage { + // Height, Id, difficulty, HeaderBlob + ReceivedJob(u64, u64, u64, String), + ResumeJob, + StopJob, + Shutdown, +} diff --git a/applications/tari_mining_node/src/stratum/stratum_types/mod.rs b/applications/tari_mining_node/src/stratum/stratum_types/mod.rs new file mode 100644 index 0000000000..432ab296ce --- /dev/null +++ b/applications/tari_mining_node/src/stratum/stratum_types/mod.rs @@ -0,0 +1,34 @@ +// Copyright 2021. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +pub(crate) mod client_message; +pub(crate) mod job; +pub(crate) mod job_params; +pub(crate) mod login_params; +pub(crate) mod login_response; +pub(crate) mod miner_message; +pub(crate) mod rpc_error; +pub(crate) mod rpc_request; +pub(crate) mod rpc_response; +pub(crate) mod submit_params; +pub(crate) mod worker_identifier; +pub(crate) mod worker_status; diff --git a/applications/tari_mining_node/src/stratum/stratum_types/rpc_error.rs b/applications/tari_mining_node/src/stratum/stratum_types/rpc_error.rs new file mode 100644 index 0000000000..118a4977ed --- /dev/null +++ b/applications/tari_mining_node/src/stratum/stratum_types/rpc_error.rs @@ -0,0 +1,29 @@ +// Copyright 2021. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug)] +pub struct RpcError { + pub code: i32, + pub message: String, +} diff --git a/applications/tari_mining_node/src/stratum/stratum_types/rpc_request.rs b/applications/tari_mining_node/src/stratum/stratum_types/rpc_request.rs new file mode 100644 index 0000000000..c5e12eb59f --- /dev/null +++ b/applications/tari_mining_node/src/stratum/stratum_types/rpc_request.rs @@ -0,0 +1,32 @@ +// Copyright 2021. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +use serde::{Deserialize, Serialize}; +use serde_json::Value; + +#[derive(Serialize, Deserialize, Debug)] +pub struct RpcRequest { + pub id: Option, + pub jsonrpc: String, + pub method: String, + pub params: Option, +} diff --git a/applications/tari_mining_node/src/stratum/stratum_types/rpc_response.rs b/applications/tari_mining_node/src/stratum/stratum_types/rpc_response.rs new file mode 100644 index 0000000000..bb1220a1b6 --- /dev/null +++ b/applications/tari_mining_node/src/stratum/stratum_types/rpc_response.rs @@ -0,0 +1,32 @@ +// Copyright 2021. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +use crate::stratum::stratum_types::rpc_error::RpcError; +use serde::{Deserialize, Serialize}; +use serde_json::Value; + +#[derive(Serialize, Deserialize, Debug)] +pub struct RpcResponse { + pub id: String, + pub result: Option, + pub error: Option, +} diff --git a/applications/tari_mining_node/src/stratum/stratum_types/submit_params.rs b/applications/tari_mining_node/src/stratum/stratum_types/submit_params.rs new file mode 100644 index 0000000000..526bc119b4 --- /dev/null +++ b/applications/tari_mining_node/src/stratum/stratum_types/submit_params.rs @@ -0,0 +1,31 @@ +// Copyright 2021. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug)] +pub struct SubmitParams { + pub id: String, + pub job_id: u64, + pub nonce: u64, + pub hash: String, +} diff --git a/applications/tari_mining_node/src/stratum/stratum_types/worker_identifier.rs b/applications/tari_mining_node/src/stratum/stratum_types/worker_identifier.rs new file mode 100644 index 0000000000..9fcbbdf95a --- /dev/null +++ b/applications/tari_mining_node/src/stratum/stratum_types/worker_identifier.rs @@ -0,0 +1,28 @@ +// Copyright 2021. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug)] +pub struct WorkerIdentifier { + pub id: String, +} diff --git a/applications/tari_mining_node/src/stratum/stratum_types/worker_status.rs b/applications/tari_mining_node/src/stratum/stratum_types/worker_status.rs new file mode 100644 index 0000000000..142e3fcb67 --- /dev/null +++ b/applications/tari_mining_node/src/stratum/stratum_types/worker_status.rs @@ -0,0 +1,33 @@ +// Copyright 2021. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug)] +pub struct WorkerStatus { + pub id: String, + pub height: u64, + pub difficulty: u64, + pub accepted: u64, + pub rejected: u64, + pub stale: u64, +} diff --git a/applications/tari_mining_node/src/stratum/stream.rs b/applications/tari_mining_node/src/stratum/stream.rs new file mode 100644 index 0000000000..91902aa040 --- /dev/null +++ b/applications/tari_mining_node/src/stratum/stream.rs @@ -0,0 +1,133 @@ +// Copyright 2021. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +use crate::stratum::error::Error; +use bufstream::BufStream; +use native_tls::{TlsConnector, TlsStream}; +use std::{ + self, + io::{self, BufRead, Read, Write}, + net::TcpStream, +}; + +pub(crate) struct Stream { + stream: Option>, + tls_stream: Option>>, +} + +impl Stream { + pub fn new() -> Stream { + Stream { + stream: None, + tls_stream: None, + } + } + + pub fn try_connect(&mut self, server_url: &str, tls: Option) -> Result<(), Error> { + match TcpStream::connect(server_url) { + Ok(conn) => { + if tls.is_some() && tls.unwrap() { + let connector = TlsConnector::new() + .map_err(|e| Error::ConnectionError(format!("Can't create TLS connector: {:?}", e)))?; + let url_port: Vec<&str> = server_url.split(':').collect(); + let split_url: Vec<&str> = url_port[0].split('.').collect(); + let base_host = format!("{}.{}", split_url[split_url.len() - 2], split_url[split_url.len() - 1]); + let mut stream = connector + .connect(&base_host, conn) + .map_err(|e| Error::ConnectionError(format!("Can't establish TLS connection: {:?}", e)))?; + stream + .get_mut() + .set_nonblocking(true) + .map_err(|e| Error::ConnectionError(format!("Can't switch to nonblocking mode: {:?}", e)))?; + self.tls_stream = Some(BufStream::new(stream)); + } else { + conn.set_nonblocking(true) + .map_err(|e| Error::ConnectionError(format!("Can't switch to nonblocking mode: {:?}", e)))?; + self.stream = Some(BufStream::new(conn)); + } + Ok(()) + }, + Err(e) => Err(Error::ConnectionError(format!("{}", e))), + } + } +} + +impl Write for Stream { + fn write(&mut self, b: &[u8]) -> Result { + if self.tls_stream.is_some() { + self.tls_stream.as_mut().unwrap().write(b) + } else { + self.stream.as_mut().unwrap().write(b) + } + } + + fn flush(&mut self) -> Result<(), std::io::Error> { + if self.tls_stream.is_some() { + self.tls_stream.as_mut().unwrap().flush() + } else { + self.stream.as_mut().unwrap().flush() + } + } +} +impl Read for Stream { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + if self.tls_stream.is_some() { + self.tls_stream.as_mut().unwrap().read(buf) + } else { + self.stream.as_mut().unwrap().read(buf) + } + } +} + +impl BufRead for Stream { + fn fill_buf(&mut self) -> io::Result<&[u8]> { + if self.tls_stream.is_some() { + self.tls_stream.as_mut().unwrap().fill_buf() + } else { + self.stream.as_mut().unwrap().fill_buf() + } + } + + fn consume(&mut self, amt: usize) { + if self.tls_stream.is_some() { + self.tls_stream.as_mut().unwrap().consume(amt) + } else { + self.stream.as_mut().unwrap().consume(amt) + } + } + + fn read_until(&mut self, byte: u8, buf: &mut Vec) -> io::Result { + if self.tls_stream.is_some() { + self.tls_stream.as_mut().unwrap().read_until(byte, buf) + } else { + self.stream.as_mut().unwrap().read_until(byte, buf) + } + } + + fn read_line(&mut self, string: &mut String) -> io::Result { + if self.tls_stream.is_some() { + self.tls_stream.as_mut().unwrap().read_line(string) + } else { + self.stream.as_mut().unwrap().read_line(string) + } + } +} diff --git a/applications/tari_stratum_transcoder/Cargo.toml b/applications/tari_stratum_transcoder/Cargo.toml new file mode 100644 index 0000000000..29f95c82da --- /dev/null +++ b/applications/tari_stratum_transcoder/Cargo.toml @@ -0,0 +1,49 @@ +[package] +name = "tari_stratum_transcoder" +authors = ["The Tari Development Community"] +description = "The tari stratum transcoder for miningcore" +repository = "https://github.com/tari-project/tari" +license = "BSD-3-Clause" +version = "0.9.0" +edition = "2018" + +[features] +default = [] +envlog = ["env_logger"] + +[dependencies] +tari_app_grpc = { path = "../tari_app_grpc" } +tari_common = { path = "../../common" } +tari_core = { path = "../../base_layer/core", default-features = false, features = ["transactions"]} +tari_crypto = "0.11.1" +tari_utilities = "^0.3" +bincode = "1.3.1" +bytes = "0.5.6" +chrono = "0.4.19" +config = { version = "0.9.3" } +derive-error = "0.0.4" +env_logger = { version = "0.7.1", optional = true } +futures = "0.3.5" +hex = "0.4.2" +hyper = "0.13.7" +jsonrpc = "0.11.0" +log = { version = "0.4.8", features = ["std"] } +rand = "0.7.2" +reqwest = {version = "0.10.8", features=["json"]} +serde = { version="1.0.106", features = ["derive"] } +serde_json = "1.0.57" +structopt = { version = "0.3.13", default_features = false } +thiserror = "1.0.15" +tokio = "0.2.10" +tokio-macros = "0.2.5" +tonic = "0.2" +tracing = "0.1" +tracing-futures = "0.2" +tracing-subscriber = "0.2" +url = "2.1.1" + +[build-dependencies] +tonic-build = "0.2" + +[dev-dependencies] +futures-test = "0.3.5" diff --git a/applications/tari_stratum_transcoder/src/common/json_rpc.rs b/applications/tari_stratum_transcoder/src/common/json_rpc.rs new file mode 100644 index 0000000000..976b421b61 --- /dev/null +++ b/applications/tari_stratum_transcoder/src/common/json_rpc.rs @@ -0,0 +1,121 @@ +// Copyright 2021, The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::error::StratumTranscoderProxyError; +use json::json; +use serde_json as json; +use tari_app_grpc::tari_rpc as grpc; +use tari_utilities::hex::Hex; + +/// Create a standard JSON RPC error response +/// More info: https://www.jsonrpc.org/specification#error_object +pub fn standard_error_response( + req_id: Option, + err: jsonrpc::error::StandardError, + data: Option, +) -> json::Value { + let err = jsonrpc::error::standard_error(err, data); + json!({ + "id": req_id.unwrap_or(-1), + "jsonrpc": "2.0", + "error": err, + }) +} + +/// Create a JSON RPC success response +/// More info: https://www.jsonrpc.org/specification#response_object +pub fn success_response(req_id: Option, result: json::Value) -> json::Value { + json!({ + "id": req_id.unwrap_or(-1), + "jsonrpc": "2.0", + "result": result, + }) +} + +/// Create a JSON RPC error response +/// More info: https://www.jsonrpc.org/specification#error_object +pub fn error_response( + req_id: Option, + err_code: i32, + err_message: &str, + err_data: Option, +) -> json::Value { + let mut err = json!({ + "code": err_code, + "message": err_message, + }); + + if let Some(d) = err_data { + err["data"] = d; + } + + json!({ + "id": req_id.unwrap_or(-1), + "jsonrpc": "2.0", + "error": err + }) +} + +/// Convert a BlockHeaderResponse into a JSON response +pub(crate) fn try_into_json_block_header_response( + header: grpc::BlockHeaderResponse, + request_id: Option, +) -> Result { + let grpc::BlockHeaderResponse { + header, + reward, + confirmations, + difficulty, + num_transactions, + } = header; + let header = header.ok_or_else(|| { + StratumTranscoderProxyError::UnexpectedTariBaseNodeResponse( + "Base node GRPC returned an empty header field when calling get_header_by_hash".into(), + ) + })?; + + let blockheader = json!({ + "block_size": 0, // TODO + "depth": confirmations, + "difficulty": difficulty, + "hash": header.hash.to_hex(), + "height": header.height, + "major_version": header.version, + "minor_version": 0, + "nonce": header.nonce, + "num_txes": num_transactions, + // Cannot be an orphan + "orphan_status": false, + "prev_hash": header.prev_hash.to_hex(), + "reward": reward, + "timestamp": header.timestamp.map(|ts| ts.seconds.into()).unwrap_or_else(|| json!(null)), + }); + + Ok(json!({ + "id": request_id.unwrap_or(-1), + "jsonrpc": "2.0", + "result": { + "blockheader": blockheader.as_object().unwrap(), + }, + "status": "OK", + })) +} diff --git a/applications/tari_stratum_transcoder/src/common/mining.rs b/applications/tari_stratum_transcoder/src/common/mining.rs new file mode 100644 index 0000000000..bf4a714b2b --- /dev/null +++ b/applications/tari_stratum_transcoder/src/common/mining.rs @@ -0,0 +1,54 @@ +// Copyright 2020, The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::error::StratumTranscoderProxyError; +use std::convert::TryFrom; +use tari_app_grpc::tari_rpc as grpc; +use tari_core::{ + blocks::NewBlockTemplate, + transactions::transaction::{TransactionKernel, TransactionOutput}, +}; + +pub fn add_coinbase( + coinbase: Option, + mut block: NewBlockTemplate, +) -> Result { + if let Some(tx) = coinbase { + let output = TransactionOutput::try_from(tx.clone().body.unwrap().outputs[0].clone()) + .map_err(StratumTranscoderProxyError::MissingDataError)?; + let kernel = TransactionKernel::try_from(tx.body.unwrap().kernels[0].clone()) + .map_err(StratumTranscoderProxyError::MissingDataError)?; + block.body.add_output(output); + block.body.add_kernel(kernel); + let template = grpc::NewBlockTemplate::try_from(block); + match template { + Ok(template) => Ok(template), + Err(_e) => Err(StratumTranscoderProxyError::MissingDataError( + "Template Invalid".to_string(), + )), + } + } else { + Err(StratumTranscoderProxyError::MissingDataError( + "Coinbase Invalid".to_string(), + )) + } +} diff --git a/applications/tari_stratum_transcoder/src/common/mod.rs b/applications/tari_stratum_transcoder/src/common/mod.rs new file mode 100644 index 0000000000..061292e794 --- /dev/null +++ b/applications/tari_stratum_transcoder/src/common/mod.rs @@ -0,0 +1,25 @@ +// Copyright 2021, The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +pub mod json_rpc; +pub mod mining; +pub mod proxy; diff --git a/applications/tari_stratum_transcoder/src/common/proxy.rs b/applications/tari_stratum_transcoder/src/common/proxy.rs new file mode 100644 index 0000000000..70276e635d --- /dev/null +++ b/applications/tari_stratum_transcoder/src/common/proxy.rs @@ -0,0 +1,64 @@ +// Copyright 2021, The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::error::StratumTranscoderProxyError; +use bytes::BytesMut; +use futures::StreamExt; +use hyper::{header, http::response, Body, Response, StatusCode}; +use serde_json as json; +use std::convert::TryInto; + +pub fn json_response(status: StatusCode, body: &json::Value) -> Result, StratumTranscoderProxyError> { + let body_str = json::to_string(body)?; + Response::builder() + .header(header::CONTENT_TYPE, "application/json".to_string()) + .header(header::CONTENT_LENGTH, body_str.len()) + .status(status) + .body(body_str.into()) + .map_err(Into::into) +} + +pub fn into_response(mut parts: response::Parts, content: &json::Value) -> Response { + let resp = json::to_string(content).expect("json::to_string cannot fail when stringifying a json::Value"); + // Ensure that the content length header is correct + parts.headers.insert(header::CONTENT_LENGTH, resp.len().into()); + parts + .headers + .insert(header::CONTENT_TYPE, "application/json".try_into().unwrap()); + Response::from_parts(parts, resp.into()) +} + +pub fn into_body_from_response(resp: Response) -> Response { + let (parts, body) = resp.into_parts(); + into_response(parts, &body) +} + +/// Reads the `Body` until there is no more to read +pub async fn read_body_until_end(body: &mut Body) -> Result { + // TODO: Perhaps there is a more efficient way to do this + let mut bytes = BytesMut::new(); + while let Some(data) = body.next().await { + let data = data?; + bytes.extend(data); + } + Ok(bytes) +} diff --git a/applications/tari_stratum_transcoder/src/error.rs b/applications/tari_stratum_transcoder/src/error.rs new file mode 100644 index 0000000000..67094cdc92 --- /dev/null +++ b/applications/tari_stratum_transcoder/src/error.rs @@ -0,0 +1,77 @@ +// Copyright 2020. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use hex::FromHexError; +use std::io; +use tari_common::{ConfigError, ConfigurationError}; +use tari_core::transactions::CoinbaseBuildError; +use thiserror::Error; +use tonic::transport; + +#[derive(Debug, Error)] +pub enum StratumTranscoderProxyError { + #[error("Configuration error: {0}")] + ConfigurationError(#[from] ConfigurationError), + #[error("Configuration error: {0}")] + ConfigError(#[from] ConfigError), + #[error("Reqwest error: {0}")] + ReqwestError(#[from] reqwest::Error), + #[error("Missing data:{0}")] + MissingDataError(String), + #[error("An IO error occurred: {0}")] + IoError(#[from] io::Error), + #[error("Tonic transport error: {0}")] + TonicTransportError(#[from] transport::Error), + #[error("GRPC response did not contain the expected field: `{0}`")] + GrpcResponseMissingField(&'static str), + #[error("Hyper error: {0}")] + HyperError(#[from] hyper::Error), + #[error("GRPC request failed with `{status}` {details}")] + GrpcRequestError { + #[source] + status: tonic::Status, + details: String, + }, + #[error("HTTP error: {0}")] + HttpError(#[from] hyper::http::Error), + #[error("Could not parse URL: {0}")] + UrlParseError(#[from] url::ParseError), + #[error("Bincode error: {0}")] + BincodeError(#[from] bincode::Error), + #[error("JSON error: {0}")] + JsonError(#[from] serde_json::Error), + #[error("Hex error: {0}")] + HexError(#[from] FromHexError), + #[error("Coinbase builder error: {0}")] + CoinbaseBuilderError(#[from] CoinbaseBuildError), + #[error("Unexpected Tari base node response: {0}")] + UnexpectedTariBaseNodeResponse(String), +} + +impl From for StratumTranscoderProxyError { + fn from(status: tonic::Status) -> Self { + Self::GrpcRequestError { + details: String::from_utf8_lossy(status.details()).to_string(), + status, + } + } +} diff --git a/applications/tari_stratum_transcoder/src/main.rs b/applications/tari_stratum_transcoder/src/main.rs new file mode 100644 index 0000000000..d55d551b5b --- /dev/null +++ b/applications/tari_stratum_transcoder/src/main.rs @@ -0,0 +1,100 @@ +// Copyright 2020. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#![cfg_attr(not(debug_assertions), deny(unused_variables))] +#![cfg_attr(not(debug_assertions), deny(unused_imports))] +#![cfg_attr(not(debug_assertions), deny(dead_code))] +#![cfg_attr(not(debug_assertions), deny(unused_extern_crates))] +#![deny(unused_must_use)] +#![deny(unreachable_patterns)] +#![deny(unknown_lints)] + +mod common; +mod error; +mod proxy; + +use crate::error::StratumTranscoderProxyError; +use futures::future; +use hyper::{service::make_service_fn, Server}; +use proxy::{StratumTranscoderProxyConfig, StratumTranscoderProxyService}; +use std::convert::Infallible; +use structopt::StructOpt; +use tari_app_grpc::tari_rpc as grpc; +use tari_common::{configuration::bootstrap::ApplicationType, ConfigBootstrap, GlobalConfig}; +use tokio::time::Duration; + +#[tokio_macros::main] +async fn main() -> Result<(), StratumTranscoderProxyError> { + let config = initialize()?; + + let config = StratumTranscoderProxyConfig::from(config); + let addr = config.transcoder_host_address; + let client = reqwest::Client::builder() + .connect_timeout(Duration::from_secs(5)) + .timeout(Duration::from_secs(10)) + .pool_max_idle_per_host(25) + .build() + .map_err(StratumTranscoderProxyError::ReqwestError)?; + let base_node_client = + grpc::base_node_client::BaseNodeClient::connect(format!("http://{}", config.grpc_base_node_address)).await?; + let wallet_client = + grpc::wallet_client::WalletClient::connect(format!("http://{}", config.grpc_console_wallet_address)).await?; + let miningcore_service = StratumTranscoderProxyService::new(config, client, base_node_client, wallet_client); + let service = make_service_fn(|_conn| future::ready(Result::<_, Infallible>::Ok(miningcore_service.clone()))); + + match Server::try_bind(&addr) { + Ok(builder) => { + println!("Listening on {}...", addr); + builder.serve(service).await?; + Ok(()) + }, + Err(err) => { + println!("Fatal: Cannot bind to '{}'.", addr); + println!("It may be part of a Port Exclusion Range. Please try to use another port for the"); + println!("'proxy_host_address' in 'config/config.toml' and for the applicable XMRig '[pools][url]' or"); + println!("[pools][self-select]' config setting that can be found in 'config/xmrig_config_***.json' or"); + println!("'/config.json'."); + println!(); + Err(err.into()) + }, + } +} + +/// Loads the configuration and sets up logging +fn initialize() -> Result { + // Parse and validate command-line arguments + let mut bootstrap = ConfigBootstrap::from_args(); + // Check and initialize configuration files + let application_type = ApplicationType::StratumTranscoder; + bootstrap.init_dirs(application_type)?; + + // Load and apply configuration file + let cfg = bootstrap.load_configuration()?; + + #[cfg(feature = "envlog")] + let _ = env_logger::try_init(); + // Initialise the logger + #[cfg(not(feature = "envlog"))] + bootstrap.initialize_logging()?; + + let cfg = GlobalConfig::convert_from(application_type, cfg)?; + Ok(cfg) +} diff --git a/applications/tari_stratum_transcoder/src/proxy.rs b/applications/tari_stratum_transcoder/src/proxy.rs new file mode 100644 index 0000000000..9bdc3b3541 --- /dev/null +++ b/applications/tari_stratum_transcoder/src/proxy.rs @@ -0,0 +1,616 @@ +// Copyright 2020, The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::{ + common::{ + json_rpc, + json_rpc::{standard_error_response, try_into_json_block_header_response}, + mining, + proxy, + }, + error::StratumTranscoderProxyError, +}; +use bytes::Bytes; +use hyper::{service::Service, Body, Method, Request, Response, StatusCode}; +use json::json; +use jsonrpc::error::StandardError; +use serde_json as json; +use std::{ + convert::TryFrom, + future::Future, + net::SocketAddr, + pin::Pin, + task::{Context, Poll}, + time::Instant, +}; +use tari_app_grpc::{tari_rpc as grpc, tari_rpc::GetCoinbaseRequest}; +use tari_common::{configuration::Network, GlobalConfig}; +use tari_core::blocks::{Block, NewBlockTemplate}; +use tari_utilities::{hex::Hex, message_format::MessageFormat}; +use tracing::{debug, error}; + +const LOG_TARGET: &str = "tari_stratum_proxy::proxy"; + +#[derive(Debug, Clone)] +pub struct StratumTranscoderProxyConfig { + pub network: Network, + pub grpc_base_node_address: SocketAddr, + pub grpc_console_wallet_address: SocketAddr, + pub transcoder_host_address: SocketAddr, +} + +impl From for StratumTranscoderProxyConfig { + fn from(config: GlobalConfig) -> Self { + Self { + network: config.network, + grpc_base_node_address: config.grpc_base_node_address, + grpc_console_wallet_address: config.grpc_console_wallet_address, + transcoder_host_address: config.transcoder_host_address, + } + } +} + +#[derive(Debug, Clone)] +pub struct StratumTranscoderProxyService { + inner: InnerService, +} + +impl StratumTranscoderProxyService { + pub fn new( + config: StratumTranscoderProxyConfig, + http_client: reqwest::Client, + base_node_client: grpc::base_node_client::BaseNodeClient, + wallet_client: grpc::wallet_client::WalletClient, + ) -> Self { + Self { + inner: InnerService { + config, + http_client, + base_node_client, + wallet_client, + }, + } + } +} + +#[allow(clippy::type_complexity)] +impl Service> for StratumTranscoderProxyService { + type Error = hyper::Error; + type Future = Pin> + Send>>; + type Response = Response; + + fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(())) + } + + fn call(&mut self, req: Request) -> Self::Future { + let inner = self.inner.clone(); + let future = async move { + match inner.handle(req).await { + Ok(resp) => Ok(resp), + Err(err) => { + error!(target: LOG_TARGET, "Error handling request: {}", err); + + Ok(proxy::json_response( + StatusCode::INTERNAL_SERVER_ERROR, + &json_rpc::standard_error_response( + None, + StandardError::InternalError, + Some(json!({"details": err.to_string()})), + ), + ) + .expect("unexpected failure")) + }, + } + }; + + Box::pin(future) + } +} + +#[derive(Debug, Clone)] +struct InnerService { + config: StratumTranscoderProxyConfig, + http_client: reqwest::Client, + base_node_client: grpc::base_node_client::BaseNodeClient, + wallet_client: grpc::wallet_client::WalletClient, +} + +impl InnerService { + async fn handle_get_info(&self) -> Result, StratumTranscoderProxyError> { + let mut client = self.base_node_client.clone(); + let tip_info = client.get_tip_info(grpc::Empty {}).await?.into_inner(); + let consensus_constants = client.get_constants(grpc::Empty {}).await?.into_inner(); + let sync_info = client.get_sync_info(grpc::Empty {}).await?.into_inner(); + let info_json; + match tip_info.metadata { + Some(metadata) => { + info_json = json!({ + "jsonrpc": "2.0", + "result": { + "blockchain_version": consensus_constants.blockchain_version, + "min_diff": consensus_constants.min_blake_pow_difficulty, + "lock_height": consensus_constants.coinbase_lock_height, + "max_block_interval": consensus_constants.difficulty_max_block_interval, + "max_weight": consensus_constants.max_block_transaction_weight, + "height_of_longest_chain": metadata.height_of_longest_chain, + "best_block": metadata.best_block.to_hex(), + "local_height": sync_info.local_height, + "tip_height": sync_info.tip_height, + "initial_sync_achieved": tip_info.initial_sync_achieved, + } + }) + }, + None => { + return Err(StratumTranscoderProxyError::UnexpectedTariBaseNodeResponse( + "Base node GRPC returned empty metadata when calling tip_info".into(), + )) + }, + } + proxy::json_response(StatusCode::OK, &info_json) + } + + async fn handle_get_block_template( + &self, + request: Request, + ) -> Result, StratumTranscoderProxyError> { + let request = request.body(); + let request_id = request["id"].as_i64(); + let mut grpc_client = self.base_node_client.clone(); + + let grpc::NewBlockTemplateResponse { + miner_data, + new_block_template, + initial_sync_achieved: _, + } = grpc_client + .get_new_block_template(grpc::NewBlockTemplateRequest { + algo: Some(grpc::PowAlgo { + pow_algo: grpc::pow_algo::PowAlgos::Sha3.into(), + }), + max_weight: 0, + }) + .await + .map_err(|status| StratumTranscoderProxyError::GrpcRequestError { + status, + details: "failed to get new block template".to_string(), + })? + .into_inner(); + + let miner_data = miner_data.ok_or(StratumTranscoderProxyError::GrpcResponseMissingField("miner_data"))?; + let new_block_template = new_block_template.ok_or(StratumTranscoderProxyError::GrpcResponseMissingField( + "new_block_template", + ))?; + + let block_reward = miner_data.reward; + let total_fees = miner_data.total_fees; + let tari_difficulty = miner_data.target_difficulty; + + let template_block = NewBlockTemplate::try_from(new_block_template) + .map_err(|e| StratumTranscoderProxyError::MissingDataError(format!("GRPC Conversion Error: {}", e)))?; + let tari_height = template_block.header.height; + + let mut grpc_wallet_client = self.wallet_client.clone(); + let coinbase_response = grpc_wallet_client + .get_coinbase(GetCoinbaseRequest { + reward: block_reward, + fee: total_fees, + height: tari_height, + }) + .await + .map_err(|status| StratumTranscoderProxyError::GrpcRequestError { + status, + details: "failed to get new block template".to_string(), + })?; + let coinbase_transaction = coinbase_response.into_inner().transaction; + + let coinbased_block = mining::add_coinbase(coinbase_transaction, template_block)?; + + let block = grpc_client + .get_new_block(coinbased_block) + .await + .map_err(|status| StratumTranscoderProxyError::GrpcRequestError { + status, + details: "failed to get new block".to_string(), + })? + .into_inner(); + + let tari_block = Block::try_from( + block + .block + .ok_or_else(|| StratumTranscoderProxyError::MissingDataError("Tari block".to_string()))?, + ) + .map_err(StratumTranscoderProxyError::MissingDataError)?; + + let tari_header = tari_block.header.clone(); + let tari_prev_hash = tari_header.prev_hash.to_hex(); + + // todo remove unwraps + let header_hex = hex::encode(tari_header.to_json().unwrap()); + let block_hex = hex::encode(tari_block.to_json().unwrap()); + + let template_json = json!({ + "id": request_id.unwrap_or(-1), + "jsonrpc": "2.0", + "result": { + "blockheader_blob": header_hex, + "blocktemplate_blob": block_hex, + "difficulty" : tari_difficulty, + "height" : tari_height, + "expected_reward": block_reward+total_fees, + "prev_hash": tari_prev_hash, + } + }); + + proxy::json_response(StatusCode::OK, &template_json) + } + + async fn handle_submit_block( + &self, + request: Request, + ) -> Result, StratumTranscoderProxyError> { + let request = request.body(); + let params = match request["params"].as_array() { + Some(v) => v, + None => { + return proxy::json_response( + StatusCode::OK, + &json_rpc::error_response( + request["id"].as_i64(), + 1, + "`params` field is empty or an invalid type for submit block request. Expected an array.", + None, + ), + ) + }, + }; + let mut json_response: Result, StratumTranscoderProxyError> = proxy::json_response( + StatusCode::OK, + &json_rpc::error_response(request["id"].as_i64(), 2, "No block", None), + ); + for param in params.iter().filter_map(|p| p.as_str()) { + let block_hex = hex::decode(param); + match block_hex { + Ok(block_hex) => { + let block: Result = + serde_json::from_str(&String::from_utf8_lossy(&block_hex).to_string()); + match block { + Ok(block) => { + let mut client = self.base_node_client.clone(); + let grpc_block: tari_app_grpc::tari_rpc::Block = block.into(); + match client.submit_block(grpc_block).await { + Ok(_) => { + json_response = proxy::json_response( + StatusCode::OK, + &json_rpc::success_response( + request["id"].as_i64(), + json!({ "status": "OK", "untrusted": false }), + ), + ) + }, + Err(_) => { + json_response = proxy::json_response( + StatusCode::OK, + &json_rpc::error_response( + request["id"].as_i64(), + 3, + "Block not accepted", + None, + ), + ) + }, + } + }, + Err(_) => { + json_response = proxy::json_response( + StatusCode::OK, + &json_rpc::error_response(request["id"].as_i64(), 4, "Invalid Block", None), + ) + }, + } + }, + Err(_) => { + json_response = proxy::json_response( + StatusCode::OK, + &json_rpc::error_response(request["id"].as_i64(), 5, "Invalid Hex", None), + ) + }, + } + } + json_response + } + + async fn handle_get_block_header_by_height( + &self, + request: Request, + ) -> Result, StratumTranscoderProxyError> { + let request = request.into_body(); + let mut height = request["params"]["height"].as_u64().unwrap_or(0); + // bug for height = 0 (genesis block), streams indefinitely + if height == 0 { + height = 1; + } + let mut client = self.base_node_client.clone(); + let mut resp = client + .get_blocks(grpc::GetBlocksRequest { heights: vec![height] }) + .await? + .into_inner(); + let message = resp.message().await?; + resp.trailers().await?; // drain stream + // todo: remove unwraps + let resp = client + .get_header_by_hash(grpc::GetHeaderByHashRequest { + hash: message.unwrap().block.unwrap().header.unwrap().hash, + }) + .await; + match resp { + Ok(resp) => { + let json_response = try_into_json_block_header_response(resp.into_inner(), request["id"].as_i64())?; + proxy::json_response(StatusCode::OK, &json_response) + }, + Err(err) if err.code() == tonic::Code::NotFound => proxy::json_response( + StatusCode::OK, + &json_rpc::error_response(request["id"].as_i64(), 5, "Not found", None), + ), + Err(err) => Err(StratumTranscoderProxyError::GrpcRequestError { + status: err, + details: "failed to get header by height".to_string(), + }), + } + } + + async fn handle_get_block_header_by_hash( + &self, + request: Request, + ) -> Result, StratumTranscoderProxyError> { + let request = request.into_body(); + let hash = request["hash"] + .as_str() + .ok_or("hash parameter is not a string") + .and_then(|hash| hex::decode(hash).map_err(|_| "hash parameter is not a valid hex value")); + let hash = match hash { + Ok(hash) => hash, + Err(err) => { + return proxy::json_response( + StatusCode::OK, + &json_rpc::error_response(request["id"].as_i64(), -1, err, None), + ) + }, + }; + + let mut client = self.base_node_client.clone(); + let resp = client + .get_header_by_hash(grpc::GetHeaderByHashRequest { hash: hash.clone() }) + .await; + match resp { + Ok(resp) => { + let json_response = try_into_json_block_header_response(resp.into_inner(), request["id"].as_i64())?; + + debug!( + target: LOG_TARGET, + "[get_header_by_hash] Found tari block header with hash `{:?}`", + hash.clone() + ); + + proxy::json_response(StatusCode::OK, &json_response) + }, + Err(err) if err.code() == tonic::Code::NotFound => { + debug!( + target: LOG_TARGET, + "[get_header_by_hash] No tari block header found with hash `{:?}`", hash + ); + proxy::json_response( + StatusCode::OK, + &json_rpc::error_response(request["id"].as_i64(), 5, "Not found", None), + ) + }, + Err(err) => Err(StratumTranscoderProxyError::GrpcRequestError { + status: err, + details: "failed to get header by hash".to_string(), + }), + } + } + + async fn handle_get_last_block_header( + &self, + request: Request, + ) -> Result, StratumTranscoderProxyError> { + let request = request.into_body(); + let mut client = self.base_node_client.clone(); + let tip_info = client.get_tip_info(grpc::Empty {}).await?; + let tip_info = tip_info.into_inner(); + let chain_metadata = tip_info.metadata.ok_or_else(|| { + StratumTranscoderProxyError::UnexpectedTariBaseNodeResponse( + "get_tip_info returned no chain metadata".into(), + ) + })?; + + let tip_header = client + .get_header_by_hash(grpc::GetHeaderByHashRequest { + hash: chain_metadata.best_block, + }) + .await?; + + let tip_header = tip_header.into_inner(); + let json_response = try_into_json_block_header_response(tip_header, request["id"].as_i64())?; + proxy::json_response(StatusCode::OK, &json_response) + } + + async fn handle_get_balance( + &self, + request: Request, + ) -> Result, StratumTranscoderProxyError> { + let request = request.body(); + let request_id = request["id"].as_i64(); + let mut client = self.wallet_client.clone(); + let balances = client.get_balance(grpc::GetBalanceRequest {}).await?.into_inner(); + + let json_response = json!({ + "id": request_id.unwrap_or(-1), + "jsonrpc": "2.0", + "result": { + "available_balance": balances.available_balance, + "pending_incoming_balance": balances.pending_incoming_balance, + "pending_outgoing_balance": balances.pending_outgoing_balance, + } + }); + proxy::json_response(StatusCode::OK, &json_response) + } + + async fn handle_transfer( + &self, + request: Request, + ) -> Result, StratumTranscoderProxyError> { + let request = request.body(); + let recipients = match request["params"]["recipients"].as_array() { + Some(v) => v, + None => { + return proxy::json_response( + StatusCode::OK, + &json_rpc::error_response( + request["id"].as_i64(), + 1, + "`recipients` field is empty or an invalid type for transfer request. Expected an array.", + None, + ), + ) + }, + }; + + let mut grpc_payments = Vec::new(); + + for recipient in recipients.iter() { + grpc_payments.push(grpc::PaymentRecipient { + address: recipient["address"].as_str().unwrap().to_string(), + amount: recipient["amount"].as_u64().unwrap(), + fee_per_gram: recipient["fee_per_gram"].as_u64().unwrap(), + message: recipient["message"].as_str().unwrap().to_string(), + payment_type: 1, + }); + } + + let mut client = self.wallet_client.clone(); + let transfer_results = client + .transfer(grpc::TransferRequest { + recipients: grpc_payments, + }) + .await? + .into_inner(); + let transaction_results = &transfer_results.results; + + let mut results = Vec::new(); + for transaction_result in transaction_results.iter() { + let result = json!({ + "address": transaction_result.address, + "transaction_id": transaction_result.transaction_id, + "is_success": transaction_result.is_success, + "failure_message": transaction_result.failure_message, + }); + results.push(result.as_object().unwrap().clone()); + } + let json_response = json!({ + "jsonrpc": "2.0", + "result": {"transaction_results" : results}, + }); + proxy::json_response(StatusCode::OK, &json_response) + } + + async fn get_proxy_response(&self, request: Request) -> Result, StratumTranscoderProxyError> { + let mut proxy_resp = Response::new(standard_error_response(Some(-1), StandardError::MethodNotFound, None)); + match request.method().clone() { + Method::GET => match request.uri().path() { + "/get_info" | "/getinfo" => self.handle_get_info().await, + _ => Ok(proxy::into_body_from_response(proxy_resp)), + }, + Method::POST => { + let json = json::from_slice::(request.body())?; + let request = request.map(move |_| json); + match request.body()["method"].as_str().unwrap_or_default() { + "get_info" | "getinfo" => self.handle_get_info().await, + "submitblock" | "submit_block" => self.handle_submit_block(request).await, + "getblocktemplate" | "get_block_template" => self.handle_get_block_template(request).await, + "getblockheaderbyhash" | "get_block_header_by_hash" => { + self.handle_get_block_header_by_hash(request).await + }, + "getblockheaderbyheight" | "get_block_header_by_height" => { + self.handle_get_block_header_by_height(request).await + }, + "getlastblockheader" | "get_last_block_header" => self.handle_get_last_block_header(request).await, + "transfer" => self.handle_transfer(request).await, + "getbalance" | "get_balance" => self.handle_get_balance(request).await, + _ => { + let request = request.body(); + proxy_resp = Response::new(standard_error_response( + request["id"].as_i64(), + StandardError::MethodNotFound, + None, + )); + Ok(proxy::into_body_from_response(proxy_resp)) + }, + } + }, + // Simply return the response "as is" + _ => Ok(proxy::into_body_from_response(proxy_resp)), + } + } + + async fn handle(self, mut request: Request) -> Result, StratumTranscoderProxyError> { + let start = Instant::now(); + let bytes = proxy::read_body_until_end(request.body_mut()).await?; + let request = request.map(|_| bytes.freeze()); + let method_name; + match *request.method() { + Method::GET => { + let mut chars = request.uri().path().chars(); + chars.next(); + method_name = chars.as_str().to_string(); + }, + Method::POST => { + let json = json::from_slice::(request.body()).unwrap_or_default(); + method_name = str::replace(json["method"].as_str().unwrap_or_default(), "\"", ""); + }, + _ => { + method_name = "unsupported".to_string(); + }, + } + + debug!( + target: LOG_TARGET, + "request: {} ({})", + String::from_utf8_lossy(&request.body().clone()[..]), + request + .headers() + .iter() + .map(|(k, v)| format!("{}={}", k, String::from_utf8_lossy(v.as_ref()))) + .collect::>() + .join(","), + ); + + let response = self.get_proxy_response(request).await?; + println!( + "Method: {}, Proxy Status: {}, Response Time: {}ms", + method_name, + response.status(), + start.elapsed().as_millis() + ); + Ok(response) + } +} diff --git a/base_layer/tari_stratum_ffi/Cargo.toml b/base_layer/tari_stratum_ffi/Cargo.toml new file mode 100644 index 0000000000..6598df9f06 --- /dev/null +++ b/base_layer/tari_stratum_ffi/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "tari_stratum_ffi" +authors = ["The Tari Development Community"] +description = "Tari cryptocurrency miningcore C FFI bindings" +license = "BSD-3-Clause" +version = "0.0.1" +edition = "2018" + +[dependencies] +tari_comms = { version = "^0.9", path = "../../comms" } +tari_crypto = "^0.11.1" +tari_common = { path = "../../common" } +tari_app_grpc = { path = "../../applications/tari_app_grpc" } +tari_core = { path = "../../base_layer/core", default-features = false, features = ["transactions"]} +tari_utilities = "^0.3" +libc = "0.2.65" +thiserror = "1.0.20" +hex = "0.4.2" +serde = { version="1.0.106", features = ["derive"] } +serde_json = "1.0.57" + +[lib] +crate-type = ["staticlib","cdylib"] diff --git a/base_layer/tari_stratum_ffi/src/error.rs b/base_layer/tari_stratum_ffi/src/error.rs new file mode 100644 index 0000000000..00d0a39bfe --- /dev/null +++ b/base_layer/tari_stratum_ffi/src/error.rs @@ -0,0 +1,87 @@ +// Copyright 2021. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +use tari_crypto::tari_utilities::hex::HexError; +use thiserror::Error; + +#[derive(Debug, Error, PartialEq)] +pub enum InterfaceError { + #[error("An error has occurred due to one of the parameters being null: `{0}`")] + NullError(String), + #[error("An error has occurred due to conversion failing for: `{0}`")] + ConversionError(String), + #[error("An error has occurred due to validation failing for: `{0}`")] + InvalidHashError(String), + #[error("An error has occurred due to difficulty being too low for share: `{0}`")] + LowDifficultyError(String), +} + +/// This struct is meant to hold an error for use by Miningcore. The error has an integer code and string +/// message +#[derive(Debug, Clone)] +pub struct StratumTranscoderError { + pub code: i32, + pub message: String, +} + +impl From for StratumTranscoderError { + fn from(v: InterfaceError) -> Self { + match v { + InterfaceError::NullError(_) => Self { + code: 1, + message: format!("{:?}", v), + }, + InterfaceError::ConversionError(_) => Self { + code: 2, + message: format!("{:?}", v), + }, + InterfaceError::InvalidHashError(_) => Self { + code: 3, + message: format!("{:?}", v), + }, + InterfaceError::LowDifficultyError(_) => Self { + code: 4, + message: format!("{:?}", v), + }, + } + } +} + +/// This implementation maps the internal HexError to a set of StratumTranscoderErrors. +/// The mapping is explicitly managed here. +impl From for StratumTranscoderError { + fn from(h: HexError) -> Self { + match h { + HexError::HexConversionError => Self { + code: 404, + message: format!("{:?}", h), + }, + HexError::LengthError => Self { + code: 501, + message: format!("{:?}", h), + }, + HexError::InvalidCharacter(_) => Self { + code: 503, + message: format!("{:?}", h), + }, + } + } +} diff --git a/base_layer/tari_stratum_ffi/src/lib.rs b/base_layer/tari_stratum_ffi/src/lib.rs new file mode 100644 index 0000000000..33e523f39d --- /dev/null +++ b/base_layer/tari_stratum_ffi/src/lib.rs @@ -0,0 +1,409 @@ +// Copyright 2021. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +#![cfg_attr(not(debug_assertions), deny(unused_variables))] +#![cfg_attr(not(debug_assertions), deny(unused_imports))] +#![cfg_attr(not(debug_assertions), deny(dead_code))] +#![cfg_attr(not(debug_assertions), deny(unused_extern_crates))] +#![deny(unused_must_use)] +#![deny(unreachable_patterns)] +#![deny(unknown_lints)] + +mod error; + +use crate::error::{InterfaceError, StratumTranscoderError}; +use core::ptr; +use libc::{c_char, c_int, c_ulonglong}; +use std::ffi::CString; +use tari_core::{ + blocks::Block, + crypto::tari_utilities::{message_format::MessageFormat, Hashable}, + proof_of_work::{sha3_difficulty, Difficulty}, +}; +use tari_crypto::tari_utilities::hex::Hex; +pub type TariPublicKey = tari_comms::types::CommsPublicKey; + +/// Validates a hex string is convertible into a TariPublicKey +/// +/// ## Arguments +/// `hex` - The hex formatted cstring to be validated +/// +/// ## Returns +/// `bool` - Returns true/false +/// `error_out` - Error code returned, 0 means no error +/// +/// # Safety +/// None +#[no_mangle] +pub unsafe extern "C" fn public_key_hex_validate(hex: *const c_char, error_out: *mut c_int) -> bool { + let mut error = 0; + ptr::swap(error_out, &mut error as *mut c_int); + let native; + + if hex.is_null() { + error = StratumTranscoderError::from(InterfaceError::NullError("hex".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return false; + } else { + native = CString::from_raw(hex as *mut i8).to_str().unwrap().to_owned(); + } + let pk = TariPublicKey::from_hex(&native); + match pk { + Ok(_pk) => true, + Err(e) => { + error = StratumTranscoderError::from(e).code; + ptr::swap(error_out, &mut error as *mut c_int); + false + }, + } +} + +/// Injects a nonce into a blocktemplate +/// +/// ## Arguments +/// `hex` - The hex formatted cstring +/// `nonce` - The nonce to be injected +/// +/// ## Returns +/// `c_char` - The updated hex formatted cstring or null on error +/// `error_out` - Error code returned, 0 means no error +/// +/// # Safety +/// None +#[no_mangle] +pub unsafe extern "C" fn inject_nonce(hex: *const c_char, nonce: c_ulonglong, error_out: *mut c_int) -> *const c_char { + let mut error = 0; + ptr::swap(error_out, &mut error as *mut c_int); + let native; + + if hex.is_null() { + error = StratumTranscoderError::from(InterfaceError::NullError("hex".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + ptr::null() + } else { + native = CString::from_raw(hex as *mut i8).to_str().unwrap().to_owned(); + let block_hex = hex::decode(native); + match block_hex { + Ok(block_hex) => { + let block: Result = + serde_json::from_str(&String::from_utf8_lossy(&block_hex).to_string()); + match block { + Ok(mut block) => { + block.header.nonce = nonce; + let block_json = block.to_json().unwrap(); + let block_hex = hex::encode(block_json); + let result = CString::new(block_hex).unwrap(); + CString::into_raw(result) + }, + Err(_) => { + error = StratumTranscoderError::from(InterfaceError::ConversionError("block".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + ptr::null() + }, + } + }, + Err(_) => { + error = StratumTranscoderError::from(InterfaceError::ConversionError("hex".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + ptr::null() + }, + } + } +} + +/// Returns the difficulty of a share +/// +/// ## Arguments +/// `hex` - The hex formatted cstring to be validated +/// +/// ## Returns +/// `c_ulonglong` - Difficulty, 0 on error +/// `error_out` - Error code returned, 0 means no error +/// +/// # Safety +/// None +#[no_mangle] +pub unsafe extern "C" fn share_difficulty(hex: *const c_char, error_out: *mut c_int) -> c_ulonglong { + let mut error = 0; + ptr::swap(error_out, &mut error as *mut c_int); + let block_hex_string; + + if hex.is_null() { + error = StratumTranscoderError::from(InterfaceError::NullError("hex".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return 0; + } else { + block_hex_string = CString::from_raw(hex as *mut i8).to_str().unwrap().to_owned(); + } + + let block_hex = hex::decode(block_hex_string); + match block_hex { + Ok(block_hex) => { + let block: Result = + serde_json::from_str(&String::from_utf8_lossy(&block_hex).to_string()); + match block { + Ok(block) => { + let difficulty = sha3_difficulty(&block.header); + difficulty.as_u64() + }, + Err(_) => { + error = StratumTranscoderError::from(InterfaceError::ConversionError("block".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + 0 + }, + } + }, + Err(_) => { + error = StratumTranscoderError::from(InterfaceError::ConversionError("hex".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + 0 + }, + } +} + +/// Validates a share submission +/// +/// ## Arguments +/// `hex` - The hex representation of the share to be validated +/// `hash` - The hash of the share to be validated +/// `nonce` - The nonce for the share to be validated +/// `stratum_difficulty` - The stratum difficulty to be checked against (meeting this means that the share is valid for +/// payout) `template_difficulty` - The difficulty to be checked against (meeting this means the share is also a block +/// to be submitted to the chain) +/// +/// ## Returns +/// `c_uint` - Returns one of the following: +/// 0: Valid Block +/// 1: Valid Share +/// 2: Invalid Share +/// `error_out` - Error code returned, 0 means no error +/// +/// # Safety +/// None +#[no_mangle] +pub unsafe extern "C" fn share_validate( + hex: *const c_char, + hash: *const c_char, + stratum_difficulty: c_ulonglong, + template_difficulty: c_ulonglong, + error_out: *mut c_int, +) -> c_int { + let mut error = 0; + ptr::swap(error_out, &mut error as *mut c_int); + let block_hex_string; + let block_hash_string; + + if hex.is_null() { + error = StratumTranscoderError::from(InterfaceError::NullError("hex".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return 2; + } else { + block_hex_string = CString::from_raw(hex as *mut i8).to_str().unwrap().to_owned(); + } + + if hash.is_null() { + error = StratumTranscoderError::from(InterfaceError::NullError("hash".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return 2; + } else { + block_hash_string = CString::from_raw(hash as *mut i8).to_str().unwrap().to_owned(); + } + + let block_hex = hex::decode(block_hex_string); + match block_hex { + Ok(block_hex) => { + let block: Result = + serde_json::from_str(&String::from_utf8_lossy(&block_hex).to_string()); + match block { + Ok(block) => { + if block.header.hash().to_hex() == block_hash_string { + // Hash submitted by miner is the same hash produced for the nonce submitted by miner + let mut result = 2; + let difficulty = sha3_difficulty(&block.header); + if difficulty >= Difficulty::from(template_difficulty) { + // Valid block + result = 0; + } else if difficulty >= Difficulty::from(stratum_difficulty) { + // Valid share + result = 1; + } else { + // Difficulty not reached + error = StratumTranscoderError::from(InterfaceError::LowDifficultyError(block_hash_string)) + .code; + ptr::swap(error_out, &mut error as *mut c_int); + } + result + } else { + error = StratumTranscoderError::from(InterfaceError::InvalidHashError(block_hash_string)).code; + ptr::swap(error_out, &mut error as *mut c_int); + 2 + } + }, + Err(_) => { + error = StratumTranscoderError::from(InterfaceError::ConversionError("block".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + 2 + }, + } + }, + Err(_) => { + error = StratumTranscoderError::from(InterfaceError::ConversionError("hex".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + 2 + }, + } +} + +#[cfg(test)] +mod tests { + use crate::{inject_nonce, public_key_hex_validate, share_difficulty, share_validate}; + use libc::{c_char, c_int}; + use std::{ffi::CString, str}; + + const BLOCK_HEX: &str = "7b22686561646572223a7b2276657273696f6e223a312c22686569676874223a343333382c22707265765f68617368223a2237663665626130376432373964366464316566656263376564346431386163396436666564663366613536303131363835636361326465336562656232633266222c2274696d657374616d70223a313632363138353739372c226f75747075745f6d72223a2237336230306466393130353263383831343061393765613831343138396239356335313634303662633434323238666562393262326563333238386534366564222c227769746e6573735f6d72223a2236326665643734633863633531633032363338356638626434663330326638306263353034393635656363363930393033646565623765613836303331376531222c226f75747075745f6d6d725f73697a65223a3130303439382c226b65726e656c5f6d72223a2263653233656430623561663938323236653936353533636631616539646538346230333432363665316164366435623231383531356431306663613930393132222c226b65726e656c5f6d6d725f73697a65223a32303438332c22696e7075745f6d72223a2232363134366135343335656631356538636637646333333534636237323638313337653862653231313739346539336430343535313537366336353631353635222c22746f74616c5f6b65726e656c5f6f6666736574223a2230303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030222c22746f74616c5f7363726970745f6f6666736574223a2230303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030222c226e6f6e6365223a302c22706f77223a7b22706f775f616c676f223a2253686133222c22706f775f64617461223a5b5d7d7d2c22626f6479223a7b22736f72746564223a66616c73652c22696e70757473223a5b5d2c226f757470757473223a5b7b226665617475726573223a7b22666c616773223a7b2262697473223a317d2c226d61747572697479223a343334347d2c22636f6d6d69746d656e74223a2264656138316332663165353461336465323035363764656665623335613730643530666165343730313438626532666335316134303330666335653764373036222c2270726f6f66223a22336339643164353032653165313637656132366336383538373931666138653836373833303062616334656264616635386261333435376566333735666432393138663566393034306638623534616363313264373031383463336234333362643236663161316234356335313739653233366535633434636665613336333362323764633031356663643266306139333861393864326633363164623466386231656335333466306262636135626661373731663838373430313764323132356331373839333437316235633462313665626262346165616137313434636666653332326361613438613436326436626462343661373534613132616336333532333365656530353463366337343766623132353436636664646561323562346365336230643364333332653763396363376137646365646334383662306533313866333132376233313735306336346634653533666339393239366666383365306332633232636235333464396262613533316562393364626433613034386635626431366563643239613939636630623436386133616332666233393439363735303964623033316332666363616636613831653030303766353330356563623730613638653537393166356462396237376162313634393434626430396665356439393564666337633933663865316435306639643362616639363330653164303737643565323237356436343834323833656461313163373139393330343637363037643761306631633561613139386463343331633736643732653436303736663030313738633466363535313432366161653437633263656263386165373932393966313732303163626261313837396565616238346637636430303737353639643864323933306437623464363261303337313765613731376632386363616366633438636135643665383037313239306234386132343736616430663562623039633762303930376231616533623133653262653136643531613465303832386364393366353734336534323939303835613936663032356338656633383436623430633634386563633733666431643065633535376166313632376362626538626639643430333232303833336138353633343337316334666639663636363663313239303436616263323939633633643064313532626437306464303336306265396339383961396133643930653639613031366164633064663937373664323661343434303237633033623263303639643438613031383762313365643236386430366530313961363733663163643636613436623838333335663663313562363566663232383737346334383536653564323466336465363633633636333739663639376162323039323537326265663434346363306361366433396562383732616538363765373536356131626539613731396231613130613833363937656133333666333438613033373864613365373036303534356434323233396138313438303632303564306466376138663961613438633834383362353432663862303564346330626235333039363534373032306137663366316362333137633733346532373866303232396234396263333635666539373935393730613662666163326462626537633337616436666337373266323038613463333637653634333030663963623136363332643034346333626436386237613939383830663533336630346465613030633761343637303035613261316432313766343261323935623264393565646664393632346463636535343432653763393039663661333834363036346466643765373538303066222c22736372697074223a223733222c2273656e6465725f6f66667365745f7075626c69635f6b6579223a2264616133376465323133323038636462323237623431666435313830643530306130643138356462346565353461646666643033386436346233386136353764222c226d657461646174615f7369676e6174757265223a7b227075626c69635f6e6f6e6365223a2261306565623636383862613363313331616565343538363435396662336533323463303537316535656639643937316462303461313331643061636435343331222c2275223a2262383633666563386336396361313136393166383363656165633531653839393833613235363334666563306438383035326232363066383862313835353032222c2276223a2264396535323238346662393536666665343837636238376538353666373837343939356366616162393034373264376432616537616539623431373537393032227d7d5d2c226b65726e656c73223a5b7b226665617475726573223a7b2262697473223a317d2c22666565223a302c226c6f636b5f686569676874223a302c22657863657373223a2263366263386263643162623836353964666664356537363634653263363265646333383639333566396230633033333130353265383836623235623264373465222c226578636573735f736967223a7b227075626c69635f6e6f6e6365223a2236326264336539663631643362633031323738386130373134633461666134353332383136663562616664613138303465623963643333616536356538323465222c227369676e6174757265223a2234643662323666383433623837623737393734343233613764656563303365663933653930326563633131393734303837646264643234333362643936363061227d7d5d7d7d"; + const HASH_HEX: &str = "3a9ea717ca7b2598d900e2ef98c270ac98ce993bce8a9e058929967ba37fbc6b"; + const NONCE: u64 = 15810795621223647638; + + #[test] + fn check_difficulty() { + // Difficulty 20025 + unsafe { + let mut error = -1; + let error_ptr = &mut error as *mut c_int; + let block_hex = CString::new(BLOCK_HEX).unwrap(); + let block_hex_ptr: *const c_char = CString::into_raw(block_hex) as *const c_char; + let block_hex_ptr2 = inject_nonce(block_hex_ptr, NONCE, error_ptr); + let result = share_difficulty(block_hex_ptr2, error_ptr); + assert_eq!(result, 20025); + } + } + + #[test] + fn check_invalid_share() { + // Difficulty 20025 + unsafe { + let mut error = -1; + let error_ptr = &mut error as *mut c_int; + let block_hex = CString::new(BLOCK_HEX).unwrap(); + let hash_hex = CString::new(HASH_HEX).unwrap(); + let block_hex_ptr: *const c_char = CString::into_raw(block_hex) as *const c_char; + let hash_hex_ptr: *const c_char = CString::into_raw(hash_hex) as *const c_char; + let template_difficulty = 30000; + let stratum_difficulty = 22200; + let block_hex_ptr2 = inject_nonce(block_hex_ptr, NONCE, error_ptr); + let result = share_validate( + block_hex_ptr2, + hash_hex_ptr, + stratum_difficulty, + template_difficulty, + error_ptr, + ); + assert_eq!(result, 2); + assert_eq!(error, 4); + } + } + + #[test] + fn check_valid_share() { + // Difficulty 20025 + unsafe { + let mut error = -1; + let error_ptr = &mut error as *mut c_int; + let block_hex = CString::new(BLOCK_HEX).unwrap(); + let hash_hex = CString::new(HASH_HEX).unwrap(); + let block_hex_ptr: *const c_char = CString::into_raw(block_hex) as *const c_char; + let hash_hex_ptr: *const c_char = CString::into_raw(hash_hex) as *const c_char; + let template_difficulty = 30000; + let stratum_difficulty = 20000; + let block_hex_ptr2 = inject_nonce(block_hex_ptr, NONCE, error_ptr); + let result = share_validate( + block_hex_ptr2, + hash_hex_ptr, + stratum_difficulty, + template_difficulty, + error_ptr, + ); + assert_eq!(result, 1); + assert_eq!(error, 0); + } + } + + #[test] + fn check_valid_block() { + // Difficulty 20025 + unsafe { + let mut error = -1; + let error_ptr = &mut error as *mut c_int; + let block_hex = CString::new(BLOCK_HEX).unwrap(); + let hash_hex = CString::new(HASH_HEX).unwrap(); + let block_hex_ptr: *const c_char = CString::into_raw(block_hex) as *const c_char; + let hash_hex_ptr: *const c_char = CString::into_raw(hash_hex) as *const c_char; + let template_difficulty = 20000; + let stratum_difficulty = 15000; + let block_hex_ptr2 = inject_nonce(block_hex_ptr, NONCE, error_ptr); + let result = share_validate( + block_hex_ptr2, + hash_hex_ptr, + stratum_difficulty, + template_difficulty, + error_ptr, + ); + assert_eq!(result, 0); + assert_eq!(error, 0); + } + } + + #[test] + fn check_valid_address() { + unsafe { + let mut error = -1; + let error_ptr = &mut error as *mut c_int; + let test_pk = CString::new("5ce83bf62521629ca185098ac24c7b02b184c2e0a2b01455f3a5957d5df94126").unwrap(); + let test_pk_ptr: *const c_char = CString::into_raw(test_pk) as *const c_char; + let success = public_key_hex_validate(test_pk_ptr, error_ptr); + assert_eq!(error, 0); + assert!(success); + } + } + + #[test] + fn check_invalid_address() { + unsafe { + let mut error = -1; + let error_ptr = &mut error as *mut c_int; + let test_pk = CString::new("5fe83bf62521629ca185098ac24c7b02b184c2e0a2b01455f3a5957d5df94126").unwrap(); + let test_pk_ptr: *const c_char = CString::into_raw(test_pk) as *const c_char; + let success = public_key_hex_validate(test_pk_ptr, error_ptr); + assert!(!success); + assert_ne!(error, 0); + } + unsafe { + let mut error = -1; + let error_ptr = &mut error as *mut c_int; + let test_pk = CString::new("5fe83bf62521629ca185098ac24c7b02b184c2e0a2b01455f3a5957d5d").unwrap(); + let test_pk_ptr: *const c_char = CString::into_raw(test_pk) as *const c_char; + let success = public_key_hex_validate(test_pk_ptr, error_ptr); + assert!(!success); + assert_ne!(error, 0); + } + } +} diff --git a/common/config/presets/tari_config_example.toml b/common/config/presets/tari_config_example.toml index f16ce8ffcb..d1f32cbf8b 100644 --- a/common/config/presets/tari_config_example.toml +++ b/common/config/presets/tari_config_example.toml @@ -470,6 +470,7 @@ monerod_url = "http://monero-stagenet.exan.tech:38081" # stagenet # Address of the tari_merge_mining_proxy application proxy_host_address = "127.0.0.1:7878" +transcoder_host_address = "127.0.0.1:7879" # In sole merged mining, the block solution is usually submitted to the Monero blockchain # (monerod) as well as to the Tari blockchain, then this setting should be "true". With pool diff --git a/common/src/configuration/bootstrap.rs b/common/src/configuration/bootstrap.rs index 144fd5b00e..aee62a9cae 100644 --- a/common/src/configuration/bootstrap.rs +++ b/common/src/configuration/bootstrap.rs @@ -59,6 +59,7 @@ use crate::{ DEFAULT_CONFIG, DEFAULT_MERGE_MINING_PROXY_LOG_CONFIG, DEFAULT_MINING_NODE_LOG_CONFIG, + DEFAULT_STRATUM_TRANSCODER_LOG_CONFIG, DEFAULT_WALLET_LOG_CONFIG, }; use std::{ @@ -230,6 +231,12 @@ impl ConfigBootstrap { Some(&self.base_path), )) }, + ApplicationType::StratumTranscoder => { + self.log_config = normalize_path(dir_utils::default_path( + DEFAULT_STRATUM_TRANSCODER_LOG_CONFIG, + Some(&self.base_path), + )) + }, ApplicationType::MiningNode => { self.log_config = normalize_path(dir_utils::default_path( DEFAULT_MINING_NODE_LOG_CONFIG, @@ -279,6 +286,10 @@ impl ConfigBootstrap { &self.log_config, logging::install_default_merge_mining_proxy_logfile_config, ), + ApplicationType::StratumTranscoder => install_configuration( + &self.log_config, + logging::install_default_stratum_transcoder_logfile_config, + ), ApplicationType::MiningNode => { install_configuration(&self.log_config, logging::install_default_mining_node_logfile_config) }, @@ -329,6 +340,7 @@ pub enum ApplicationType { ConsoleWallet, MergeMiningProxy, MiningNode, + StratumTranscoder, } impl ApplicationType { @@ -339,6 +351,7 @@ impl ApplicationType { ConsoleWallet => "Tari Console Wallet", MergeMiningProxy => "Tari Merge Mining Proxy", MiningNode => "Tari Mining Node", + StratumTranscoder => "Tari Stratum Transcoder", } } @@ -349,6 +362,7 @@ impl ApplicationType { ConsoleWallet => "wallet", MergeMiningProxy => "merge_mining_proxy", MiningNode => "miner", + StratumTranscoder => "stratum-transcoder", } } } @@ -363,6 +377,7 @@ impl FromStr for ApplicationType { "console-wallet" | "console_wallet" => Ok(ConsoleWallet), "mm-proxy" | "mm_proxy" => Ok(MergeMiningProxy), "miner" => Ok(MiningNode), + "stratum-proxy" => Ok(StratumTranscoder), _ => Err(ConfigError::new("Invalid ApplicationType", None)), } } diff --git a/common/src/configuration/global.rs b/common/src/configuration/global.rs index 3dcfdf3a0c..6b316b1bfd 100644 --- a/common/src/configuration/global.rs +++ b/common/src/configuration/global.rs @@ -120,6 +120,7 @@ pub struct GlobalConfig { pub monerod_password: String, pub monerod_use_auth: bool, pub proxy_host_address: SocketAddr, + pub transcoder_host_address: SocketAddr, pub proxy_submit_to_origin: bool, pub force_sync_peers: Vec, pub wait_for_initial_sync_at_startup: bool, @@ -130,6 +131,8 @@ pub struct GlobalConfig { pub flood_ban_max_msg_count: usize, pub mine_on_tip_only: bool, pub validate_tip_timeout_sec: u64, + pub mining_pool_address: String, + pub mining_wallet_address: String, } impl GlobalConfig { @@ -616,6 +619,15 @@ fn convert_node_config( .map_err(|e| ConfigurationError::new(&key, &e.to_string())) })?; + let key = config_string("merge_mining_proxy", &net_str, "transcoder_host_address"); + let transcoder_host_address = cfg + .get_str(&key) + .map_err(|e| ConfigurationError::new(&key, &e.to_string())) + .and_then(|addr| { + addr.parse::() + .map_err(|e| ConfigurationError::new(&key, &e.to_string())) + })?; + let key = config_string("merge_mining_proxy", &net_str, "wait_for_initial_sync_at_startup"); let wait_for_initial_sync_at_startup = cfg .get_bool(&key) @@ -658,6 +670,11 @@ fn convert_node_config( let key = "common.auto_update.hashes_sig_url"; let autoupdate_hashes_sig_url = cfg.get_str(&key)?; + let key = "mining_node.mining_pool_address"; + let mining_pool_address = cfg.get_str(&key).unwrap_or_else(|_| "".to_string()); + let key = "mining_node.mining_wallet_address"; + let mining_wallet_address = cfg.get_str(&key).unwrap_or_else(|_| "".to_string()); + Ok(GlobalConfig { autoupdate_check_interval, autoupdate_dns_hosts, @@ -723,6 +740,7 @@ fn convert_node_config( wallet_base_node_service_request_max_age, prevent_fee_gt_amount, proxy_host_address, + transcoder_host_address, proxy_submit_to_origin, monerod_url, monerod_username, @@ -737,6 +755,8 @@ fn convert_node_config( flood_ban_max_msg_count, mine_on_tip_only, validate_tip_timeout_sec, + mining_pool_address, + mining_wallet_address, }) } diff --git a/common/src/configuration/utils.rs b/common/src/configuration/utils.rs index 5d65103d9e..fa19117e31 100644 --- a/common/src/configuration/utils.rs +++ b/common/src/configuration/utils.rs @@ -254,6 +254,8 @@ fn set_merge_mining_defaults(cfg: &mut Config) { .unwrap(); cfg.set_default("merge_mining_proxy.mainnet.proxy_host_address", "127.0.0.1:7878") .unwrap(); + cfg.set_default("merge_mining_proxy.mainnet.transcoder_host_address", "127.0.0.1:7879") + .unwrap(); cfg.set_default("merge_mining_proxy.mainnet.monerod_use_auth", "false") .unwrap(); cfg.set_default("merge_mining_proxy.mainnet.monerod_username", "") @@ -270,6 +272,8 @@ fn set_merge_mining_defaults(cfg: &mut Config) { .unwrap(); cfg.set_default("merge_mining_proxy.weatherwax.proxy_host_address", "127.0.0.1:7878") .unwrap(); + cfg.set_default("merge_mining_proxy.mainnet.transcoder_host_address", "127.0.0.1:7879") + .unwrap(); cfg.set_default("merge_mining_proxy.weatherwax.proxy_submit_to_origin", true) .unwrap(); cfg.set_default("merge_mining_proxy.weatherwax.monerod_use_auth", "false") diff --git a/common/src/lib.rs b/common/src/lib.rs index bc65a132a0..6f5c98a2e4 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -98,6 +98,7 @@ pub const DEFAULT_CONFIG: &str = "config/config.toml"; pub const DEFAULT_BASE_NODE_LOG_CONFIG: &str = "config/log4rs_base_node.yml"; pub const DEFAULT_WALLET_LOG_CONFIG: &str = "config/log4rs_console_wallet.yml"; pub const DEFAULT_MERGE_MINING_PROXY_LOG_CONFIG: &str = "config/log4rs_merge_mining_proxy.yml"; +pub const DEFAULT_STRATUM_TRANSCODER_LOG_CONFIG: &str = "config/log4rs_miningcore_transcoder.yml"; pub const DEFAULT_MINING_NODE_LOG_CONFIG: &str = "config/log4rs_mining_node.yml"; pub(crate) const LOG_TARGET: &str = "common::config"; diff --git a/common/src/logging.rs b/common/src/logging.rs index 1cf3621325..6e8e9f288b 100644 --- a/common/src/logging.rs +++ b/common/src/logging.rs @@ -69,6 +69,17 @@ pub fn install_default_merge_mining_proxy_logfile_config(path: &Path) -> Result< file.write_all(source.as_ref()) } +/// Installs a new default logfile configuration, copied from `log4rs_sample_proxy.yml` to the given path. +pub fn install_default_stratum_transcoder_logfile_config(path: &Path) -> Result<(), std::io::Error> { + let source = include_str!("../logging/log4rs_sample_proxy.yml"); + if let Some(d) = path.parent() { + fs::create_dir_all(d)? + }; + // Note: `fs::write(path, source)` did not work as expected, as the file name was not changed + let mut file = File::create(path)?; + file.write_all(source.as_ref()) +} + /// Installs a new default logfile configuration, copied from `log4rs_sample_wallet.yml` to the given path. pub fn install_default_mining_node_logfile_config(path: &Path) -> Result<(), std::io::Error> { let source = include_str!("../logging/log4rs_sample_mining_node.yml"); diff --git a/integration_tests/helpers/baseNodeProcess.js b/integration_tests/helpers/baseNodeProcess.js index 4551e4c030..0130a25e10 100644 --- a/integration_tests/helpers/baseNodeProcess.js +++ b/integration_tests/helpers/baseNodeProcess.js @@ -111,6 +111,7 @@ class BaseNodeProcess { this.grpcPort, this.port, "127.0.0.1:8080", + "127.0.0.1:8085", this.options, this.peerSeeds ); diff --git a/integration_tests/helpers/config.js b/integration_tests/helpers/config.js index 3743561a22..5034ad0cf7 100644 --- a/integration_tests/helpers/config.js +++ b/integration_tests/helpers/config.js @@ -124,6 +124,7 @@ function createEnv( baseNodeGrpcPort = "8080", baseNodePort = "8081", proxyFullAddress = "127.0.0.1:8084", + transcoderFullAddress = "127.0.0.1:8085", options, peerSeeds = [], _txnSendingMechanism = "DirectAndStoreAndForward" @@ -149,6 +150,9 @@ function createEnv( configEnvs[ `TARI_MERGE_MINING_PROXY__${network}__PROXY_HOST_ADDRESS` ] = `${proxyFullAddress}`; + configEnvs[ + `TARI_MERGE_MINING_PROXY__${network}__TRANSCODER_HOST_ADDRESS` + ] = `${transcoderFullAddress}`; configEnvs[`TARI_BASE_NODE__${network}__TRANSPORT`] = "tcp"; configEnvs[`TARI_WALLET__${network}__TRANSPORT`] = "tcp"; configEnvs[`TARI_WALLET__${network}__TCP_LISTENER_ADDRESS`] = diff --git a/integration_tests/helpers/mergeMiningProxyProcess.js b/integration_tests/helpers/mergeMiningProxyProcess.js index d26a081668..b1fdb563e9 100644 --- a/integration_tests/helpers/mergeMiningProxyProcess.js +++ b/integration_tests/helpers/mergeMiningProxyProcess.js @@ -46,6 +46,7 @@ class MergeMiningProxyProcess { } const proxyAddress = "127.0.0.1:" + this.port; + const envs = createEnv( this.name, false, @@ -57,6 +58,7 @@ class MergeMiningProxyProcess { this.nodeGrpcPort, this.baseNodePort, proxyAddress, + "127.0.0.1:8085", [], [] ); diff --git a/integration_tests/helpers/miningNodeProcess.js b/integration_tests/helpers/miningNodeProcess.js index faf05c7429..edee1a9301 100644 --- a/integration_tests/helpers/miningNodeProcess.js +++ b/integration_tests/helpers/miningNodeProcess.js @@ -69,6 +69,7 @@ class MiningNodeProcess { this.nodeGrpcPort, this.baseNodePort, "127.0.0.1:8084", + "127.0.0.1:8085", { mineOnTipOnly: this.mineOnTipOnly, numMiningThreads: this.numMiningThreads, diff --git a/integration_tests/helpers/walletProcess.js b/integration_tests/helpers/walletProcess.js index 80c291c407..3fb503b3dc 100644 --- a/integration_tests/helpers/walletProcess.js +++ b/integration_tests/helpers/walletProcess.js @@ -71,6 +71,7 @@ class WalletProcess { "8080", "8081", "127.0.0.1:8084", + "127.0.0.1:8085", this.options, this.peerSeeds );