diff --git a/roles/pool/Cargo.toml b/roles/pool/Cargo.toml index f3c284721..729ef00d7 100644 --- a/roles/pool/Cargo.toml +++ b/roles/pool/Cargo.toml @@ -39,6 +39,3 @@ key-utils = { path = "../../utils/key-utils" } [dev-dependencies] hex = "0.4.3" - -[features] -test_only_allow_unencrypted = [] diff --git a/roles/pool/src/lib/config.rs b/roles/pool/src/lib/config.rs new file mode 100644 index 000000000..51643a76f --- /dev/null +++ b/roles/pool/src/lib/config.rs @@ -0,0 +1,146 @@ +use key_utils::{Secp256k1PublicKey, Secp256k1SecretKey}; +use roles_logic_sv2::utils::CoinbaseOutput as CoinbaseOutput_; +use std::convert::TryFrom; + +#[derive(Clone, Debug, serde::Deserialize)] +pub struct PoolConfig { + listen_address: String, + tp_address: String, + tp_authority_public_key: Option, + authority_public_key: Secp256k1PublicKey, + authority_secret_key: Secp256k1SecretKey, + cert_validity_sec: u64, + coinbase_outputs: Vec, + pool_signature: String, +} + +impl PoolConfig { + pub fn new( + pool_connection: ConnectionConfig, + template_provider: TemplateProviderConfig, + authority_config: AuthorityConfig, + coinbase_outputs: Vec, + ) -> Self { + Self { + listen_address: pool_connection.listen_address, + tp_address: template_provider.address, + tp_authority_public_key: template_provider.authority_public_key, + authority_public_key: authority_config.public_key, + authority_secret_key: authority_config.secret_key, + cert_validity_sec: pool_connection.cert_validity_sec, + coinbase_outputs, + pool_signature: pool_connection.signature, + } + } + + pub fn coinbase_outputs(&self) -> &Vec { + self.coinbase_outputs.as_ref() + } + + pub fn listen_address(&self) -> &String { + &self.listen_address + } + + pub fn authority_public_key(&self) -> &Secp256k1PublicKey { + &self.authority_public_key + } + + pub fn authority_secret_key(&self) -> &Secp256k1SecretKey { + &self.authority_secret_key + } + + pub fn cert_validity_sec(&self) -> u64 { + self.cert_validity_sec + } + + pub fn pool_signature(&self) -> &String { + &self.pool_signature + } + + pub fn tp_authority_public_key(&self) -> Option<&Secp256k1PublicKey> { + self.tp_authority_public_key.as_ref() + } + + pub fn tp_address(&self) -> &String { + &self.tp_address + } + + pub fn set_coinbase_outputs(&mut self, coinbase_outputs: Vec) { + self.coinbase_outputs = coinbase_outputs; + } +} + +pub struct TemplateProviderConfig { + address: String, + authority_public_key: Option, +} + +impl TemplateProviderConfig { + pub fn new(address: String, authority_public_key: Option) -> Self { + Self { + address, + authority_public_key, + } + } +} + +pub struct AuthorityConfig { + pub public_key: Secp256k1PublicKey, + pub secret_key: Secp256k1SecretKey, +} + +impl AuthorityConfig { + pub fn new(public_key: Secp256k1PublicKey, secret_key: Secp256k1SecretKey) -> Self { + Self { + public_key, + secret_key, + } + } +} + +pub struct ConnectionConfig { + listen_address: String, + cert_validity_sec: u64, + signature: String, +} + +impl ConnectionConfig { + pub fn new(listen_address: String, cert_validity_sec: u64, signature: String) -> Self { + Self { + listen_address, + cert_validity_sec, + signature, + } + } +} + +#[derive(Clone, Debug, serde::Deserialize)] +pub struct CoinbaseOutput { + output_script_type: String, + output_script_value: String, +} + +impl CoinbaseOutput { + pub fn new(output_script_type: String, output_script_value: String) -> Self { + Self { + output_script_type, + output_script_value, + } + } +} + +impl TryFrom<&CoinbaseOutput> for CoinbaseOutput_ { + type Error = roles_logic_sv2::errors::Error; + + fn try_from(pool_output: &CoinbaseOutput) -> Result { + match pool_output.output_script_type.as_str() { + "TEST" | "P2PK" | "P2PKH" | "P2WPKH" | "P2SH" | "P2WSH" | "P2TR" => { + Ok(CoinbaseOutput_ { + output_script_type: pool_output.clone().output_script_type, + output_script_value: pool_output.clone().output_script_value, + }) + } + _ => Err(roles_logic_sv2::Error::UnknownOutputScriptType), + } + } +} diff --git a/roles/pool/src/lib/mining_pool/mod.rs b/roles/pool/src/lib/mining_pool/mod.rs index 454429cee..f843b516e 100644 --- a/roles/pool/src/lib/mining_pool/mod.rs +++ b/roles/pool/src/lib/mining_pool/mod.rs @@ -1,3 +1,5 @@ +use crate::config::PoolConfig; + use super::{ error::{PoolError, PoolResult}, status, @@ -6,7 +8,7 @@ use async_channel::{Receiver, Sender}; use binary_sv2::U256; use codec_sv2::{HandshakeRole, Responder, StandardEitherFrame, StandardSv2Frame}; use error_handling::handle_result; -use key_utils::{Secp256k1PublicKey, Secp256k1SecretKey, SignatureService}; +use key_utils::SignatureService; use network_helpers_sv2::noise_connection::Connection; use nohash_hasher::BuildNoHashHasher; use roles_logic_sv2::{ @@ -21,13 +23,7 @@ use roles_logic_sv2::{ template_distribution_sv2::{NewTemplate, SetNewPrevHash, SubmitSolution}, utils::{CoinbaseOutput as CoinbaseOutput_, Mutex}, }; -use serde::Deserialize; -use std::{ - collections::HashMap, - convert::{TryFrom, TryInto}, - net::SocketAddr, - sync::Arc, -}; +use std::{collections::HashMap, convert::TryInto, net::SocketAddr, sync::Arc}; use stratum_common::{ bitcoin::{Script, TxOut}, secp256k1, @@ -44,9 +40,9 @@ pub type Message = PoolMessages<'static>; pub type StdFrame = StandardSv2Frame; pub type EitherFrame = StandardEitherFrame; -pub fn get_coinbase_output(config: &Configuration) -> Result, Error> { +pub fn get_coinbase_output(config: &PoolConfig) -> Result, Error> { let mut result = Vec::new(); - for coinbase_output_pool in &config.coinbase_outputs { + for coinbase_output_pool in config.coinbase_outputs() { let coinbase_output: CoinbaseOutput_ = coinbase_output_pool.try_into()?; let output_script: Script = coinbase_output.try_into()?; result.push(TxOut { @@ -60,118 +56,6 @@ pub fn get_coinbase_output(config: &Configuration) -> Result, Error> } } -#[derive(Debug, Deserialize, Clone)] -pub struct CoinbaseOutput { - output_script_type: String, - output_script_value: String, -} - -impl CoinbaseOutput { - pub fn new(output_script_type: String, output_script_value: String) -> Self { - Self { - output_script_type, - output_script_value, - } - } -} - -impl TryFrom<&CoinbaseOutput> for CoinbaseOutput_ { - type Error = Error; - - fn try_from(pool_output: &CoinbaseOutput) -> Result { - match pool_output.output_script_type.as_str() { - "TEST" | "P2PK" | "P2PKH" | "P2WPKH" | "P2SH" | "P2WSH" | "P2TR" => { - Ok(CoinbaseOutput_ { - output_script_type: pool_output.clone().output_script_type, - output_script_value: pool_output.clone().output_script_value, - }) - } - _ => Err(Error::UnknownOutputScriptType), - } - } -} - -#[derive(Debug, Deserialize, Clone)] -pub struct Configuration { - pub listen_address: String, - pub tp_address: String, - pub tp_authority_public_key: Option, - pub authority_public_key: Secp256k1PublicKey, - pub authority_secret_key: Secp256k1SecretKey, - pub cert_validity_sec: u64, - pub coinbase_outputs: Vec, - pub pool_signature: String, - #[cfg(feature = "test_only_allow_unencrypted")] - pub test_only_listen_adress_plain: String, -} - -pub struct TemplateProviderConfig { - address: String, - authority_public_key: Option, -} - -impl TemplateProviderConfig { - pub fn new(address: String, authority_public_key: Option) -> Self { - Self { - address, - authority_public_key, - } - } -} - -pub struct AuthorityConfig { - pub public_key: Secp256k1PublicKey, - pub secret_key: Secp256k1SecretKey, -} - -impl AuthorityConfig { - pub fn new(public_key: Secp256k1PublicKey, secret_key: Secp256k1SecretKey) -> Self { - Self { - public_key, - secret_key, - } - } -} - -pub struct ConnectionConfig { - listen_address: String, - cert_validity_sec: u64, - signature: String, -} - -impl ConnectionConfig { - pub fn new(listen_address: String, cert_validity_sec: u64, signature: String) -> Self { - Self { - listen_address, - cert_validity_sec, - signature, - } - } -} - -impl Configuration { - pub fn new( - pool_connection: ConnectionConfig, - template_provider: TemplateProviderConfig, - authority_config: AuthorityConfig, - coinbase_outputs: Vec, - #[cfg(feature = "test_only_allow_unencrypted")] test_only_listen_adress_plain: String, - ) -> Self { - Self { - listen_address: pool_connection.listen_address, - tp_address: template_provider.address, - tp_authority_public_key: template_provider.authority_public_key, - authority_public_key: authority_config.public_key, - authority_secret_key: authority_config.secret_key, - cert_validity_sec: pool_connection.cert_validity_sec, - coinbase_outputs, - pool_signature: pool_connection.signature, - #[cfg(feature = "test_only_allow_unencrypted")] - test_only_listen_adress_plain, - } - } -} - #[derive(Debug)] pub struct Downstream { // Either group or channel id @@ -383,44 +267,15 @@ impl IsDownstream for Downstream { impl IsMiningDownstream for Downstream {} impl Pool { - #[cfg(feature = "test_only_allow_unencrypted")] - async fn accept_incoming_plain_connection( - self_: Arc>, - config: Configuration, - ) -> PoolResult<()> { - let listner = TcpListener::bind(&config.test_only_listen_adress_plain) - .await - .unwrap(); - let status_tx = self_.safe_lock(|s| s.status_tx.clone())?; - - info!( - "Listening for unencrypted connection on: {}", - config.test_only_listen_adress_plain - ); - while let Ok((stream, _)) = listner.accept().await { - let address = stream.peer_addr().unwrap(); - debug!("New connection from {}", address); - - let (receiver, sender): (Receiver, Sender) = - network_helpers_sv2::plain_connection_tokio::PlainConnection::new(stream).await; - - handle_result!( - status_tx, - Self::accept_incoming_connection_(self_.clone(), receiver, sender, address).await - ); - } - Ok(()) - } - async fn accept_incoming_connection( self_: Arc>, - config: Configuration, + config: PoolConfig, ) -> PoolResult<()> { let status_tx = self_.safe_lock(|s| s.status_tx.clone())?; - let listener = TcpListener::bind(&config.listen_address).await?; + let listener = TcpListener::bind(&config.listen_address()).await?; info!( "Listening for encrypted connection on: {}", - config.listen_address + config.listen_address() ); while let Ok((stream, _)) = listener.accept().await { let address = stream.peer_addr().unwrap(); @@ -430,9 +285,9 @@ impl Pool { ); let responder = Responder::from_authority_kp( - &config.authority_public_key.into_bytes(), - &config.authority_secret_key.into_bytes(), - std::time::Duration::from_secs(config.cert_validity_sec), + &config.authority_public_key().into_bytes(), + &config.authority_secret_key().into_bytes(), + std::time::Duration::from_secs(config.cert_validity_sec()), ); match responder { Ok(resp) => { @@ -590,7 +445,7 @@ impl Pool { } pub fn start( - config: Configuration, + config: PoolConfig, new_template_rx: Receiver>, new_prev_hash_rx: Receiver>, solution_sender: Sender>, @@ -618,7 +473,7 @@ impl Pool { share_per_min, kind, pool_coinbase_outputs.expect("Invalid coinbase output in config"), - config.pool_signature.clone().into(), + config.pool_signature().clone().into(), ))); let pool = Arc::new(Mutex::new(Pool { downstreams: HashMap::with_hasher(BuildNoHashHasher::default()), @@ -633,31 +488,6 @@ impl Pool { let cloned2 = pool.clone(); let cloned3 = pool.clone(); - #[cfg(feature = "test_only_allow_unencrypted")] - { - let cloned4 = pool.clone(); - let status_tx_clone_unenc = status_tx.clone(); - let config_unenc = config.clone(); - - task::spawn(async move { - if let Err(e) = Self::accept_incoming_plain_connection(cloned4, config_unenc).await - { - error!("{}", e); - } - if status_tx_clone_unenc - .send(status::Status { - state: status::State::DownstreamShutdown(PoolError::ComponentShutdown( - "Downstream no longer accepting incoming connections".to_string(), - )), - }) - .await - .is_err() - { - error!("Downstream shutdown and Status Channel dropped"); - } - }); - } - info!("Starting up pool listener"); let status_tx_clone = status_tx.clone(); task::spawn(async move { @@ -742,7 +572,7 @@ mod test { bitcoin::{util::psbt::serialize::Serialize, Transaction, Witness}, }; - use super::Configuration; + use super::PoolConfig; // this test is used to verify the `coinbase_tx_prefix` and `coinbase_tx_suffix` values tested // against in message generator @@ -752,11 +582,11 @@ mod test { let config_path = "./config-examples/pool-config-local-tp-example.toml"; // Load config - let config: Configuration = match Config::builder() + let config: PoolConfig = match Config::builder() .add_source(File::new(&config_path, FileFormat::Toml)) .build() { - Ok(settings) => match settings.try_deserialize::() { + Ok(settings) => match settings.try_deserialize::() { Ok(c) => c, Err(e) => { error!("Failed to deserialize config: {}", e); @@ -785,8 +615,8 @@ mod test { // build coinbase TX from 'job_creator::coinbase()' let mut bip34_bytes = get_bip_34_bytes(coinbase_prefix.try_into().unwrap()); - let script_prefix_length = bip34_bytes.len() + config.pool_signature.as_bytes().len(); - bip34_bytes.extend_from_slice(config.pool_signature.as_bytes()); + let script_prefix_length = bip34_bytes.len() + config.pool_signature().as_bytes().len(); + bip34_bytes.extend_from_slice(config.pool_signature().as_bytes()); bip34_bytes.extend_from_slice(&vec![0; extranonce_len as usize]); let witness = match bip34_bytes.len() { 0 => Witness::from_vec(vec![]), diff --git a/roles/pool/src/lib/mod.rs b/roles/pool/src/lib/mod.rs index 4fee0be15..6b712ca7a 100644 --- a/roles/pool/src/lib/mod.rs +++ b/roles/pool/src/lib/mod.rs @@ -1,3 +1,4 @@ +pub mod config; pub mod error; pub mod mining_pool; pub mod status; @@ -5,8 +6,9 @@ pub mod template_receiver; use async_channel::{bounded, unbounded}; +use config::PoolConfig; use error::PoolError; -use mining_pool::{get_coinbase_output, Configuration, Pool}; +use mining_pool::{get_coinbase_output, Pool}; use template_receiver::TemplateRx; use tracing::{error, info, warn}; @@ -14,11 +16,11 @@ use tokio::select; #[derive(Debug, Clone)] pub struct PoolSv2 { - config: Configuration, + config: PoolConfig, } impl PoolSv2 { - pub fn new(config: Configuration) -> PoolSv2 { + pub fn new(config: PoolConfig) -> PoolSv2 { PoolSv2 { config } } @@ -31,16 +33,16 @@ impl PoolSv2 { let (s_message_recv_signal, r_message_recv_signal) = bounded(10); let coinbase_output_result = get_coinbase_output(&config); let coinbase_output_len = coinbase_output_result?.len() as u32; - let tp_authority_public_key = config.tp_authority_public_key; + let tp_authority_public_key = config.tp_authority_public_key(); TemplateRx::connect( - config.tp_address.parse().unwrap(), + config.tp_address().parse().unwrap(), s_new_t, s_prev_hash, r_solution, r_message_recv_signal, status::Sender::Upstream(status_tx.clone()), coinbase_output_len, - tp_authority_public_key, + tp_authority_public_key.cloned(), ) .await?; let pool = Pool::start( @@ -109,16 +111,16 @@ mod tests { #[tokio::test] async fn pool_bad_coinbase_output() { - let invalid_coinbase_output = vec![mining_pool::CoinbaseOutput::new( + let invalid_coinbase_output = vec![config::CoinbaseOutput::new( "P2PK".to_string(), "wrong".to_string(), )]; let config_path = "config-examples/pool-config-hosted-tp-example.toml"; - let mut config: Configuration = match Config::builder() + let mut config: PoolConfig = match Config::builder() .add_source(File::new(config_path, FileFormat::Toml)) .build() { - Ok(settings) => match settings.try_deserialize::() { + Ok(settings) => match settings.try_deserialize::() { Ok(c) => c, Err(e) => { error!("Failed to deserialize config: {}", e); @@ -130,7 +132,7 @@ mod tests { return; } }; - config.coinbase_outputs = invalid_coinbase_output; + config.set_coinbase_outputs(invalid_coinbase_output); let pool = PoolSv2::new(config); let result = pool.start().await; assert!(result.is_err()); diff --git a/roles/pool/src/main.rs b/roles/pool/src/main.rs index 34f5983e5..4696a0bb0 100644 --- a/roles/pool/src/main.rs +++ b/roles/pool/src/main.rs @@ -2,7 +2,7 @@ mod lib; use ext_config::{Config, File, FileFormat}; -pub use lib::{mining_pool::Configuration, status, PoolSv2}; +pub use lib::{config, status, PoolSv2}; use tracing::error; mod args { @@ -83,11 +83,11 @@ async fn main() { let config_path = args.config_path.to_str().expect("Invalid config path"); // Load config - let config: Configuration = match Config::builder() + let config: config::PoolConfig = match Config::builder() .add_source(File::new(config_path, FileFormat::Toml)) .build() { - Ok(settings) => match settings.try_deserialize::() { + Ok(settings) => match settings.try_deserialize::() { Ok(c) => c, Err(e) => { error!("Failed to deserialize config: {}", e); diff --git a/roles/tests-integration/lib/mod.rs b/roles/tests-integration/lib/mod.rs index ef28b66e5..0b959a52d 100644 --- a/roles/tests-integration/lib/mod.rs +++ b/roles/tests-integration/lib/mod.rs @@ -52,7 +52,7 @@ pub async fn start_sniffer( } pub async fn start_pool(template_provider_address: Option) -> (PoolSv2, SocketAddr) { - use pool_sv2::mining_pool::{CoinbaseOutput, Configuration}; + use pool_sv2::config::{CoinbaseOutput, PoolConfig}; let listening_address = get_available_address(); let authority_public_key = Secp256k1PublicKey::try_from( "9auqWEzQDVyd2oe1JVGFLMLHZtCo2FFqZwtKA5gd9xbuEu7PH72".to_string(), @@ -73,16 +73,15 @@ pub async fn start_pool(template_provider_address: Option) -> (PoolS } else { "127.0.0.1:8442".to_string() }; - let connection_config = pool_sv2::mining_pool::ConnectionConfig::new( + let connection_config = pool_sv2::config::ConnectionConfig::new( listening_address.to_string(), cert_validity_sec, pool_signature, ); - let template_provider_config = - pool_sv2::mining_pool::TemplateProviderConfig::new(tp_address, None); + let template_provider_config = pool_sv2::config::TemplateProviderConfig::new(tp_address, None); let authority_config = - pool_sv2::mining_pool::AuthorityConfig::new(authority_public_key, authority_secret_key); - let config = Configuration::new( + pool_sv2::config::AuthorityConfig::new(authority_public_key, authority_secret_key); + let config = PoolConfig::new( connection_config, template_provider_config, authority_config,