diff --git a/Cargo.lock b/Cargo.lock index 687e614d25..a19a9759cf 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.2" 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.2" 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.2" @@ -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 0ff4302c35..2cc7b2a31e 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 d30a4983d6..9ed6f28db7 100644 --- a/integration_tests/helpers/walletProcess.js +++ b/integration_tests/helpers/walletProcess.js @@ -72,6 +72,7 @@ class WalletProcess { "8080", "8081", "127.0.0.1:8084", + "127.0.0.1:8085", this.options, this.peerSeeds );