diff --git a/Cargo.toml b/Cargo.toml index ff880a4..ea91921 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,4 +1,5 @@ [workspace] +resolver = "2" members = [ "cli", @@ -9,5 +10,6 @@ members = [ "proof_of_space", "puzzles", "serialize", + "servers", "tests" ] \ No newline at end of file diff --git a/cli/Cargo.toml b/cli/Cargo.toml index f1b7b34..39dff97 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dg_xch_cli" -version = "1.2.1" +version = "2.0.0" edition = "2021" authors = ["James Hoerr"] description = "CLI Utilities for the Chia Blockchain" @@ -22,12 +22,12 @@ bip39 = {version= "2.0.0", features=["rand"] } blst = "0.3.11" clap = { version = "4.4.8", features = ["derive"] } dashmap = "5.5.3" -dg_xch_clients = {path = "../clients", version="1.2.1"} -dg_xch_core = {path = "../core", version = "1.2.1", features = ["paperclip"] } -dg_xch_keys = {path = "../keys", version="1.2.1"} -dg_xch_pos = {path = "../proof_of_space", version="1.2.1"} -dg_xch_puzzles = {path = "../puzzles", version="1.2.1"} -dg_xch_serialize= {path = "../serialize", version="1.2.1"} +dg_xch_clients = {path = "../clients", version="2.0.0"} +dg_xch_core = {path = "../core", version = "2.0.0", features = ["paperclip"] } +dg_xch_keys = {path = "../keys", version="2.0.0"} +dg_xch_pos = {path = "../proof_of_space", version="2.0.0"} +dg_xch_puzzles = {path = "../puzzles", version="2.0.0"} +dg_xch_serialize= {path = "../serialize", version="2.0.0"} hex = "0.4.3" lazy_static = "1.4.0" log = "0.4.20" diff --git a/cli/src/main.rs b/cli/src/main.rs index d9ff8dc..5673f5c 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -5,7 +5,7 @@ use cli::*; use dg_xch_cli::wallet_commands::{ create_cold_wallet, get_plotnft_ready_state, migrate_plot_nft, migrate_plot_nft_with_owner_key, }; -use dg_xch_clients::protocols::pool::create_pool_login_url; +use dg_xch_clients::api::pool::create_pool_login_url; use dg_xch_clients::rpc::full_node::FullnodeClient; use dg_xch_core::blockchain::sized_bytes::Bytes32; use simple_logger::SimpleLogger; diff --git a/cli/src/wallet_commands.rs b/cli/src/wallet_commands.rs index 8cafa64..61259ae 100644 --- a/cli/src/wallet_commands.rs +++ b/cli/src/wallet_commands.rs @@ -7,14 +7,12 @@ use bip39::Mnemonic; use blst::min_pk::SecretKey; use dg_xch_clients::api::full_node::FullnodeAPI; use dg_xch_clients::api::pool::{DefaultPoolClient, PoolClient}; -use dg_xch_clients::protocols::pool::{ - GetPoolInfoResponse, FARMING_TO_POOL, POOL_PROTOCOL_VERSION, -}; use dg_xch_clients::rpc::full_node::FullnodeClient; use dg_xch_core::blockchain::coin_spend::CoinSpend; use dg_xch_core::blockchain::sized_bytes::{Bytes32, Bytes48}; use dg_xch_core::plots::PlotNft; use dg_xch_core::pool::PoolState; +use dg_xch_core::protocols::pool::{GetPoolInfoResponse, FARMING_TO_POOL, POOL_PROTOCOL_VERSION}; use dg_xch_keys::*; use dg_xch_puzzles::p2_delegated_puzzle_or_hidden_puzzle::{ calculate_synthetic_secret_key, puzzle_hash_for_pk, DEFAULT_HIDDEN_PUZZLE_HASH, diff --git a/cli/src/wallets/plotnft_utils.rs b/cli/src/wallets/plotnft_utils.rs index 40c039b..2f862e5 100644 --- a/cli/src/wallets/plotnft_utils.rs +++ b/cli/src/wallets/plotnft_utils.rs @@ -4,7 +4,6 @@ use crate::wallets::{Wallet, WalletInfo}; use async_trait::async_trait; use blst::min_pk::SecretKey; use dg_xch_clients::api::full_node::FullnodeAPI; -use dg_xch_clients::protocols::pool::{FARMING_TO_POOL, LEAVING_POOL, POOL_PROTOCOL_VERSION}; use dg_xch_clients::rpc::full_node::FullnodeClient; use dg_xch_core::blockchain::announcement::Announcement; use dg_xch_core::blockchain::coin_record::CoinRecord; @@ -17,6 +16,7 @@ use dg_xch_core::blockchain::wallet_type::WalletType; use dg_xch_core::consensus::constants::ConsensusConstants; use dg_xch_core::plots::PlotNft; use dg_xch_core::pool::PoolState; +use dg_xch_core::protocols::pool::{FARMING_TO_POOL, LEAVING_POOL, POOL_PROTOCOL_VERSION}; use dg_xch_keys::{ master_sk_to_singleton_owner_sk, master_sk_to_wallet_sk, master_sk_to_wallet_sk_unhardened, }; diff --git a/clients/Cargo.toml b/clients/Cargo.toml index d8bb23d..2d550e3 100644 --- a/clients/Cargo.toml +++ b/clients/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dg_xch_clients" -version = "1.2.1" +version = "2.0.0" edition = "2021" authors = ["James Hoerr"] description = "RPC and Websocket Clients the Chia Blockchain" @@ -12,22 +12,28 @@ repository = "https://github.com/GalactechsLLC/dg_xch_utils/clients" async-trait = "0.1.74" blst = "0.3.11" dashmap = "5.5.3" -dg_xch_core = {path = "../core", version = "1.2.1", features = ["paperclip"] } -dg_xch_macros = {path = "../macros", version="1.2.1"} -dg_xch_pos = {path = "../proof_of_space", version="1.2.1"} -dg_xch_serialize = {path = "../serialize", version="1.2.1"} +dg_xch_core = {path = "../core", version = "2.0.0", features = ["paperclip"] } +dg_xch_keys = {path = "../keys", version="2.0.0"} +dg_xch_macros = {path = "../macros", version="2.0.0"} +dg_xch_pos = {path = "../proof_of_space", version="2.0.0"} +dg_xch_serialize = {path = "../serialize", version="2.0.0"} futures-util = "0.3.29" hex = "0.4.3" hyper = {version="1.0.1", features=["full"]} hyper-util = {version="0.1.1", features=["full"]} log = "0.4.20" +prometheus = {version="0.13.3", features=["protobuf"], optional = true} reqwest = {version="0.11.22", default-features = false, features =["rustls-tls-webpki-roots", "json"]} -rustls = {version = "0.21.8", features = ["dangerous_configuration"] } -rustls-pemfile = "1.0.3" +rustls = {version = "0.21.9", features = ["dangerous_configuration"] } +rustls-pemfile = "1.0.4" serde = { version = "1.0.193", features = ["derive"] } serde_json = "1.0.108" time = "0.3.30" tokio = {version = "1.34.0", features=["rt-multi-thread", "sync", "signal", "macros", "process", "time", "fs", "net"]} tokio-tungstenite = {version = "0.20.1", features = ["rustls-tls-webpki-roots", "rustls"] } urlencoding = "2.1.3" -uuid = {version="1.6.1", features=["v4"]} \ No newline at end of file +uuid = {version="1.6.1", features=["v4"]} + +[features] +metrics = ["dep:prometheus", "dg_xch_core/metrics"] +default = [] \ No newline at end of file diff --git a/clients/src/api/full_node.rs b/clients/src/api/full_node.rs index 056af8a..de71a7f 100644 --- a/clients/src/api/full_node.rs +++ b/clients/src/api/full_node.rs @@ -1,5 +1,3 @@ -use crate::protocols::full_node::BlockCountMetrics; -use crate::protocols::full_node::FeeEstimate; use async_trait::async_trait; use dg_xch_core::blockchain::block_record::BlockRecord; use dg_xch_core::blockchain::blockchain_state::BlockchainState; @@ -13,6 +11,8 @@ use dg_xch_core::blockchain::sized_bytes::Bytes32; use dg_xch_core::blockchain::spend_bundle::SpendBundle; use dg_xch_core::blockchain::tx_status::TXStatus; use dg_xch_core::blockchain::unfinished_block::UnfinishedBlock; +use dg_xch_core::protocols::full_node::BlockCountMetrics; +use dg_xch_core::protocols::full_node::FeeEstimate; use std::collections::HashMap; use std::io::Error; diff --git a/clients/src/api/pool.rs b/clients/src/api/pool.rs index 49ddd78..434e913 100644 --- a/clients/src/api/pool.rs +++ b/clients/src/api/pool.rs @@ -1,15 +1,20 @@ use crate::api::RequestMode; -use crate::protocols::pool::{ - GetFarmerRequest, GetFarmerResponse, GetPoolInfoResponse, PoolError, PoolErrorCode, - PostFarmerRequest, PostFarmerResponse, PostPartialRequest, PostPartialResponse, - PutFarmerRequest, PutFarmerResponse, -}; use async_trait::async_trait; +use blst::min_pk::{AggregateSignature, SecretKey, Signature}; +use dg_xch_core::blockchain::sized_bytes::{Bytes32, SizedBytes}; +use dg_xch_core::clvm::bls_bindings::sign; +use dg_xch_core::protocols::pool::{ + get_current_authentication_token, AuthenticationPayload, GetFarmerRequest, GetFarmerResponse, + GetPoolInfoResponse, PoolError, PoolErrorCode, PostFarmerRequest, PostFarmerResponse, + PostPartialRequest, PostPartialResponse, PutFarmerRequest, PutFarmerResponse, +}; +use dg_xch_serialize::{hash_256, ChiaSerialize}; use log::warn; use reqwest::{Client, RequestBuilder}; use serde::de::DeserializeOwned; use serde::Serialize; use std::collections::HashMap; +use std::io::{Error, ErrorKind}; #[async_trait] pub trait PoolClient { @@ -194,3 +199,71 @@ async fn send_request( } } } + +#[derive(serde::Serialize, serde::Deserialize, Debug)] +pub struct PoolLoginParts { + pub auth_token: u64, + pub aggregate_signature: String, +} + +pub async fn create_pool_login_url( + target_pool: &str, + keys_and_launcher_ids: &[(SecretKey, Bytes32)], +) -> Result { + let parts = create_pool_login_parts(target_pool, keys_and_launcher_ids).await?; + let mut ids = String::new(); + for (index, (_, launcher_id)) in keys_and_launcher_ids.iter().enumerate() { + if index != 0 { + ids.push(',') + } + ids.push_str(&hex::encode(launcher_id.as_slice())); + } + Ok(format!( + "{target_pool}/login?launcher_id={ids}&authentication_token={}&signature={})", + parts.auth_token, parts.aggregate_signature + )) +} + +pub async fn create_pool_login_parts( + target_pool: &str, + keys_and_launcher_ids: &[(SecretKey, Bytes32)], +) -> Result { + let pool_client = DefaultPoolClient::new(); + let pool_info = pool_client + .get_pool_info(target_pool) + .await + .map_err(|e| Error::new(ErrorKind::Other, format!("{:?}", e)))?; + let current_auth_token = + get_current_authentication_token(pool_info.authentication_token_timeout); + let mut sigs = vec![]; + for (sec_key, launcher_id) in keys_and_launcher_ids { + let payload = AuthenticationPayload { + method_name: String::from("get_login"), + launcher_id: *launcher_id, + target_puzzle_hash: pool_info.target_puzzle_hash, + authentication_token: current_auth_token, + }; + let to_sign = hash_256(payload.to_bytes()); + let sig = sign(sec_key, &to_sign); + sigs.push(sig); + } + if !sigs.is_empty() { + let aggregate_signature = + AggregateSignature::aggregate(sigs.iter().collect::>().as_ref(), true) + .map_err(|e| { + Error::new( + ErrorKind::InvalidInput, + format!("Failed to calculate signature: {:?}", e), + ) + })?; + Ok(PoolLoginParts { + auth_token: current_auth_token, + aggregate_signature: hex::encode(aggregate_signature.to_signature().to_bytes()), + }) + } else { + Err(Error::new( + ErrorKind::NotFound, + "No Launcher IDs with Keys found", + )) + } +} diff --git a/clients/src/api/responses.rs b/clients/src/api/responses.rs index 0cc99d1..84e8ecf 100644 --- a/clients/src/api/responses.rs +++ b/clients/src/api/responses.rs @@ -1,5 +1,3 @@ -use crate::protocols::full_node::BlockCountMetrics; -use crate::protocols::full_node::FeeEstimate; use dg_xch_core::blockchain::block_record::BlockRecord; use dg_xch_core::blockchain::blockchain_state::BlockchainState; use dg_xch_core::blockchain::coin_record::{CoinRecord, HintedCoinRecord}; @@ -13,6 +11,8 @@ use dg_xch_core::blockchain::tx_status::TXStatus; use dg_xch_core::blockchain::unfinished_block::UnfinishedBlock; use dg_xch_core::blockchain::wallet_balance::WalletBalance; use dg_xch_core::blockchain::wallet_info::WalletInfo; +use dg_xch_core::protocols::full_node::BlockCountMetrics; +use dg_xch_core::protocols::full_node::FeeEstimate; use dg_xch_core::blockchain::sized_bytes::Bytes32; use serde::{Deserialize, Serialize}; diff --git a/clients/src/lib.rs b/clients/src/lib.rs index 4a9e7bd..6296788 100644 --- a/clients/src/lib.rs +++ b/clients/src/lib.rs @@ -1,4 +1,3 @@ pub mod api; -pub mod protocols; pub mod rpc; pub mod websocket; diff --git a/clients/src/protocols/farmer.rs b/clients/src/protocols/farmer.rs deleted file mode 100644 index 8de061b..0000000 --- a/clients/src/protocols/farmer.rs +++ /dev/null @@ -1,108 +0,0 @@ -use dg_xch_core::blockchain::pool_target::PoolTarget; -use dg_xch_core::blockchain::proof_of_space::ProofOfSpace; -use dg_xch_core::blockchain::sized_bytes::{Bytes32, Bytes96}; -use dg_xch_macros::ChiaSerial; -use hyper::body::Buf; -use log::debug; -use serde::{Deserialize, Serialize}; - -#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)] -pub struct NewSignagePoint { - pub challenge_hash: Bytes32, - pub challenge_chain_sp: Bytes32, - pub reward_chain_sp: Bytes32, - pub difficulty: u64, - pub sub_slot_iters: u64, - pub signage_point_index: u8, - pub peak_height: u32, -} - -impl dg_xch_serialize::ChiaSerialize for NewSignagePoint { - fn to_bytes(&self) -> Vec { - let mut bytes = vec![]; - bytes.extend(dg_xch_serialize::ChiaSerialize::to_bytes( - &self.challenge_hash, - )); - bytes.extend(dg_xch_serialize::ChiaSerialize::to_bytes( - &self.challenge_chain_sp, - )); - bytes.extend(dg_xch_serialize::ChiaSerialize::to_bytes( - &self.reward_chain_sp, - )); - bytes.extend(dg_xch_serialize::ChiaSerialize::to_bytes(&self.difficulty)); - bytes.extend(dg_xch_serialize::ChiaSerialize::to_bytes( - &self.sub_slot_iters, - )); - bytes.extend(dg_xch_serialize::ChiaSerialize::to_bytes( - &self.signage_point_index, - )); - bytes.extend(dg_xch_serialize::ChiaSerialize::to_bytes(&self.peak_height)); - bytes - } - fn from_bytes>(bytes: &mut std::io::Cursor) -> Result - where - Self: Sized, - { - let challenge_hash = dg_xch_serialize::ChiaSerialize::from_bytes(bytes)?; - let challenge_chain_sp = dg_xch_serialize::ChiaSerialize::from_bytes(bytes)?; - let reward_chain_sp = dg_xch_serialize::ChiaSerialize::from_bytes(bytes)?; - let difficulty = dg_xch_serialize::ChiaSerialize::from_bytes(bytes)?; - let sub_slot_iters = dg_xch_serialize::ChiaSerialize::from_bytes(bytes)?; - let signage_point_index = dg_xch_serialize::ChiaSerialize::from_bytes(bytes)?; - let peak_height = if bytes.remaining() >= 4 { - //Maintain Compatibility with < Chia 2.X nodes for now - dg_xch_serialize::ChiaSerialize::from_bytes(bytes)? - } else { - debug!("You are connected to an old node version, Please update your Fullnode."); - 0u32 - }; - Ok(Self { - challenge_hash, - challenge_chain_sp, - reward_chain_sp, - difficulty, - sub_slot_iters, - signage_point_index, - peak_height, - }) - } -} - -#[derive(ChiaSerial, Clone, PartialEq, Eq, Serialize, Deserialize, Debug)] -pub struct DeclareProofOfSpace { - pub challenge_hash: Bytes32, - pub challenge_chain_sp: Bytes32, - pub signage_point_index: u8, - pub reward_chain_sp: Bytes32, - pub proof_of_space: ProofOfSpace, - pub challenge_chain_sp_signature: Bytes96, - pub reward_chain_sp_signature: Bytes96, - pub farmer_puzzle_hash: Bytes32, - pub pool_target: Option, - pub pool_signature: Option, -} - -#[derive(ChiaSerial, Clone, PartialEq, Eq, Serialize, Deserialize, Debug)] -pub struct RequestSignedValues { - pub quality_string: Bytes32, - pub foliage_block_data_hash: Bytes32, - pub foliage_transaction_block_hash: Bytes32, -} - -#[derive(ChiaSerial, Clone, PartialEq, Eq, Serialize, Deserialize, Debug)] -pub struct FarmingInfo { - pub challenge_hash: Bytes32, - pub sp_hash: Bytes32, - pub timestamp: u64, - pub passed: u32, - pub proofs: u32, - pub total_plots: u32, - pub lookup_time: u64, -} - -#[derive(ChiaSerial, Clone, PartialEq, Eq, Serialize, Deserialize, Debug)] -pub struct SignedValues { - pub quality_string: Bytes32, - pub foliage_block_data_signature: Bytes96, - pub foliage_transaction_block_signature: Bytes96, -} diff --git a/clients/src/protocols/shared.rs b/clients/src/protocols/shared.rs deleted file mode 100644 index 99383b7..0000000 --- a/clients/src/protocols/shared.rs +++ /dev/null @@ -1,125 +0,0 @@ -use dg_xch_macros::ChiaSerial; -use log::error; -use rustls::client::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier}; -use rustls::{Certificate, DigitallySignedStruct, PrivateKey, ServerName}; -use rustls_pemfile::{certs, read_one, Item}; -use serde::{Deserialize, Serialize}; -use std::fs::File; -use std::io::{BufReader, Error, ErrorKind}; -use std::iter; -use std::time::SystemTime; - -pub const PROTOCOL_VERSION: &str = "0.0.34"; -pub const SOFTWARE_VERSION: &str = "dg_xch_utils_1_0_0"; - -pub enum Capability { - Base = 1, - BlockHeaders = 2, - RateLimitsV2 = 3, - NoneResponse = 4, -} - -#[derive(ChiaSerial, Serialize, Deserialize, Debug, Clone)] -pub struct Handshake { - pub network_id: String, - pub protocol_version: String, - pub software_version: String, - pub server_port: u16, - pub node_type: u8, - pub capabilities: Vec<(u16, String)>, -} - -pub const CAPABILITIES: [(u16, &str); 3] = [ - (Capability::Base as u16, "1"), - (Capability::BlockHeaders as u16, "1"), - (Capability::RateLimitsV2 as u16, "1"), - //(Capability::NoneResponse as u16, "1"), //This is not currently supported, Causes the Fullnode to close the connection -]; - -pub struct NoCertificateVerification; - -impl ServerCertVerifier for NoCertificateVerification { - fn verify_server_cert( - &self, - _end_entity: &Certificate, - _intermediates: &[Certificate], - _server_name: &ServerName, - _scts: &mut dyn Iterator, - _ocsp_response: &[u8], - _now: SystemTime, - ) -> Result { - Ok(ServerCertVerified::assertion()) - } - - fn verify_tls12_signature( - &self, - _message: &[u8], - _cert: &Certificate, - _dss: &DigitallySignedStruct, - ) -> Result { - Ok(HandshakeSignatureValid::assertion()) - } - - fn verify_tls13_signature( - &self, - _message: &[u8], - _cert: &Certificate, - _dss: &DigitallySignedStruct, - ) -> Result { - Ok(HandshakeSignatureValid::assertion()) - } -} - -pub fn load_certs(filename: &str) -> Result, Error> { - let cert_file = File::open(filename)?; - let mut reader = BufReader::new(cert_file); - let certs = certs(&mut reader)?; - Ok(certs.into_iter().map(Certificate).collect()) -} - -pub fn load_certs_from_bytes(bytes: &[u8]) -> Result, Error> { - let mut reader = BufReader::new(bytes); - let certs = certs(&mut reader)?; - Ok(certs.into_iter().map(Certificate).collect()) -} - -pub fn load_private_key(filename: &str) -> Result { - let keyfile = File::open(filename)?; - let mut reader = BufReader::new(keyfile); - for item in iter::from_fn(|| read_one(&mut reader).transpose()) { - match item? { - Item::X509Certificate(_) => error!("Found Certificate, not Private Key"), - Item::RSAKey(key) => { - return Ok(PrivateKey(key)); - } - Item::PKCS8Key(key) => { - return Ok(PrivateKey(key)); - } - Item::ECKey(key) => { - return Ok(PrivateKey(key)); - } - _ => error!("Unknown Item while loading private key"), - } - } - Err(Error::new(ErrorKind::NotFound, "Private Key Not Found")) -} - -pub fn load_private_key_from_bytes(bytes: &[u8]) -> Result { - let mut reader = BufReader::new(bytes); - for item in iter::from_fn(|| read_one(&mut reader).transpose()) { - match item? { - Item::X509Certificate(_) => error!("Found Certificate, not Private Key"), - Item::RSAKey(key) => { - return Ok(PrivateKey(key)); - } - Item::PKCS8Key(key) => { - return Ok(PrivateKey(key)); - } - Item::ECKey(key) => { - return Ok(PrivateKey(key)); - } - _ => error!("Unknown Item while loading private key"), - } - } - Err(Error::new(ErrorKind::NotFound, "Private Key Not Found")) -} diff --git a/clients/src/rpc/full_node.rs b/clients/src/rpc/full_node.rs index 4f6287d..cdab2c8 100644 --- a/clients/src/rpc/full_node.rs +++ b/clients/src/rpc/full_node.rs @@ -3,8 +3,6 @@ use crate::api::responses::{ BlockCountMetricsResp, CoinHintsResp, CoinSpendMapResp, FeeEstimateResp, HintedAdditionsAndRemovalsResp, MempoolItemAryResp, PaginatedCoinRecordAryResp, }; -use crate::protocols::full_node::BlockCountMetrics; -use crate::protocols::full_node::FeeEstimate; use async_trait::async_trait; use dg_xch_core::blockchain::block_record::BlockRecord; use dg_xch_core::blockchain::blockchain_state::BlockchainState; @@ -18,6 +16,8 @@ use dg_xch_core::blockchain::sized_bytes::Bytes32; use dg_xch_core::blockchain::spend_bundle::SpendBundle; use dg_xch_core::blockchain::tx_status::TXStatus; use dg_xch_core::blockchain::unfinished_block::UnfinishedBlock; +use dg_xch_core::protocols::full_node::BlockCountMetrics; +use dg_xch_core::protocols::full_node::FeeEstimate; use reqwest::Client; use serde_json::{json, Map}; use std::collections::HashMap; diff --git a/clients/src/rpc/mod.rs b/clients/src/rpc/mod.rs index 143fe53..a9d1b3a 100644 --- a/clients/src/rpc/mod.rs +++ b/clients/src/rpc/mod.rs @@ -1,8 +1,8 @@ pub mod full_node; pub mod wallet; -use crate::protocols::shared::NoCertificateVerification; -use crate::protocols::shared::{load_certs, load_private_key}; +use dg_xch_core::protocols::shared::NoCertificateVerification; +use dg_xch_core::ssl::{load_certs, load_private_key}; use reqwest::{Client, ClientBuilder}; use rustls::ClientConfig; use serde::de::DeserializeOwned; diff --git a/clients/src/websocket/daemon.rs b/clients/src/websocket/daemon.rs deleted file mode 100644 index f037010..0000000 --- a/clients/src/websocket/daemon.rs +++ /dev/null @@ -1,57 +0,0 @@ - -pub struct ChiaMessageFilter { - pub destination: Option, - pub command: Option, - pub request_id: Option, - pub origin: Option, -} -impl ChiaMessageFilter { - pub fn matches(&self, msg: Arc) -> bool { - if let Some(s) = &self.destination { - if *s != msg.destination { - return false; - } - } - if let Some(s) = &self.command { - if *s != msg.command { - return false; - } - } - if let Some(s) = &self.request_id { - if *s != msg.request_id { - return false; - } - } - if let Some(s) = &self.origin { - if *s != msg.origin { - return false; - } - } - true - } -} - -#[derive(ChiaSerial, Clone, PartialEq, Eq, Serialize, Deserialize, Debug)] -struct RegisterMessage { - service: String, -} - -pub struct ChiaMessageHandler { - filter: ChiaMessageFilter, - handle: Arc, -} -impl ChiaMessageHandler { - pub fn new(filter: ChiaMessageFilter, handle: Arc) -> Self { - ChiaMessageHandler { filter, handle } - } -} - -#[derive(ChiaSerial, Clone, PartialEq, Eq, Serialize, Deserialize, Debug)] -pub struct ChiaMessage { - pub destination: String, - pub command: String, - pub request_id: String, - pub origin: String, - pub ack: bool, - pub data: Value, -} \ No newline at end of file diff --git a/clients/src/websocket/farmer.rs b/clients/src/websocket/farmer.rs deleted file mode 100644 index dbbef73..0000000 --- a/clients/src/websocket/farmer.rs +++ /dev/null @@ -1,68 +0,0 @@ -use crate::websocket::{ - get_client, get_client_generated_tls, get_client_tls, perform_handshake, Client, - ClientSSLConfig, NodeType, -}; -use std::collections::HashMap; -use std::io::{Error, ErrorKind}; -use std::sync::atomic::AtomicBool; -use std::sync::Arc; -use tokio::sync::Mutex; -use tokio::task::JoinHandle; - -pub struct FarmerClient { - pub client: Arc>, - handle: JoinHandle<()>, -} -impl FarmerClient { - pub async fn new_ssl_generate( - host: &str, - port: u16, - network_id: &str, - additional_headers: &Option>, - run: Arc, - ) -> Result { - let (client, mut stream) = get_client_generated_tls(host, port, additional_headers).await?; - let handle = tokio::spawn(async move { stream.run(run).await }); - let client = Arc::new(Mutex::new(client)); - perform_handshake(client.clone(), network_id, port, NodeType::Farmer).await?; - Ok(FarmerClient { client, handle }) - } - pub async fn new_ssl( - host: &str, - port: u16, - ssl_info: ClientSSLConfig<'_>, - network_id: &str, - additional_headers: &Option>, - run: Arc, - ) -> Result { - let (client, mut stream) = get_client_tls(host, port, ssl_info, additional_headers).await?; - let handle = tokio::spawn(async move { stream.run(run).await }); - let client = Arc::new(Mutex::new(client)); - perform_handshake(client.clone(), network_id, port, NodeType::Farmer).await?; - Ok(FarmerClient { client, handle }) - } - pub async fn new( - host: &str, - port: u16, - network_id: &str, - additional_headers: &Option>, - run: Arc, - ) -> Result { - let (client, mut stream) = get_client(host, port, additional_headers).await?; - let handle = tokio::spawn(async move { stream.run(run).await }); - let client = Arc::new(Mutex::new(client)); - perform_handshake(client.clone(), network_id, port, NodeType::Farmer).await?; - Ok(FarmerClient { client, handle }) - } - - pub async fn join(self) -> Result<(), Error> { - self.handle - .await - .map_err(|e| Error::new(ErrorKind::Other, format!("Failed to join farmer: {:?}", e)))?; - self.client.lock().await.shutdown().await - } - - pub fn is_closed(&self) -> bool { - self.handle.is_finished() - } -} diff --git a/clients/src/websocket/farmer/mod.rs b/clients/src/websocket/farmer/mod.rs new file mode 100644 index 0000000..53ef127 --- /dev/null +++ b/clients/src/websocket/farmer/mod.rs @@ -0,0 +1,90 @@ +use crate::websocket::farmer::request_signed_values::RequestSignedValuesHandle; +use crate::websocket::farmer::signage_point::NewSignagePointHandle; +use crate::websocket::{WsClient, WsClientConfig}; +use dg_xch_core::consensus::constants::{ConsensusConstants, CONSENSUS_CONSTANTS_MAP, MAINNET}; +use dg_xch_core::protocols::farmer::FarmerSharedState; +use dg_xch_core::protocols::{ + ChiaMessageFilter, ChiaMessageHandler, NodeType, ProtocolMessageTypes, +}; +use std::collections::HashMap; +use std::io::Error; +use std::sync::atomic::AtomicBool; +use std::sync::Arc; +use tokio::sync::Mutex; +use uuid::Uuid; + +pub mod request_signed_values; +pub mod signage_point; + +pub struct FarmerClient { + pub client: WsClient, + pub shared_state: Arc, +} +impl FarmerClient { + pub async fn new( + client_config: Arc, + shared_state: Arc, + run: Arc, + ) -> Result { + let constants = CONSENSUS_CONSTANTS_MAP + .get(&client_config.network_id) + .unwrap_or(&MAINNET); + let handles = Arc::new(Mutex::new(handles(constants, shared_state.clone()))); + let client = WsClient::new(client_config, NodeType::Farmer, handles, run.clone()).await?; + Ok(FarmerClient { + client, + shared_state, + }) + } + + pub async fn join(self) -> Result<(), Error> { + self.client.connection.lock().await.shutdown().await?; + self.client.join().await + } + + pub fn is_closed(&self) -> bool { + self.client.handle.is_finished() + } +} + +fn handles( + constants: &'static ConsensusConstants, + shared_state: Arc, +) -> HashMap> { + HashMap::from([ + ( + Uuid::new_v4(), + Arc::new(ChiaMessageHandler::new( + Arc::new(ChiaMessageFilter { + msg_type: Some(ProtocolMessageTypes::NewSignagePoint), + id: None, + }), + Arc::new(NewSignagePointHandle { + constants, + harvester_peers: shared_state.harvester_peers.clone(), + signage_points: shared_state.signage_points.clone(), + pool_state: shared_state.pool_state.clone(), + cache_time: shared_state.cache_time.clone(), + running_state: shared_state.running_state.clone(), + most_recent_sp: shared_state.most_recent_sp.clone(), + #[cfg(feature = "metrics")] + metrics: shared_state.metrics.clone(), + }), + )), + ), + ( + Uuid::new_v4(), + Arc::new(ChiaMessageHandler::new( + Arc::new(ChiaMessageFilter { + msg_type: Some(ProtocolMessageTypes::RequestSignedValues), + id: None, + }), + Arc::new(RequestSignedValuesHandle { + quality_to_identifiers: shared_state.quality_to_identifiers.clone(), + recent_errors: Arc::new(Default::default()), + harvester_peers: shared_state.harvester_peers.clone(), + }), + )), + ), + ]) +} diff --git a/clients/src/websocket/farmer/request_signed_values.rs b/clients/src/websocket/farmer/request_signed_values.rs new file mode 100644 index 0000000..4c08ce0 --- /dev/null +++ b/clients/src/websocket/farmer/request_signed_values.rs @@ -0,0 +1,75 @@ +use async_trait::async_trait; +use dg_xch_core::blockchain::sized_bytes::Bytes32; +use dg_xch_core::protocols::error::RecentErrors; +use dg_xch_core::protocols::farmer::{FarmerIdentifier, RequestSignedValues}; +use dg_xch_core::protocols::harvester::RequestSignatures; +use dg_xch_core::protocols::{ChiaMessage, MessageHandler, PeerMap, ProtocolMessageTypes}; +use dg_xch_serialize::ChiaSerialize; +use std::collections::HashMap; +use std::io::{Cursor, Error, ErrorKind}; +use std::sync::Arc; +use tokio::sync::Mutex; +use tokio_tungstenite::tungstenite::Message; + +pub struct RequestSignedValuesHandle { + pub quality_to_identifiers: Arc>>, + pub recent_errors: Arc>>, + pub harvester_peers: PeerMap, +} +#[async_trait] +impl MessageHandler for RequestSignedValuesHandle { + async fn handle( + &self, + msg: Arc, + _peer_id: Arc, + _peers: PeerMap, + ) -> Result<(), Error> { + let mut cursor = Cursor::new(&msg.data); + let request = RequestSignedValues::from_bytes(&mut cursor)?; + if let Some(identifier) = self + .quality_to_identifiers + .lock() + .await + .get(&request.quality_string) + { + if let Some(peer) = self + .harvester_peers + .lock() + .await + .get(&identifier.peer_node_id) + { + let _ = peer + .websocket + .lock() + .await + .send(Message::Binary( + ChiaMessage::new( + ProtocolMessageTypes::RequestSignatures, + &RequestSignatures { + plot_identifier: identifier.plot_identifier.clone(), + challenge_hash: identifier.challenge_hash, + sp_hash: identifier.sp_hash, + messages: vec![ + request.foliage_block_data_hash, + request.foliage_transaction_block_hash, + ], + }, + None, + ) + .to_bytes(), + )) + .await; + } + Ok(()) + } else { + self.recent_errors + .lock() + .await + .add(format!("Do not have quality {}", &request.quality_string)); + Err(Error::new( + ErrorKind::NotFound, + format!("Do not have quality {}", &request.quality_string), + )) + } + } +} diff --git a/clients/src/websocket/farmer/signage_point.rs b/clients/src/websocket/farmer/signage_point.rs new file mode 100644 index 0000000..928f6bb --- /dev/null +++ b/clients/src/websocket/farmer/signage_point.rs @@ -0,0 +1,155 @@ +use async_trait::async_trait; +use dg_xch_core::blockchain::proof_of_space::calculate_prefix_bits; +use dg_xch_core::blockchain::sized_bytes::Bytes32; +use dg_xch_core::consensus::constants::ConsensusConstants; +use dg_xch_core::consensus::pot_iterations::POOL_SUB_SLOT_ITERS; +#[cfg(feature = "metrics")] +use dg_xch_core::protocols::farmer::FarmerMetrics; +use dg_xch_core::protocols::farmer::{ + FarmerPoolState, FarmerRunningState, MostRecentSignagePoint, NewSignagePoint, +}; +use dg_xch_core::protocols::harvester::{NewSignagePointHarvester, PoolDifficulty}; +use dg_xch_core::protocols::{ + ChiaMessage, MessageHandler, NodeType, PeerMap, ProtocolMessageTypes, SocketPeer, +}; +use dg_xch_serialize::ChiaSerialize; +use log::{debug, info, warn}; +use std::collections::HashMap; +use std::io::{Cursor, Error}; +use std::sync::Arc; +use std::time::Instant; +use tokio::sync::Mutex; +use tokio_tungstenite::tungstenite::Message; + +pub struct NewSignagePointHandle { + pub constants: &'static ConsensusConstants, + pub harvester_peers: PeerMap, + pub signage_points: Arc>>>, + pub pool_state: Arc>>, + pub cache_time: Arc>>, + pub running_state: Arc>, + pub most_recent_sp: Arc>, + #[cfg(feature = "metrics")] + pub metrics: Arc>>, +} +#[async_trait] +impl MessageHandler for NewSignagePointHandle { + async fn handle( + &self, + msg: Arc, + _peer_id: Arc, + _peers: PeerMap, + ) -> Result<(), Error> { + let mut cursor = Cursor::new(&msg.data); + let sp = NewSignagePoint::from_bytes(&mut cursor)?; + let mut pool_difficulties = vec![]; + for (p2_singleton_puzzle_hash, pool_dict) in self.pool_state.lock().await.iter() { + if let Some(config) = &pool_dict.pool_config { + if config.pool_url.is_empty() { + //Self Pooling + continue; + } + if let Some(difficulty) = pool_dict.current_difficulty { + debug!("Setting Difficulty for pool: {}", difficulty); + pool_difficulties.push(PoolDifficulty { + difficulty, + sub_slot_iters: POOL_SUB_SLOT_ITERS, + pool_contract_puzzle_hash: *p2_singleton_puzzle_hash, + }) + } else { + warn!("No pool specific difficulty has been set for {p2_singleton_puzzle_hash}, check communication with the pool, skipping this signage point, pool: {}", &config.pool_url); + continue; + } + } + } + info!( + "New Signage Point({}): {:?}", + sp.signage_point_index, sp.challenge_hash + ); + let harvester_point = NewSignagePointHarvester { + challenge_hash: sp.challenge_hash, + difficulty: sp.difficulty, + sub_slot_iters: sp.sub_slot_iters, + signage_point_index: sp.signage_point_index, + sp_hash: sp.challenge_chain_sp, + pool_difficulties, + filter_prefix_bits: calculate_prefix_bits(self.constants, sp.peak_height), + }; + let msg = Message::Binary( + ChiaMessage::new( + ProtocolMessageTypes::NewSignagePointHarvester, + &harvester_point, + None, + ) + .to_bytes(), + ); + let peers: Vec> = self + .harvester_peers + .lock() + .await + .values() + .cloned() + .collect(); + for peer in peers { + if *peer.node_type.lock().await == NodeType::Harvester { + let _ = peer.websocket.lock().await.send(msg.clone()).await; + } + } + { + //Lock Scope + let mut signage_points = self.signage_points.lock().await; + if signage_points.get(&sp.challenge_chain_sp).is_none() { + signage_points.insert(sp.challenge_chain_sp, vec![]); + } + } + #[cfg(feature = "metrics")] + { + let now = Instant::now(); + let sums = self + .pool_state + .lock() + .await + .iter_mut() + .map(|(_, s)| { + s.points_acknowledged_24h + .retain(|(i, _)| now.duration_since(*i).as_secs() <= 60 * 60 * 24); + s.points_found_24h + .retain(|(i, _)| now.duration_since(*i).as_secs() <= 60 * 60 * 24); + ( + s.points_acknowledged_24h.iter().map(|(_, v)| *v).sum(), + s.points_found_24h.iter().map(|(_, v)| *v).sum(), + ) + }) + .collect::>(); + if let Some(r) = self.metrics.lock().await.as_mut() { + if let Some(c) = &mut r.points_acknowledged_24h { + c.set(sums.iter().map(|(v, _)| *v).sum()); + } + if let Some(c) = &mut r.points_found_24h { + c.set(sums.iter().map(|(_, v)| *v).sum()); + } + if let Some(c) = &mut r.last_signage_point_index { + c.set(sp.signage_point_index as u64); + } + } + } + if let Some(sps) = self + .signage_points + .lock() + .await + .get_mut(&sp.challenge_chain_sp) + { + sps.push(sp.clone()); + } + *self.most_recent_sp.lock().await = MostRecentSignagePoint { + hash: sp.challenge_chain_sp, + index: sp.signage_point_index, + timestamp: Instant::now(), + }; + self.cache_time + .lock() + .await + .insert(sp.challenge_chain_sp, Instant::now()); + Ok(()) + } +} diff --git a/clients/src/websocket/full_node.rs b/clients/src/websocket/full_node.rs deleted file mode 100644 index fe76f4e..0000000 --- a/clients/src/websocket/full_node.rs +++ /dev/null @@ -1,53 +0,0 @@ -use crate::websocket::{ - get_client, get_client_tls, perform_handshake, Client, ClientSSLConfig, NodeType, -}; -use std::collections::HashMap; -use std::io::{Error, ErrorKind}; -use std::sync::atomic::AtomicBool; -use std::sync::Arc; -use tokio::sync::Mutex; -use tokio::task::JoinHandle; - -pub struct FullnodeClient { - pub client: Arc>, - handle: JoinHandle<()>, -} -impl FullnodeClient { - pub async fn new( - host: &str, - port: u16, - network_id: &str, - additional_headers: &Option>, - run: Arc, - ) -> Result { - let (client, mut stream) = get_client(host, port, additional_headers).await?; - let handle = tokio::spawn(async move { stream.run(run).await }); - let client = Arc::new(Mutex::new(client)); - perform_handshake(client.clone(), network_id, port, NodeType::FullNode).await?; - Ok(FullnodeClient { client, handle }) - } - pub async fn new_ssl( - host: &str, - port: u16, - ssl_info: ClientSSLConfig<'_>, - network_id: &str, - additional_headers: &Option>, - run: Arc, - ) -> Result { - let (client, mut stream) = get_client_tls(host, port, ssl_info, additional_headers).await?; - let handle = tokio::spawn(async move { stream.run(run).await }); - let client = Arc::new(Mutex::new(client)); - perform_handshake(client.clone(), network_id, port, NodeType::FullNode).await?; - Ok(FullnodeClient { client, handle }) - } - - pub async fn join(self) -> Result<(), Error> { - self.handle.await.map_err(|e| { - Error::new( - ErrorKind::Other, - format!("Failed to join fullnode: {:?}", e), - ) - })?; - self.client.lock().await.shutdown().await - } -} diff --git a/clients/src/websocket/full_node/mod.rs b/clients/src/websocket/full_node/mod.rs new file mode 100644 index 0000000..7fcccf3 --- /dev/null +++ b/clients/src/websocket/full_node/mod.rs @@ -0,0 +1,35 @@ +use crate::websocket::{WsClient, WsClientConfig}; +use dg_xch_core::protocols::{ChiaMessageHandler, NodeType}; +use std::collections::HashMap; +use std::io::Error; +use std::sync::atomic::AtomicBool; +use std::sync::Arc; +use tokio::sync::Mutex; +use uuid::Uuid; + +pub struct FullnodeClient { + pub client: WsClient, +} +impl FullnodeClient { + pub async fn new( + client_config: Arc, + run: Arc, + ) -> Result { + let handles = Arc::new(Mutex::new(handles())); + let client = WsClient::new(client_config, NodeType::FullNode, handles, run).await?; + Ok(FullnodeClient { client }) + } + + pub async fn join(self) -> Result<(), Error> { + self.client.connection.lock().await.shutdown().await?; + self.client.join().await + } + + pub fn is_closed(&self) -> bool { + self.client.handle.is_finished() + } +} + +fn handles() -> HashMap> { + HashMap::from([]) +} diff --git a/clients/src/websocket/harvester.rs b/clients/src/websocket/harvester.rs deleted file mode 100644 index 87e7f64..0000000 --- a/clients/src/websocket/harvester.rs +++ /dev/null @@ -1,58 +0,0 @@ -use crate::websocket::{ - get_client, get_client_tls, perform_handshake, Client, ClientSSLConfig, NodeType, -}; -use log::debug; -use std::collections::HashMap; -use std::io::{Error, ErrorKind}; -use std::sync::atomic::AtomicBool; -use std::sync::Arc; -use tokio::sync::Mutex; -use tokio::task::JoinHandle; - -pub struct HarvesterClient { - pub client: Arc>, - handle: JoinHandle<()>, -} -impl HarvesterClient { - pub async fn new( - host: &str, - port: u16, - network_id: &str, - additional_headers: &Option>, - run: Arc, - ) -> Result { - let (client, mut stream) = get_client(host, port, additional_headers).await?; - let handle = tokio::spawn(async move { stream.run(run).await }); - let client = Arc::new(Mutex::new(client)); - perform_handshake(client.clone(), network_id, port, NodeType::Harvester).await?; - Ok(HarvesterClient { client, handle }) - } - pub async fn new_ssl( - host: &str, - port: u16, - ssl_info: ClientSSLConfig<'_>, - network_id: &str, - additional_headers: &Option>, - run: Arc, - ) -> Result { - debug!("Starting Harvester SSL Connection"); - let (client, mut stream) = get_client_tls(host, port, ssl_info, additional_headers).await?; - debug!("Spawning Stream Handler for Harvester SSL Connection"); - let handle = tokio::spawn(async move { stream.run(run).await }); - debug!("Performing Handshake"); - let client = Arc::new(Mutex::new(client)); - perform_handshake(client.clone(), network_id, port, NodeType::Harvester).await?; - debug!("Harvester Handshake Complete"); - Ok(HarvesterClient { client, handle }) - } - - pub async fn join(self) -> Result<(), Error> { - self.handle.await.map_err(|e| { - Error::new( - ErrorKind::Other, - format!("Failed to join harvester: {:?}", e), - ) - })?; - self.client.lock().await.shutdown().await - } -} diff --git a/clients/src/websocket/harvester/harvester_handshake.rs b/clients/src/websocket/harvester/harvester_handshake.rs new file mode 100644 index 0000000..35adadf --- /dev/null +++ b/clients/src/websocket/harvester/harvester_handshake.rs @@ -0,0 +1,52 @@ +use async_trait::async_trait; +use dg_xch_core::blockchain::sized_bytes::Bytes32; +use dg_xch_core::protocols::harvester::{HarvesterHandshake, HarvesterState}; +use dg_xch_core::protocols::{ChiaMessage, MessageHandler, PeerMap}; +use dg_xch_pos::PlotManagerAsync; +use dg_xch_serialize::ChiaSerialize; +use log::{debug, info, warn}; +use std::io::{Cursor, Error}; +use std::sync::Arc; +use tokio::sync::Mutex; + +pub struct HarvesterHandshakeHandle { + pub plot_manager: Arc>, + pub harvester_state: Arc>, +} +#[async_trait] +impl MessageHandler for HarvesterHandshakeHandle { + async fn handle( + &self, + msg: Arc, + _peer_id: Arc, + _peers: PeerMap, + ) -> Result<(), Error> { + let mut cursor = Cursor::new(msg.data.clone()); + let handshake = HarvesterHandshake::from_bytes(&mut cursor)?; + info!("Handshake from farmer: {:?}", handshake); + if handshake.farmer_public_keys.is_empty() && handshake.pool_public_keys.is_empty() { + warn!("Farmer Failed to send keys"); + } else { + self.plot_manager + .lock() + .await + .set_public_keys(handshake.farmer_public_keys, handshake.pool_public_keys); + } + info!("Got Keys, Loading Plots."); + match self + .plot_manager + .lock() + .await + .load_plots(self.harvester_state.clone()) + .await + { + Ok(_) => { + debug!("Done Loading Plots"); + } + Err(e) => { + debug!("Error loading plots: {:?}", e); + } + } + Ok(()) + } +} diff --git a/clients/src/websocket/harvester/mod.rs b/clients/src/websocket/harvester/mod.rs new file mode 100644 index 0000000..78c494f --- /dev/null +++ b/clients/src/websocket/harvester/mod.rs @@ -0,0 +1,99 @@ +use crate::websocket::harvester::harvester_handshake::HarvesterHandshakeHandle; +use crate::websocket::harvester::new_signage_point_harvester::NewSignagePointHarvesterHandle; +use crate::websocket::harvester::request_signatures::RequestSignaturesHandle; +use crate::websocket::{WsClient, WsClientConfig}; +use dg_xch_core::consensus::constants::{ConsensusConstants, CONSENSUS_CONSTANTS_MAP, MAINNET}; +use dg_xch_core::protocols::harvester::HarvesterState; +use dg_xch_core::protocols::{ + ChiaMessageFilter, ChiaMessageHandler, NodeType, ProtocolMessageTypes, +}; +use dg_xch_pos::PlotManagerAsync; +use std::collections::HashMap; +use std::io::Error; +use std::sync::atomic::AtomicBool; +use std::sync::Arc; +use tokio::sync::Mutex; +use uuid::Uuid; +pub mod harvester_handshake; +pub mod new_signage_point_harvester; +pub mod request_signatures; + +pub struct HarvesterClient { + pub client: WsClient, +} +impl HarvesterClient { + pub async fn new( + client_config: Arc, + plot_manager: Arc>, + plots_ready: Arc, + harvester_state: Arc>, + run: Arc, + ) -> Result { + let constants = CONSENSUS_CONSTANTS_MAP + .get(&client_config.network_id) + .unwrap_or(&MAINNET); + let handles = Arc::new(Mutex::new(handles( + constants, + plot_manager.clone(), + plots_ready, + harvester_state, + ))); + let client = WsClient::new(client_config, NodeType::Harvester, handles, run).await?; + Ok(HarvesterClient { client }) + } + + pub async fn join(self) -> Result<(), Error> { + self.client.join().await + } + + pub fn is_closed(&self) -> bool { + self.client.handle.is_finished() + } +} + +fn handles( + constants: &'static ConsensusConstants, + plot_manager: Arc>, + plots_ready: Arc, + harvester_state: Arc>, +) -> HashMap> { + HashMap::from([ + ( + Uuid::new_v4(), + Arc::new(ChiaMessageHandler::new( + Arc::new(ChiaMessageFilter { + msg_type: Some(ProtocolMessageTypes::HarvesterHandshake), + id: None, + }), + Arc::new(HarvesterHandshakeHandle { + plot_manager: plot_manager.clone(), + harvester_state, + }), + )), + ), + ( + Uuid::new_v4(), + Arc::new(ChiaMessageHandler::new( + Arc::new(ChiaMessageFilter { + msg_type: Some(ProtocolMessageTypes::NewSignagePointHarvester), + id: None, + }), + Arc::new(NewSignagePointHarvesterHandle { + constants, + plot_manager: plot_manager.clone(), + plots_ready, + }), + )), + ), + ( + Uuid::new_v4(), + Arc::new(ChiaMessageHandler::new( + Arc::new(ChiaMessageFilter { + msg_type: Some(ProtocolMessageTypes::RequestSignatures), + id: None, + }), + Arc::new(RequestSignaturesHandle { plot_manager }), + )), + ), + ]) +} diff --git a/clients/src/websocket/harvester/new_signage_point_harvester.rs b/clients/src/websocket/harvester/new_signage_point_harvester.rs new file mode 100644 index 0000000..542d84b --- /dev/null +++ b/clients/src/websocket/harvester/new_signage_point_harvester.rs @@ -0,0 +1,257 @@ +use async_trait::async_trait; +use dg_xch_core::blockchain::proof_of_space::{ + calculate_pos_challenge, passes_plot_filter, ProofBytes, ProofOfSpace, +}; +use dg_xch_core::blockchain::sized_bytes::Bytes32; +use dg_xch_core::consensus::constants::ConsensusConstants; +use dg_xch_core::consensus::pot_iterations::{ + calculate_iterations_quality, calculate_sp_interval_iters, +}; +use dg_xch_core::protocols::harvester::{NewProofOfSpace, NewSignagePointHarvester}; +use dg_xch_core::protocols::{ChiaMessage, MessageHandler, PeerMap, ProtocolMessageTypes}; +use dg_xch_pos::verifier::proof_to_bytes; +use dg_xch_pos::PlotManagerAsync; +use dg_xch_serialize::ChiaSerialize; +use futures_util::stream::FuturesUnordered; +use futures_util::StreamExt; +use hex::encode; +use log::{debug, error, info, trace, warn}; +use std::io::{Cursor, Error}; +use std::sync::atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering}; +use std::sync::Arc; +use std::time::Duration; +use tokio::sync::Mutex; +use tokio::time::timeout; +use tokio_tungstenite::tungstenite::Message; + +#[derive(Default)] +struct PlotCounts { + og_passed: Arc, + og_total: Arc, + pool_total: Arc, + pool_passed: Arc, + compressed_passed: Arc, + compressed_total: Arc, +} +pub struct NewSignagePointHarvesterHandle { + pub constants: &'static ConsensusConstants, + pub plot_manager: Arc>, + pub plots_ready: Arc, +} +#[async_trait] +impl MessageHandler for NewSignagePointHarvesterHandle { + async fn handle( + &self, + msg: Arc, + peer_id: Arc, + peers: PeerMap, + ) -> Result<(), Error> { + if !self.plots_ready.load(Ordering::Relaxed) { + info!("Plots Not Ready Yet, skipping"); + return Ok(()); + } + let mut cursor = Cursor::new(msg.data.clone()); + let harvester_point = NewSignagePointHarvester::from_bytes(&mut cursor)?; + trace!("{}", &harvester_point); + let plot_counts = Arc::new(PlotCounts::default()); + let harvester_point = Arc::new(harvester_point); + let constants = Arc::new(self.constants); + let mut jobs = FuturesUnordered::new(); + self.plot_manager.lock().await.plots().iter().map(|(path_info, plot_info)|{ + (path_info.clone(), plot_info.clone()) + }).for_each(|(path, plot_info)| { + let data_arc = harvester_point.clone(); + let constants_arc = constants.clone(); + let plot_counts = plot_counts.clone(); + let mut responses = vec![]; + let plot_handle = timeout(Duration::from_secs(15), tokio::spawn(async move { + let plot_id = plot_info.reader.header().id(); + let k = plot_info.reader.header().k(); + let memo = plot_info.reader.header().memo(); + let c_level = plot_info.reader.header().compression_level(); + if plot_info.pool_public_key.is_some(){ + plot_counts.og_total.fetch_add(1, Ordering::Relaxed); + } else if c_level > 0 { + plot_counts.compressed_total.fetch_add(1, Ordering::Relaxed); + } else { + plot_counts.pool_total.fetch_add(1, Ordering::Relaxed); + } + if passes_plot_filter( + data_arc.filter_prefix_bits, + &plot_id, + &data_arc.challenge_hash, + &data_arc.sp_hash, + ) { + if plot_info.pool_public_key.is_some() { + plot_counts.og_passed.fetch_add(1, Ordering::Relaxed); + } else if c_level > 0 { + plot_counts.compressed_passed.fetch_add(1, Ordering::Relaxed); + } else { + plot_counts.pool_passed.fetch_add(1, Ordering::Relaxed); + } + let sp_challenge_hash = calculate_pos_challenge( + &plot_id, + &data_arc.challenge_hash, + &data_arc.sp_hash, + ); + debug!("Starting Search for challenge {sp_challenge_hash} in plot {}", path.file_name); + let qualities = match plot_info + .reader + .fetch_qualities_for_challenge(sp_challenge_hash.as_ref()).await { + Ok(qualities) => { + qualities + } + Err(e) => { + debug!("Plot({:?}) - Error for Hash: {}", path.file_name, sp_challenge_hash); + return Err(e); + } + }; + if !qualities.is_empty() { + debug!("Plot: {} Qualities Found: {}", &path.file_name, qualities.len()); + let mut dif = data_arc.difficulty; + let mut sub_slot_iters = data_arc.sub_slot_iters; + let mut is_partial = false; + if let Some(pool_contract_puzzle_hash) = + &memo.pool_contract_puzzle_hash + { + if let Some(p_dif) = data_arc.pool_difficulties.iter().find(|p| { + p.pool_contract_puzzle_hash == *pool_contract_puzzle_hash + }) { + debug!("Setting Difficulty for pool: {}", dif); + dif = p_dif.difficulty; + sub_slot_iters = p_dif.sub_slot_iters; + is_partial = true; + } else if memo.pool_contract_puzzle_hash.is_some() { + warn!("Failed to find Pool Contract Difficulties for PH: {} ", pool_contract_puzzle_hash); + } + } + for (index, quality) in qualities.into_iter() { + let required_iters = calculate_iterations_quality( + constants_arc.difficulty_constant_factor, + &quality, + k, + dif, + &data_arc.sp_hash, + ); + if let Ok(sp_interval_iters) = + calculate_sp_interval_iters(&constants_arc, sub_slot_iters) + { + if required_iters < sp_interval_iters { + info!("Plot: {}, Passed Required Iterations, Loading Index: {}", path.file_name, index); + match plot_info.reader.fetch_ordered_proof(index).await { + Ok(proof) => { + let proof_bytes = proof_to_bytes(&proof); + debug!( + "File: {:?} Plot ID: {}, challenge: {sp_challenge_hash}, Quality Str: {}, proof: {:?}", + path, + &plot_id, + encode(quality.to_bytes()), + encode(&proof_bytes) + ); + responses.push(( + quality, + ProofOfSpace { + challenge: sp_challenge_hash, + pool_contract_puzzle_hash: plot_info + .pool_contract_puzzle_hash, + plot_public_key: plot_info + .plot_public_key, + pool_public_key: plot_info + .pool_public_key, + proof: ProofBytes::from(proof_bytes), + size: k, + }, + (is_partial, c_level) + )); + } + Err(e) => { + error!("Failed to read Proof: {:?}", e); + } + } + } else { + debug!( + "Not Enough Iterations: {} > {}", + required_iters, sp_interval_iters + ); + } + } + } + } + } + Ok((path.clone(), responses)) + })); + jobs.push(plot_handle); + }); + let proofs = AtomicU64::new(0); + let nft_partials = AtomicU64::new(0); + let compressed_partials = AtomicU64::new(0); + while let Some(timeout_result) = jobs.next().await { + match timeout_result { + Ok(join_result) => match join_result { + Ok(read_result) => match read_result { + Ok((path, responses)) => { + if let Some(client) = peers.lock().await.get(&peer_id).cloned() { + for (quality, proof, (is_partial, c_level)) in responses { + let _ = client + .websocket + .lock() + .await + .send(Message::Binary( + ChiaMessage::new( + ProtocolMessageTypes::NewProofOfSpace, + &NewProofOfSpace { + challenge_hash: harvester_point.challenge_hash, + sp_hash: harvester_point.sp_hash, + plot_identifier: encode(quality.to_bytes()) + + path.file_name.as_str(), + proof, + signage_point_index: harvester_point + .signage_point_index, + }, + None, + ) + .to_bytes(), + )) + .await; + if is_partial { + if c_level > 0 { + compressed_partials.fetch_add(1, Ordering::Relaxed); + } else { + nft_partials.fetch_add(1, Ordering::Relaxed); + } + } else { + proofs.fetch_add(1, Ordering::Relaxed); + } + } + } else { + error!("No Connection to send Proof"); + } + } + Err(e) => { + debug!("Failed to read plot: {:?}", e); + } + }, + Err(e) => { + error!("Failed to join reader thread: {:?}", e); + } + }, + Err(e) => { + error!("Failed to read qualities due to Timeout: {:?}", e); + } + } + } + info!( + "Passed Filter - OG: {}/{}. NFT: {}/{}. Compressed: {}/{}. Proofs Found: {}. Partials Found: NFT({}), Compressed({})", + plot_counts.og_passed.load(Ordering::Relaxed), + plot_counts.og_total.load(Ordering::Relaxed), + plot_counts.pool_passed.load(Ordering::Relaxed), + plot_counts.pool_total.load(Ordering::Relaxed), + plot_counts.compressed_passed.load(Ordering::Relaxed), + plot_counts.compressed_total.load(Ordering::Relaxed), + proofs.load(Ordering::Relaxed), + nft_partials.load(Ordering::Relaxed), + compressed_partials.load(Ordering::Relaxed), + ); + Ok(()) + } +} diff --git a/clients/src/websocket/harvester/request_signatures.rs b/clients/src/websocket/harvester/request_signatures.rs new file mode 100644 index 0000000..63a6c5a --- /dev/null +++ b/clients/src/websocket/harvester/request_signatures.rs @@ -0,0 +1,85 @@ +use async_trait::async_trait; +use blst::min_pk::{PublicKey, SecretKey}; +use dg_xch_core::blockchain::proof_of_space::generate_plot_public_key; +use dg_xch_core::blockchain::sized_bytes::Bytes32; +use dg_xch_core::clvm::bls_bindings::sign_prepend; +use dg_xch_core::protocols::harvester::{RequestSignatures, RespondSignatures}; +use dg_xch_core::protocols::{ChiaMessage, MessageHandler, PeerMap, ProtocolMessageTypes}; +use dg_xch_keys::master_sk_to_local_sk; +use dg_xch_pos::{PathInfo, PlotManagerAsync}; +use dg_xch_serialize::ChiaSerialize; +use log::{debug, error}; +use std::io::{Cursor, Error, ErrorKind}; +use std::sync::Arc; +use tokio::sync::Mutex; +use tokio_tungstenite::tungstenite::Message; + +pub struct RequestSignaturesHandle { + pub plot_manager: Arc>, +} +#[async_trait] +impl MessageHandler for RequestSignaturesHandle { + async fn handle( + &self, + msg: Arc, + peer_id: Arc, + peers: PeerMap, + ) -> Result<(), Error> { + debug!("{:?}", msg.msg_type); + let mut cursor = Cursor::new(msg.data.clone()); + let request_signatures = RequestSignatures::from_bytes(&mut cursor)?; + let file_name = request_signatures.plot_identifier.split_at(64).1; + let memo = match self.plot_manager.lock().await.plots().get(&PathInfo { + path: Default::default(), + file_name: file_name.to_string(), + }) { + None => { + debug!("Failed to find plot info for plot: {}", file_name); + return Err(Error::new( + ErrorKind::NotFound, + format!("Failed to find plot info for plot: {}", file_name), + )); + } + Some(info) => *info.reader.header().memo(), + }; + let local_master_secret = SecretKey::from_bytes(memo.local_master_secret_key.as_ref()) + .map_err(|e| Error::new(ErrorKind::InvalidInput, format!("{:?}", e)))?; + let local_sk = master_sk_to_local_sk(&local_master_secret)?; + let agg_pk = generate_plot_public_key( + &local_sk.sk_to_pk(), + &PublicKey::from_bytes(memo.farmer_public_key.as_ref()) + .map_err(|e| Error::new(ErrorKind::InvalidInput, format!("{:?}", e)))?, + memo.pool_contract_puzzle_hash.is_some(), + )?; + let mut message_signatures = vec![]; + for msg in request_signatures.messages { + let sig = sign_prepend(&local_sk, msg.as_ref(), &agg_pk); + message_signatures.push((msg, sig.to_bytes().into())); + } + if let Some(peer) = peers.lock().await.get(peer_id.as_ref()).cloned() { + let _ = peer + .websocket + .lock() + .await + .send(Message::Binary( + ChiaMessage::new( + ProtocolMessageTypes::RespondSignatures, + &RespondSignatures { + plot_identifier: request_signatures.plot_identifier, + challenge_hash: request_signatures.challenge_hash, + sp_hash: request_signatures.sp_hash, + local_pk: local_sk.sk_to_pk().to_bytes().into(), + farmer_pk: memo.farmer_public_key, + message_signatures, + }, + msg.id, + ) + .to_bytes(), + )) + .await; + } else { + error!("Failed to find client in PeerMap"); + } + Ok(()) + } +} diff --git a/clients/src/websocket/mod.rs b/clients/src/websocket/mod.rs index 3bdd9d1..c6025fd 100644 --- a/clients/src/websocket/mod.rs +++ b/clients/src/websocket/mod.rs @@ -3,81 +3,41 @@ pub mod full_node; pub mod harvester; pub mod wallet; -use crate::protocols::shared::{ - load_certs, load_certs_from_bytes, load_private_key, load_private_key_from_bytes, Handshake, - NoCertificateVerification, CAPABILITIES, PROTOCOL_VERSION, SOFTWARE_VERSION, -}; -use crate::protocols::ProtocolMessageTypes; use async_trait::async_trait; -use dashmap::DashMap; -use dg_xch_core::ssl::{generate_ca_signed_cert_data, CHIA_CA_CRT, CHIA_CA_KEY}; -use dg_xch_macros::ChiaSerial; -use dg_xch_serialize::ChiaSerialize; -use futures_util::stream::{SplitSink, SplitStream}; -use futures_util::{SinkExt, StreamExt, TryFutureExt}; -use hyper::upgrade::Upgraded; -use hyper_util::rt::TokioIo; -use log::{debug, error, info, trace}; +use dg_xch_core::blockchain::sized_bytes::{Bytes32, SizedBytes}; +use dg_xch_core::protocols::shared::{ + Handshake, NoCertificateVerification, CAPABILITIES, PROTOCOL_VERSION, +}; +use dg_xch_core::protocols::{ + ChiaMessage, ChiaMessageFilter, ChiaMessageHandler, MessageHandler, NodeType, SocketPeer, + WebsocketConnection, +}; +use dg_xch_core::protocols::{PeerMap, ProtocolMessageTypes, WebsocketMsgStream}; +use dg_xch_core::ssl::{ + generate_ca_signed_cert_data, load_certs, load_certs_from_bytes, load_private_key, + load_private_key_from_bytes, CHIA_CA_CRT, CHIA_CA_KEY, +}; +use dg_xch_serialize::{hash_256, ChiaSerialize}; +use log::debug; use reqwest::header::{HeaderName, HeaderValue}; use rustls::ClientConfig; use serde::Deserialize; use std::collections::HashMap; +use std::fs; use std::io::{Cursor, Error, ErrorKind}; use std::str::FromStr; -use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::atomic::AtomicBool; use std::sync::Arc; use std::time::Duration; -use tokio::net::TcpStream; -#[cfg(not(target_os = "windows"))] -use tokio::signal::unix::{signal, SignalKind}; -#[cfg(target_os = "windows")] -use tokio::signal::windows::{ctrl_break, ctrl_c, ctrl_close, ctrl_logoff, ctrl_shutdown}; +use tokio::select; use tokio::sync::mpsc::Sender; use tokio::sync::Mutex; -use tokio::{fs, select}; +use tokio::task::JoinHandle; use tokio_tungstenite::tungstenite::client::IntoClientRequest; -use tokio_tungstenite::tungstenite::error::ProtocolError; -use tokio_tungstenite::tungstenite::Message; -use tokio_tungstenite::{ - connect_async_tls_with_config, Connector, MaybeTlsStream, WebSocketStream, -}; +use tokio_tungstenite::{connect_async_tls_with_config, Connector}; use urlencoding::encode; use uuid::Uuid; -#[cfg(not(target_os = "windows"))] -pub async fn await_termination() -> Result<(), Error> { - let mut term_signal = signal(SignalKind::terminate())?; - let mut int_signal = signal(SignalKind::interrupt())?; - let mut quit_signal = signal(SignalKind::quit())?; - let mut alarm_signal = signal(SignalKind::alarm())?; - let mut hup_signal = signal(SignalKind::hangup())?; - select! { - _ = term_signal.recv() => (), - _ = int_signal.recv() => (), - _ = quit_signal.recv() => (), - _ = alarm_signal.recv() => (), - _ = hup_signal.recv() => () - } - Ok(()) -} - -#[cfg(target_os = "windows")] -pub async fn await_termination() -> Result<(), Error> { - let mut ctrl_break_signal = ctrl_break()?; - let mut ctrl_c_signal = ctrl_c()?; - let mut ctrl_close_signal = ctrl_close()?; - let mut ctrl_logoff_signal = ctrl_logoff()?; - let mut ctrl_shutdown_signal = ctrl_shutdown()?; - select! { - _ = ctrl_break_signal.recv() => (), - _ = ctrl_c_signal.recv() => (), - _ = ctrl_close_signal.recv() => (), - _ = ctrl_logoff_signal.recv() => (), - _ = ctrl_shutdown_signal.recv() => () - } - Ok(()) -} - fn _version() -> &'static str { env!("CARGO_PKG_VERSION") } @@ -94,67 +54,83 @@ fn test_version() { println!("{}", version()); } -pub async fn get_client_tls( - host: &str, - port: u16, - ssl_info: ClientSSLConfig<'_>, - additional_headers: &Option>, -) -> Result<(Client, ReadStream), Error> { - let certs = load_certs(ssl_info.ssl_crt_path)?; - let key = load_private_key(ssl_info.ssl_key_path)?; - let cfg = Arc::new( - ClientConfig::builder() - .with_safe_defaults() - .with_custom_certificate_verifier(Arc::new(NoCertificateVerification {})) - .with_client_auth_cert(certs, key) - .map_err(|e| Error::new(ErrorKind::Other, format!("Error Building Client: {:?}", e)))?, - ); - - let connector = Connector::Rustls(cfg.clone()); - let mut request = format!("wss://{}:{}/ws", host, port) - .into_client_request() - .map_err(|e| { - Error::new( - ErrorKind::InvalidData, - format!("Failed to Parse Request: {}", e), +pub struct WsClient { + pub connection: Arc>, + pub client_config: Arc, + handle: JoinHandle<()>, +} +impl WsClient { + pub async fn new( + client_config: Arc, + node_type: NodeType, + message_handlers: Arc>>>, + run: Arc, + ) -> Result { + let (certs, key, cert_str) = if let Some(ssl_info) = &client_config.ssl_info { + ( + load_certs(&ssl_info.ssl_crt_path)?, + load_private_key(&ssl_info.ssl_key_path)?, + fs::read_to_string(&ssl_info.ssl_crt_path)?, ) - })?; - if let Some(m) = additional_headers { - for (k, v) in m { - request.headers_mut().insert( - HeaderName::from_str(k).map_err(|e| { - Error::new( - ErrorKind::InvalidData, - format!("Failed to Parse Header Name {},\r\n {}", k, e), - ) - })?, - HeaderValue::from_str(v).map_err(|e| { - Error::new( - ErrorKind::InvalidData, - format!("Failed to Parse Header value {},\r\n {}", v, e), - ) - })?, - ); - } - } - request.headers_mut().insert( - "chia-client-cert", - HeaderValue::from_str( - encode( - &fs::read_to_string(ssl_info.ssl_crt_path) - .await - .unwrap_or_default(), + } else { + let (cert_bytes, key_bytes) = generate_ca_signed_cert_data(CHIA_CA_CRT, CHIA_CA_KEY) + .map_err(|e| Error::new(ErrorKind::Other, format!("OpenSSL Errors: {:?}", e)))?; + ( + load_certs_from_bytes(cert_bytes.as_bytes())?, + load_private_key_from_bytes(key_bytes.as_bytes())?, + cert_bytes, ) - .as_ref(), + }; + let mut request = format!("wss://{}:{}/ws", client_config.host, client_config.port) + .into_client_request() + .map_err(|e| { + Error::new( + ErrorKind::InvalidData, + format!("Failed to Parse Request: {}", e), + ) + })?; + if let Some(m) = &client_config.additional_headers { + for (k, v) in m { + request.headers_mut().insert( + HeaderName::from_str(k).map_err(|e| { + Error::new( + ErrorKind::InvalidData, + format!("Failed to Parse Header Name {},\r\n {}", k, e), + ) + })?, + HeaderValue::from_str(v).map_err(|e| { + Error::new( + ErrorKind::InvalidData, + format!("Failed to Parse Header value {},\r\n {}", v, e), + ) + })?, + ); + } + } + request.headers_mut().insert( + "chia-client-cert", + HeaderValue::from_str(&encode(&cert_str)).map_err(|e| { + Error::new( + ErrorKind::InvalidData, + format!("Failed to Parse Header value CHIA_CA_CRT,\r\n {}", e), + ) + })?, + ); + let peer_id = Arc::new(Bytes32::new(&hash_256(&certs[0].0))); + let (stream, _) = connect_async_tls_with_config( + request, + None, + false, + Some(Connector::Rustls(Arc::new( + ClientConfig::builder() + .with_safe_defaults() + .with_custom_certificate_verifier(Arc::new(NoCertificateVerification {})) + .with_client_auth_cert(certs, key) + .map_err(|e| { + Error::new(ErrorKind::Other, format!("Error Building Client: {:?}", e)) + })?, + ))), ) - .map_err(|e| { - Error::new( - ErrorKind::InvalidData, - format!("Failed to Parse Header value CHIA_CA_CRT,\r\n {}", e), - ) - })?, - ); - let (stream, resp) = connect_async_tls_with_config(request, None, false, Some(connector)) .await .map_err(|e| { Error::new( @@ -162,167 +138,76 @@ pub async fn get_client_tls( format!("Error Connecting Client: {:?}", e), ) })?; - debug!("Client Connect Resp: {:?}", resp); - Ok(Client::new(stream)) -} - -pub async fn get_client_generated_tls( - host: &str, - port: u16, - additional_headers: &Option>, -) -> Result<(Client, ReadStream), Error> { - let (cert_bytes, key_bytes) = generate_ca_signed_cert_data(CHIA_CA_CRT, CHIA_CA_KEY) - .map_err(|e| Error::new(ErrorKind::Other, format!("OpenSSL Errors: {:?}", e)))?; - let certs = load_certs_from_bytes(cert_bytes.as_bytes())?; - let key = load_private_key_from_bytes(key_bytes.as_bytes())?; - let cfg = Arc::new( - ClientConfig::builder() - .with_safe_defaults() - .with_custom_certificate_verifier(Arc::new(NoCertificateVerification {})) - .with_client_auth_cert(certs, key) - .map_err(|e| Error::new(ErrorKind::Other, format!("Error Building Client: {:?}", e)))?, - ); - let connector = Connector::Rustls(cfg.clone()); - let mut request = format!("wss://{}:{}/ws", host, port) - .into_client_request() - .map_err(|e| { - Error::new( - ErrorKind::InvalidData, - format!("Failed to Parse Request: {}", e), - ) - })?; - request.headers_mut().insert( - "chia-client-cert", - HeaderValue::from_str(encode(&cert_bytes).as_ref()).map_err(|e| { - Error::new( - ErrorKind::InvalidData, - format!("Failed to Parse Header value CHIA_CA_CRT,\r\n {}", e), - ) - })?, - ); - if let Some(m) = additional_headers { - for (k, v) in m { - request.headers_mut().insert( - HeaderName::from_str(k).map_err(|e| { - Error::new( - ErrorKind::InvalidData, - format!("Failed to Parse Header Name {},\r\n {}", k, e), - ) - })?, - HeaderValue::from_str(v).map_err(|e| { - Error::new( - ErrorKind::InvalidData, - format!("Failed to Parse Header value {},\r\n {}", v, e), - ) - })?, - ); - } + let peers = Arc::new(Mutex::new(HashMap::new())); + let (ws_con, mut stream) = WebsocketConnection::new( + WebsocketMsgStream::Tls(stream), + message_handlers, + peer_id.clone(), + peers.clone(), + ); + let connection = Arc::new(Mutex::new(ws_con)); + peers.lock().await.insert( + *peer_id.as_ref(), + Arc::new(SocketPeer { + node_type: Arc::new(Mutex::new(NodeType::Harvester)), + websocket: connection.clone(), + }), + ); + let ws_client = WsClient { + connection, + client_config, + handle: tokio::spawn(async move { stream.run(run).await }), + }; + ws_client.perform_handshake(node_type).await?; + Ok(ws_client) } - let (stream, resp) = connect_async_tls_with_config(request, None, false, Some(connector)) - .await - .map_err(|e| { - Error::new( - ErrorKind::Other, - format!("Error Connecting Client: {:?}", e), - ) - })?; - debug!("Client Connect Resp: {:?}", resp); - Ok(Client::new(stream)) -} -pub async fn get_client( - host: &str, - port: u16, - additional_headers: &Option>, -) -> Result<(Client, ReadStream), Error> { - let mut request = format!("wss://{}:{}/ws", host, port) - .into_client_request() - .map_err(|e| { - Error::new( - ErrorKind::InvalidData, - format!("Failed to Parse Request: {}", e), - ) - })?; - if let Some(m) = additional_headers { - for (k, v) in m { - request.headers_mut().insert( - HeaderName::from_str(k).map_err(|e| { - Error::new( - ErrorKind::InvalidData, - format!("Failed to Parse Header Name {},\r\n {}", k, e), - ) - })?, - HeaderValue::from_str(v).map_err(|e| { - Error::new( - ErrorKind::InvalidData, - format!("Failed to Parse Header value {},\r\n {}", v, e), - ) - })?, - ); - } + pub async fn join(self) -> Result<(), Error> { + self.handle + .await + .map_err(|e| Error::new(ErrorKind::Other, format!("Failed to join farmer: {:?}", e))) } - let (cert_bytes, _) = generate_ca_signed_cert_data(CHIA_CA_CRT, CHIA_CA_KEY) - .map_err(|e| Error::new(ErrorKind::Other, format!("OpenSSL Errors: {:?}", e)))?; - request.headers_mut().insert( - "chia-client-cert", - HeaderValue::from_str(encode(&cert_bytes).as_ref()).map_err(|e| { - Error::new( - ErrorKind::InvalidData, - format!("Failed to Parse Header value CHIA_CA_CRT,\r\n {}", e), - ) - })?, - ); - let (stream, resp) = connect_async_tls_with_config(request, None, false, None) + + async fn perform_handshake(&self, node_type: NodeType) -> Result { + oneshot::( + self.connection.clone(), + ChiaMessage::new( + ProtocolMessageTypes::Handshake, + &Handshake { + network_id: self.client_config.network_id.to_string(), + protocol_version: PROTOCOL_VERSION.to_string(), + software_version: version(), + server_port: self.client_config.port, + node_type: node_type as u8, + capabilities: CAPABILITIES + .iter() + .map(|e| (e.0, e.1.to_string())) + .collect(), + }, + None, + ), + Some(ProtocolMessageTypes::Handshake), + None, + Some(15000), + ) .await - .map_err(|e| Error::new(ErrorKind::Other, e))?; - debug!("Client Connect Resp: {:?}", resp); - Ok(Client::new(stream)) -} -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum NodeType { - Unknown = 0, - FullNode = 1, - Harvester = 2, - Farmer = 3, - Timelord = 4, - Introducer = 5, - Wallet = 6, - DataLayer = 7, -} -impl From for NodeType { - fn from(byte: u8) -> Self { - match byte { - i if i == NodeType::Unknown as u8 => NodeType::Unknown, - i if i == NodeType::FullNode as u8 => NodeType::FullNode, - i if i == NodeType::Harvester as u8 => NodeType::Harvester, - i if i == NodeType::Farmer as u8 => NodeType::Farmer, - i if i == NodeType::Timelord as u8 => NodeType::Timelord, - i if i == NodeType::Introducer as u8 => NodeType::Introducer, - i if i == NodeType::Wallet as u8 => NodeType::Wallet, - i if i == NodeType::DataLayer as u8 => NodeType::DataLayer, - _ => NodeType::Unknown, - } } } -#[derive(ChiaSerial, Debug, Clone)] -pub struct ChiaMessage { - pub msg_type: ProtocolMessageTypes, - pub id: Option, - pub data: Vec, -} -impl ChiaMessage { - pub fn new(msg_type: ProtocolMessageTypes, msg: &T, id: Option) -> Self { - ChiaMessage { - msg_type, - id, - data: msg.to_bytes(), - } - } + +pub struct ClientSSLConfig { + pub ssl_crt_path: String, + pub ssl_key_path: String, + pub ssl_ca_crt_path: String, } -impl From for Message { - fn from(val: ChiaMessage) -> Self { - Message::Binary(val.to_bytes()) - } + +pub struct WsClientConfig { + pub host: String, + pub port: u16, + pub network_id: String, + pub ssl_info: Option, + //Used to control software version sent to server, default is dg_xch_clients: VERSION + pub software_version: Option, + pub additional_headers: Option>, } #[derive(Debug, Clone, Deserialize)] @@ -331,79 +216,26 @@ pub struct HandshakeResp { pub success: bool, } -async fn perform_handshake( - client: Arc>, - network_id: &str, - port: u16, - node_type: NodeType, -) -> Result { - oneshot::( - client, - ChiaMessage::new( - ProtocolMessageTypes::Handshake, - &Handshake { - network_id: network_id.to_string(), - protocol_version: PROTOCOL_VERSION.to_string(), - software_version: SOFTWARE_VERSION.to_string(), - server_port: port, - node_type: node_type as u8, - capabilities: CAPABILITIES - .iter() - .map(|e| (e.0, e.1.to_string())) - .collect(), - }, - None, - ), - Some(ProtocolMessageTypes::Handshake), - None, - Some(15000), - ) - .await -} -#[derive(Debug)] -pub struct ChiaMessageFilter { - pub msg_type: Option, - pub id: Option, -} -impl ChiaMessageFilter { - pub fn matches(&self, msg: Arc) -> bool { - if self.id.is_some() && self.id != msg.id { - return false; - } - if let Some(s) = &self.msg_type { - if *s != msg.msg_type { - return false; - } - } - true - } -} - -pub struct ChiaMessageHandler { - filter: ChiaMessageFilter, - handle: Arc, -} -impl ChiaMessageHandler { - pub fn new(filter: ChiaMessageFilter, handle: Arc) -> Self { - ChiaMessageHandler { filter, handle } - } -} - pub struct OneShotHandler { pub id: Uuid, channel: Sender>, } #[async_trait] impl MessageHandler for OneShotHandler { - async fn handle(&self, msg: Arc) -> Result<(), Error> { + async fn handle( + &self, + msg: Arc, + _peer_id: Arc, + _peers: PeerMap, + ) -> Result<(), Error> { debug!("{:?}", msg.as_ref()); let _ = &self.channel.send(msg.data.clone()).await; Ok(()) } } -pub async fn oneshot( - client: Arc>, +pub async fn oneshot( + connection: Arc>, msg: ChiaMessage, resp_type: Option, msg_id: Option, @@ -417,27 +249,36 @@ pub async fn oneshot( }; let handle = Arc::new(handle); let chia_handle = ChiaMessageHandler { - filter: ChiaMessageFilter { + filter: Arc::new(ChiaMessageFilter { msg_type: resp_type, id: msg_id, - }, + }), handle: handle.clone(), }; - client.lock().await.subscribe(handle.id, chia_handle).await; + connection + .lock() + .await + .subscribe(handle.id, chia_handle) + .await; let res_handle = tokio::spawn(async move { let res = rx.recv().await; rx.close(); res }); - client.lock().await.send(msg.into()).await.map_err(|e| { - Error::new( - ErrorKind::InvalidData, - format!("Failed to parse send data: {:?}", e), - ) - })?; + connection + .lock() + .await + .send(msg.into()) + .await + .map_err(|e| { + Error::new( + ErrorKind::InvalidData, + format!("Failed to parse send data: {:?}", e), + ) + })?; select!( _ = tokio::time::sleep(Duration::from_millis(timeout.unwrap_or(15000))) => { - client.lock().await.unsubscribe(handle.id).await; + connection.lock().await.unsubscribe(handle.id).await; Err(Error::new( ErrorKind::Other, "Timeout before oneshot completed", @@ -447,7 +288,7 @@ pub async fn oneshot( let res = res?; if let Some(v) = res { let mut cursor = Cursor::new(v); - client.lock().await.unsubscribe(handle.id).await; + connection.lock().await.unsubscribe(handle.id).await; R::from_bytes(&mut cursor).map_err(|e| { Error::new( ErrorKind::InvalidData, @@ -455,7 +296,7 @@ pub async fn oneshot( ) }) } else { - client.lock().await.unsubscribe(handle.id).await; + connection.lock().await.unsubscribe(handle.id).await; Err(Error::new( ErrorKind::Other, "Channel Closed before response received", @@ -464,317 +305,3 @@ pub async fn oneshot( } ) } - -#[async_trait] -pub trait MessageHandler { - async fn handle(&self, msg: Arc) -> Result<(), Error>; -} - -pub struct ReadStream { - read: SplitStream>>, - subscribers: Arc>, -} -impl ReadStream { - pub async fn run(&mut self, run: Arc) { - loop { - select! { - msg = self.read.next() => { - match msg { - Some(Ok(msg)) => { - match msg { - Message::Binary(bin_data) => { - let mut cursor = Cursor::new(bin_data); - match ChiaMessage::from_bytes(&mut cursor) { - Ok(chia_msg) => { - let msg_arc: Arc = Arc::new(chia_msg); - for v in self.subscribers.as_ref() { - if v.filter.matches(msg_arc.clone()) { - let msg_arc_c = msg_arc.clone(); - let v_arc_c = v.handle.clone(); - tokio::spawn(async move { - if let Err(e) = v_arc_c.handle(msg_arc_c.clone()).await { - error!("Error Handling Message: {:?}, {:?}", msg_arc_c, e); - } - }); - } - } - debug!("Processed Message: {:?}", &msg_arc.msg_type); - } - Err(e) => { - error!("Invalid Message: {:?}", e); - } - } - }, - Message::Close(reason) => { - info!("Received Close: {:?}", reason); - return; - } - _ => { - error!("Invalid Message: {:?}", msg); - } - } - } - Some(Err(msg)) => { - match msg { - tokio_tungstenite::tungstenite::Error::Protocol(ProtocolError::ResetWithoutClosingHandshake) => { - debug!("Client Stream Closed without Handshake"); - }, - others => { - error!("Client Stream Error: {:?}", others); - } - } - return; - } - None => { - info!("End of client read Stream"); - return; - } - } - } - _ = async { - loop { - if !run.load(Ordering::Relaxed) { - debug!("Client is exiting"); - return; - } else { - tokio::time::sleep(Duration::from_secs(1)).await - } - } - } => { - return; - } - } - } - } -} - -#[async_trait] -pub trait Websocket { - async fn send(&self, msg: Message) -> Result<(), Error>; - async fn subscribe(&self, uuid: Uuid, handle: ChiaMessageHandler); - async fn unsubscribe(&self, uuid: Uuid); - async fn close(&self, msg: Option) -> Result<(), Error>; -} - -pub struct ClientSSLConfig<'a> { - pub ssl_crt_path: &'a str, - pub ssl_key_path: &'a str, - pub ssl_ca_crt_path: &'a str, -} -pub struct Client { - write: Arc>, Message>>>, - subscribers: Arc>, -} -impl Client { - pub fn new(stream: WebSocketStream>) -> (Self, ReadStream) { - let (write, read) = stream.split(); - let subscribers = Arc::new(DashMap::::new()); - let client = Client { - write: Arc::new(Mutex::new(write)), - subscribers: subscribers.clone(), - }; - let stream = ReadStream { read, subscribers }; - (client, stream) - } - pub async fn clear(&mut self) { - self.subscribers.clear() - } - pub async fn shutdown(&mut self) -> Result<(), Error> { - self.subscribers.clear(); - self.close(None).await - } -} -#[async_trait] -impl Websocket for Client { - async fn send(&self, msg: Message) -> Result<(), Error> { - trace!("Sending Request: {:?}", &msg); - self.write - .lock() - .await - .send(msg) - .map_err(|e| Error::new(ErrorKind::Other, e)) - .await - } - - async fn subscribe(&self, uuid: Uuid, handle: ChiaMessageHandler) { - self.subscribers.insert(uuid, handle); - } - - async fn unsubscribe(&self, uuid: Uuid) { - self.subscribers.remove(&uuid); - } - - async fn close(&self, msg: Option) -> Result<(), Error> { - trace!("Sending Request: {:?}", &msg); - if let Some(msg) = msg { - let _ = self - .write - .lock() - .await - .send(msg) - .map_err(|e| Error::new(ErrorKind::Other, e)) - .await; - self.write - .lock() - .await - .close() - .map_err(|e| Error::new(ErrorKind::Other, e)) - .await - } else { - self.write - .lock() - .await - .close() - .map_err(|e| Error::new(ErrorKind::Other, e)) - .await - } - } -} - -pub struct ServerReadStream { - read: SplitStream>>, - subscribers: Arc>, -} -impl ServerReadStream { - pub async fn run(&mut self, run: Arc) { - loop { - select! { - msg = self.read.next() => { - match msg { - Some(Ok(msg)) => { - match msg { - Message::Binary(bin_data) => { - let mut cursor = Cursor::new(bin_data); - match ChiaMessage::from_bytes(&mut cursor) { - Ok(chia_msg) => { - let msg_arc: Arc = Arc::new(chia_msg); - let mut matched = false; - for v in self.subscribers.iter() { - if v.filter.matches(msg_arc.clone()) { - let msg_arc_c = msg_arc.clone(); - let v_arc_c = v.handle.clone(); - tokio::spawn(async move { v_arc_c.handle(msg_arc_c).await }); - matched = true; - } - } - if !matched{ - error!("No Matches for Message: {:?}", &msg_arc); - } - debug!("Processed Message: {:?}", &msg_arc.msg_type); - } - Err(e) => { - error!("Invalid Message: {:?}", e); - } - } - } - Message::Close(e) => { - debug!("Server Got Close Message: {:?}", e); - return; - }, - _ => { - error!("Invalid Message: {:?}", msg); - } - } - } - Some(Err(msg)) => { - match msg { - tokio_tungstenite::tungstenite::Error::Protocol(ProtocolError::ResetWithoutClosingHandshake) => { - debug!("Server Stream Closed without Handshake"); - }, - others => { - error!("Server Stream Error: {:?}", others); - } - } - return; - } - None => { - info!("End of server read Stream"); - return; - } - } - } - _ = await_termination() => { - return; - } - _ = async { - loop { - if !run.load(Ordering::Relaxed){ - debug!("Server is exiting"); - return; - } else { - tokio::time::sleep(Duration::from_secs(1)).await - } - } - } => { - return; - } - } - } - } -} - -pub struct ServerConnection { - write: Arc>, Message>>>, - subscribers: Arc>, -} -impl ServerConnection { - pub fn new(stream: WebSocketStream>) -> (Self, ServerReadStream) { - let (write, read) = stream.split(); - let subscribers = Arc::new(DashMap::::new()); - let server = ServerConnection { - write: Arc::new(Mutex::new(write)), - subscribers: subscribers.clone(), - }; - let stream = ServerReadStream { read, subscribers }; - (server, stream) - } - pub async fn clear(&mut self) { - self.subscribers.clear() - } -} -#[async_trait] -impl Websocket for ServerConnection { - async fn send(&self, msg: Message) -> Result<(), Error> { - trace!("Sending Request: {:?}", &msg); - self.write - .lock() - .await - .send(msg) - .map_err(|e| Error::new(ErrorKind::Other, e)) - .await - } - - async fn subscribe(&self, uuid: Uuid, handle: ChiaMessageHandler) { - self.subscribers.insert(uuid, handle); - } - - async fn unsubscribe(&self, uuid: Uuid) { - self.subscribers.remove(&uuid); - } - - async fn close(&self, msg: Option) -> Result<(), Error> { - trace!("Sending Request: {:?}", &msg); - if let Some(msg) = msg { - let _ = self - .write - .lock() - .await - .send(msg) - .map_err(|e| Error::new(ErrorKind::Other, e)) - .await; - self.write - .lock() - .await - .close() - .map_err(|e| Error::new(ErrorKind::Other, e)) - .await - } else { - self.write - .lock() - .await - .close() - .map_err(|e| Error::new(ErrorKind::Other, e)) - .await - } - } -} diff --git a/clients/src/websocket/wallet.rs b/clients/src/websocket/wallet.rs deleted file mode 100644 index 222b282..0000000 --- a/clients/src/websocket/wallet.rs +++ /dev/null @@ -1,41 +0,0 @@ -use crate::websocket::{ - get_client, get_client_tls, perform_handshake, Client, ClientSSLConfig, NodeType, -}; -use std::collections::HashMap; -use std::io::Error; -use std::sync::atomic::AtomicBool; -use std::sync::Arc; -use tokio::sync::Mutex; - -pub struct WalletClient { - pub client: Arc>, -} -impl WalletClient { - pub async fn new( - host: &str, - port: u16, - network_id: &str, - additional_headers: &Option>, - run: Arc, - ) -> Result { - let (client, mut stream) = get_client(host, port, additional_headers).await?; - tokio::spawn(async move { stream.run(run).await }); - let client = Arc::new(Mutex::new(client)); - let _ = perform_handshake(client.clone(), network_id, port, NodeType::Wallet).await; - Ok(WalletClient { client }) - } - pub async fn new_ssl( - host: &str, - port: u16, - ssl_info: ClientSSLConfig<'_>, - network_id: &str, - additional_headers: &Option>, - run: Arc, - ) -> Result { - let (client, mut stream) = get_client_tls(host, port, ssl_info, additional_headers).await?; - tokio::spawn(async move { stream.run(run).await }); - let client = Arc::new(Mutex::new(client)); - let _ = perform_handshake(client.clone(), network_id, port, NodeType::Wallet).await; - Ok(WalletClient { client }) - } -} diff --git a/clients/src/websocket/wallet/mod.rs b/clients/src/websocket/wallet/mod.rs new file mode 100644 index 0000000..0b0116d --- /dev/null +++ b/clients/src/websocket/wallet/mod.rs @@ -0,0 +1,35 @@ +use crate::websocket::{WsClient, WsClientConfig}; +use dg_xch_core::protocols::{ChiaMessageHandler, NodeType}; +use std::collections::HashMap; +use std::io::Error; +use std::sync::atomic::AtomicBool; +use std::sync::Arc; +use tokio::sync::Mutex; +use uuid::Uuid; + +pub struct WalletClient { + pub client: WsClient, +} +impl WalletClient { + pub async fn new( + client_config: Arc, + run: Arc, + ) -> Result { + let handles = Arc::new(Mutex::new(handles())); + let client = WsClient::new(client_config, NodeType::Wallet, handles, run).await?; + Ok(WalletClient { client }) + } + + pub async fn join(self) -> Result<(), Error> { + self.client.connection.lock().await.shutdown().await?; + self.client.join().await + } + + pub fn is_closed(&self) -> bool { + self.client.handle.is_finished() + } +} + +fn handles() -> HashMap> { + HashMap::from([]) +} diff --git a/core/Cargo.toml b/core/Cargo.toml index 4492415..67f9cc9 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dg_xch_core" -version = "1.2.1" +version = "2.0.0" edition = "2021" authors = ["James Hoerr"] description = "Core library containing type/error definitions, CLVM tools, Consensus and Pool definitions" @@ -9,28 +9,42 @@ homepage = "https://github.com/GalactechsLLC/dg_xch_utils" repository = "https://github.com/GalactechsLLC/dg_xch_utils/core" [dependencies] +async-trait = "0.1.74" bech32 = "0.9.1" bip39 = {version= "2.0.0", features=["rand"] } bls12_381 = "0.8.0" blst = "0.3.11" bytes = "1.5.0" der = "0.7.8" -dg_xch_macros = {path = "../macros", version="1.2.1"} -dg_xch_serialize = {path = "../serialize", version="1.2.1"} +dg_xch_macros = {path = "../macros", version="2.0.0"} +dg_xch_serialize = {path = "../serialize", version="2.0.0"} +futures-util = "0.3.28" hex = "0.4.3" hkdf = "0.12.3" +hyper = {version="1.0.1", features=["full"]} +hyper-util = {version="0.1.1", features=["full"]} log = "0.4.20" -num-bigint = "0.4.4" +num-bigint = { version = "0.4.4", features = ["serde"] } num-integer = "0.1.45" num-traits = "0.2.17" once_cell = "1.18.0" paperclip = { version = "0.8.2", features = ["actix4"], optional = true } +prometheus = {version="0.13.3", features=["protobuf"], optional = true} rand = "0.8.5" regex = "1.10.2" +rustls = {version = "0.21.9", features = ["dangerous_configuration"] } +rustls-pemfile = "1.0.4" rsa = { version = "0.9.4", features = ["std", "pem", "sha2"] } serde = { version = "1.0.193", features = ["derive"] } serde_json = "1.0.108" sha2 = { version = "0.10.8", features = ["oid"] } simple_logger = "4.3.0" -tokio = {version = "1.34.0", features=["rt-multi-thread", "sync", "signal", "macros", "process", "time", "fs", "net"]} +time = "0.3.30" +tokio = {version = "1.34.0", features=["rt-multi-thread", "sync", "signal", "macros", "process", "time", "fs", "net", "io-util"]} +tokio-tungstenite = {version = "0.20.1", features = ["rustls-tls-webpki-roots", "rustls"] } +uuid = {version="1.6.1", features=["v4"]} x509-cert = { version = "0.2.4", features = ["std", "pem", "builder"] } + +[features] +metrics = ["dep:prometheus"] +default = [] \ No newline at end of file diff --git a/core/src/config.rs b/core/src/config.rs new file mode 100644 index 0000000..c619326 --- /dev/null +++ b/core/src/config.rs @@ -0,0 +1,1653 @@ +use crate::blockchain::sized_bytes::{Bytes32, Bytes48}; +use crate::consensus::overrides::ConsensusOverrides; +use num_bigint::BigInt; +use std::collections::HashMap; + +fn alerts_url() -> String { + "https://download.chia.net/notify/mainnet_alert.txt".to_string() +} +fn chia_alerts_pubkey() -> String { + "89b7fd87cb56e926ecefb879a29aae308be01f31980569f6a75a69d2a9a69daefd71fb778d865f7c50d6c967e3025937".to_string() +} +fn chia_ssl_ca() -> CaSsl { + CaSsl { + crt: "config/ssl/ca/chia_ca.crt".to_string(), + key: "config/ssl/ca/chia_ca.key".to_string(), + } +} +fn crawler() -> CrawlerConfig { + CrawlerConfig::default() +} +const fn crawler_port() -> u16 { + 8561 +} +fn crawler_ssl() -> PrivateSsl { + PrivateSsl { + private_crt: "config/ssl/crawler/private_crawler.crt".to_string(), + private_key: "config/ssl/crawler/private_crawler.key".to_string(), + } +} +const fn daemon_port() -> u16 { + 55400 +} +const fn daemon_max_message_size() -> u32 { + 50000000 +} +const fn daemon_heartbeat() -> u32 { + 300 +} +fn daemon_ssl() -> PrivateSsl { + PrivateSsl { + private_crt: "config/ssl/daemon/private_daemon.crt".to_string(), + private_key: "config/ssl/daemon/private_daemon.key".to_string(), + } +} +fn dns_servers() -> Vec { + vec![ + "dns-introducer.chia.net".to_string(), + "chia.ctrlaltdel.ch".to_string(), + "seeder.dexie.space".to_string(), + "chia-seeder.h9.com".to_string(), + "chia.hoffmang.com".to_string(), + "seeder.xchpool.org".to_string(), + ] +} +const fn data_layer_client_timeout() -> usize { + 15 +} +fn data_layer_database_path() -> String { + "data_layer/db/data_layer_CHALLENGE.sqlite".to_string() +} +const fn data_layer_fee() -> usize { + 1000000000 +} +fn data_layer_host_ip() -> String { + "0.0.0.0".to_string() +} +const fn data_layer_host_port() -> u16 { + 8575 +} +const fn data_layer_manage_data_interval() -> usize { + 60 +} +const fn data_layer_rpc_port() -> u16 { + 8562 +} +const fn data_layer_rpc_server_max_request_body_size() -> usize { + 26214400 +} +fn data_layer_server_files_location() -> String { + "data_layer/db/server_files_location_CHALLENGE".to_string() +} +fn data_layer_ssl() -> CombinedSsl { + CombinedSsl { + private_crt: "config/ssl/data_layer/private_data_layer.crt".to_string(), + private_key: "config/ssl/data_layer/private_data_layer.key".to_string(), + public_crt: "config/ssl/data_layer/public_data_layer.crt".to_string(), + public_key: "config/ssl/data_layer/public_data_layer.key".to_string(), + } +} +fn data_layer_wallet_peer() -> PeerConfig { + PeerConfig { + host: crate::config::self_hostname(), + port: 9256, + } +} +const fn default_true() -> bool { + true +} +const fn farmer_pool_share_threshold() -> usize { + 1000 +} +const fn farmer_port() -> u16 { + 8447 +} +const fn farmer_rpc_port() -> u16 { + 8559 +} +fn farmer_ssl() -> CombinedSsl { + CombinedSsl { + private_crt: "config/ssl/farmer/private_farmer.crt".to_string(), + private_key: "config/ssl/farmer/private_farmer.key".to_string(), + public_crt: "config/ssl/farmer/public_farmer.crt".to_string(), + public_key: "config/ssl/farmer/public_farmer.key".to_string(), + } +} +fn full_node_db_sync() -> String { + "auto".to_string() +} +fn full_node_peers() -> Vec { + vec![PeerConfig { + host: self_hostname(), + port: 8444, + }] +} +const fn full_node_port() -> u16 { + 8444 +} +const fn full_node_db_readers() -> usize { + 4 +} +fn full_node_database_path() -> String { + "db/blockchain_v2_CHALLENGE.sqlite".to_string() +} +fn full_node_peer_db_path() -> String { + "db/peer_table_node.sqlite".to_string() +} +fn full_node_peers_file_path() -> String { + "db/peers.dat".to_string() +} +const fn full_node_rpc_port() -> u16 { + 8555 +} +const fn full_node_sync_blocks_behind_threshold() -> usize { + 300 +} +const fn full_node_short_sync_blocks_behind_threshold() -> usize { + 20 +} +const fn full_node_bad_peak_cache_size() -> usize { + 100 +} +const fn full_node_peer_connect_interval() -> usize { + 30 +} +const fn full_node_peer_connect_timeout() -> usize { + 30 +} +const fn full_node_target_peer_count() -> usize { + 80 +} +const fn full_node_target_outbound_peer_count() -> usize { + 8 +} +const fn full_node_max_inbound_wallet() -> usize { + 20 +} +const fn full_node_max_inbound_farmer() -> usize { + 10 +} +const fn full_node_max_inbound_timelord() -> usize { + 5 +} +const fn full_node_recent_peer_threshold() -> usize { + 6000 +} +const fn full_node_target_uncompact_proofs() -> usize { + 100 +} +const fn full_node_weight_proof_timeout() -> usize { + 360 +} +const fn full_node_max_sync_wait() -> usize { + 30 +} +const fn full_node_max_subscribe_items() -> usize { + 200000 +} +const fn full_node_max_subscribe_response_items() -> usize { + 100000 +} +const fn full_node_trusted_max_subscribe_items() -> usize { + 2000000 +} +const fn full_node_trusted_max_subscribe_response_items() -> usize { + 500000 +} +fn full_node_ssl() -> CombinedSsl { + CombinedSsl { + private_crt: "config/ssl/full_node/private_full_node.crt".to_string(), + private_key: "config/ssl/full_node/private_full_node.key".to_string(), + public_crt: "config/ssl/full_node/public_full_node.crt".to_string(), + public_key: "config/ssl/full_node/public_full_node.key".to_string(), + } +} +const fn harvester_decompressor_timeout() -> usize { + 20 +} +fn harvester_farmer_peers() -> Vec { + vec![PeerConfig { + host: self_hostname(), + port: 8447, + }] +} +const fn harvester_max_compression_level_allowed() -> u8 { + 7 +} +const fn harvester_num_threads() -> usize { + 30 +} +const fn harvester_rpc_port() -> u16 { + 8560 +} +fn harvester_ssl() -> PrivateSsl { + PrivateSsl { + private_crt: "config/ssl/harvester/private_harvester.crt".to_string(), + private_key: "config/ssl/harvester/private_harvester.key".to_string(), + } +} + +const fn inbound_rate_limit_percent() -> u8 { + 100 +} +fn introducer_peer() -> IntroducerPeer { + IntroducerPeer { + host: "introducer.chia.net".to_string(), + port: 8444, + enable_private_networks: false, + } +} +const fn introducer_port() -> u16 { + 8445 +} +const fn introducer_max_peers_to_send() -> usize { + 20 +} +const fn introducer_recent_peer_threshold() -> usize { + 6000 +} +fn introducer_ssl() -> PublicSsl { + PublicSsl { + public_crt: "config/ssl/full_node/public_full_node.crt".to_string(), + public_key: "config/ssl/full_node/public_full_node.key".to_string(), + } +} +fn logging() -> LoggingConfig { + LoggingConfig::default() +} +const fn min_mainnet_k_size() -> u8 { + 32 +} +fn multiprocessing_start_method() -> String { + "default".to_string() +} +fn network_overrides() -> NetworkOverrides { + NetworkOverrides::default() +} +const fn outbound_rate_limit_percent() -> u8 { + 30 +} +fn plots_refresh_parameter() -> PlotRefreshParameter { + PlotRefreshParameter::default() +} +const fn ping_interval() -> u32 { + 120 +} +fn private_ssl_ca() -> CaSsl { + CaSsl { + crt: "config/ssl/ca/private_ca.crt".to_string(), + key: "config/ssl/ca/private_ca.key".to_string(), + } +} +const fn rpc_timeout() -> u32 { + 300 +} +const fn seeder_port() -> u16 { + 8444 +} +const fn seeder_other_peers_port() -> u16 { + 8444 +} +const fn seeder_dns_port() -> u16 { + 53 +} +const fn seeder_peer_connect_timeout() -> usize { + 2 +} +fn seeder_crawler_db_path() -> String { + "crawler.db".to_string() +} +fn seeder_bootstrap_peers() -> Vec { + vec!["node.chia.net".to_string()] +} +const fn seeder_minimum_height() -> usize { + 240000 +} +const fn seeder_minimum_version_count() -> usize { + 100 +} +fn seeder_domain_name() -> String { + "seeder.example.com.".to_string() +} +fn seeder_nameserver() -> String { + "example.com.".to_string() +} +const fn seeder_ttl() -> usize { + 300 +} +fn selected_network() -> String { + "mainnet".to_string() +} +fn self_hostname() -> String { + "localhost".to_string() +} +fn simulator_plot_directory() -> String { + "simulator/plots".to_string() +} +fn ssh_filename() -> String { + "config/ssh_host_key".to_string() +} +const fn timelord_max_connection_time() -> usize { + 60 +} +const fn timelord_rpc_port() -> u16 { + 8557 +} +const fn timelord_slow_bluebox_process_count() -> usize { + 1 +} +fn timelord_ssl() -> CombinedSsl { + CombinedSsl { + private_crt: "config/ssl/timelord/private_timelord.crt".to_string(), + private_key: "config/ssl/timelord/private_timelord.key".to_string(), + public_crt: "config/ssl/timelord/public_timelord.crt".to_string(), + public_key: "config/ssl/timelord/public_timelord.key".to_string(), + } +} +const fn ui_rpc_port() -> u16 { + 8555 +} +fn vdf_clients() -> VdfClients { + VdfClients { + ip: vec![ + self_hostname(), + "localhost".to_string(), + "127.0.0.1".to_string(), + ], + ips_estimate: 150000, + } +} +fn vdf_server() -> PeerConfig { + PeerConfig { + host: self_hostname(), + port: 8000, + } +} +const fn wallet_rpc_port() -> u16 { + 9256 +} +fn wallet_db_sync() -> String { + "auto".to_string() +} +const fn wallet_db_readers() -> usize { + 2 +} +const fn wallet_initial_num_public_keys() -> usize { + 425 +} +fn wallet_nft_metadata_cache_path() -> String { + "nft_cache".to_string() +} +const fn wallet_nft_metadata_cache_hash_length() -> usize { + 3 +} +fn wallet_database_path() -> String { + "wallet/db/blockchain_wallet_v2_CHALLENGE_KEY.sqlite".to_string() +} +fn wallet_wallet_peers_path() -> String { + "wallet/db/wallet_peers.sqlite".to_string() +} +fn wallet_wallet_peers_file_path() -> String { + "wallet/db/wallet_peers.dat".to_string() +} +const fn wallet_target_peer_count() -> usize { + 3 +} +const fn wallet_peer_connect_interval() -> usize { + 60 +} +const fn wallet_recent_peer_threshold() -> usize { + 6000 +} +const fn wallet_short_sync_blocks_behind_threshold() -> usize { + 20 +} +const fn wallet_inbound_rate_limit_percent() -> usize { + 100 +} +const fn wallet_outbound_rate_limit_percent() -> usize { + 60 +} +const fn wallet_weight_proof_timeout() -> usize { + 360 +} +const fn wallet_tx_resend_timeout_secs() -> usize { + 1800 +} +const fn wallet_spam_filter_after_n_txs() -> usize { + 200 +} +const fn wallet_xch_spam_amount() -> usize { + 1000000 +} +const fn wallet_required_notification_amount() -> usize { + 10000000 +} +fn wallet_ssl() -> CombinedSsl { + CombinedSsl { + private_crt: "config/ssl/wallet/private_wallet.crt".to_string(), + private_key: "config/ssl/wallet/private_wallet.key".to_string(), + public_crt: "config/ssl/wallet/public_wallet.crt".to_string(), + public_key: "config/ssl/wallet/public_wallet.key".to_string(), + } +} + +#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub struct ChiaConfig { + #[serde(default = "min_mainnet_k_size")] + pub min_mainnet_k_size: u8, + #[serde(default = "ping_interval")] + pub ping_interval: u32, + #[serde(default = "self_hostname")] + pub self_hostname: String, + #[serde(default)] + pub prefer_ipv6: bool, + #[serde(default = "rpc_timeout")] + pub rpc_timeout: u32, + #[serde(default = "daemon_port")] + pub daemon_port: u16, + #[serde(default = "daemon_max_message_size")] + pub daemon_max_message_size: u32, + #[serde(default = "daemon_heartbeat")] + pub daemon_heartbeat: u32, + #[serde(default)] + pub daemon_allow_tls_1_2: bool, + #[serde(default = "inbound_rate_limit_percent")] + pub inbound_rate_limit_percent: u8, + #[serde(default = "outbound_rate_limit_percent")] + pub outbound_rate_limit_percent: u8, + #[serde(default = "network_overrides")] + pub network_overrides: NetworkOverrides, + #[serde(default = "selected_network")] + pub selected_network: String, + #[serde(default = "alerts_url", rename = "ALERTS_URL")] + pub alerts_url: String, + #[serde(default = "chia_alerts_pubkey", rename = "CHIA_ALERTS_PUBKEY")] + pub chia_alerts_pubkey: String, + #[serde(default = "private_ssl_ca")] + pub private_ssl_ca: CaSsl, + #[serde(default = "chia_ssl_ca")] + pub chia_ssl_ca: CaSsl, + #[serde(default = "daemon_ssl")] + pub daemon_ssl: PrivateSsl, + #[serde(default)] + pub logging: LoggingConfig, + #[serde(default)] + pub seeder: SeederConfig, + #[serde(default)] + pub harvester: HarvesterConfig, + #[serde(default)] + pub pool: PoolConfig, + #[serde(default)] + pub farmer: FarmerConfig, + #[serde(default)] + pub timelord_launcher: TimelordLauncherConfig, + #[serde(default)] + pub timelord: TimelordConfig, + #[serde(default)] + pub full_node: FullnodeConfig, + #[serde(default)] + pub ui: UiConfig, + #[serde(default)] + pub wallet: WalletConfig, + #[serde(default)] + pub data_layer: DataLayerConfig, + #[serde(default)] + pub simulator: SimulatorConfig, +} +impl Default for ChiaConfig { + fn default() -> Self { + ChiaConfig { + min_mainnet_k_size: min_mainnet_k_size(), + ping_interval: ping_interval(), + self_hostname: self_hostname(), + prefer_ipv6: false, + rpc_timeout: rpc_timeout(), + daemon_port: daemon_port(), + daemon_max_message_size: daemon_max_message_size(), + daemon_heartbeat: daemon_heartbeat(), + daemon_allow_tls_1_2: false, + inbound_rate_limit_percent: inbound_rate_limit_percent(), + outbound_rate_limit_percent: outbound_rate_limit_percent(), + network_overrides: Default::default(), + selected_network: selected_network(), + alerts_url: alerts_url(), + chia_alerts_pubkey: chia_alerts_pubkey(), + private_ssl_ca: private_ssl_ca(), + chia_ssl_ca: chia_ssl_ca(), + daemon_ssl: daemon_ssl(), + logging: Default::default(), + seeder: Default::default(), + harvester: Default::default(), + pool: Default::default(), + farmer: Default::default(), + timelord_launcher: Default::default(), + timelord: Default::default(), + full_node: Default::default(), + ui: Default::default(), + wallet: Default::default(), + data_layer: Default::default(), + simulator: Default::default(), + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub struct SimulatorConfig { + #[serde(default = "default_true")] + pub auto_farm: bool, + #[serde(default)] + pub key_fingerprint: Option, + #[serde(default)] + pub farming_address: Option, + #[serde(default = "simulator_plot_directory")] + pub plot_directory: String, + #[serde(default = "default_true")] + pub use_current_time: bool, +} +impl Default for SimulatorConfig { + fn default() -> Self { + SimulatorConfig { + auto_farm: true, + key_fingerprint: None, + farming_address: None, + plot_directory: simulator_plot_directory(), + use_current_time: true, + } + } +} + +#[derive(Default, Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub struct DatalayerPlugin { + pub url: String, + pub headers: HashMap, +} + +#[derive(Default, Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub struct DatalayerPlugins { + pub uploaders: Vec, + pub downloaders: Vec, +} + +#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub struct DataLayerConfig { + #[serde(default = "data_layer_wallet_peer")] + pub wallet_peer: PeerConfig, + #[serde(default = "data_layer_database_path")] + pub database_path: String, + #[serde(default = "data_layer_server_files_location")] + pub server_files_location: String, + #[serde(default = "data_layer_client_timeout")] + pub client_timeout: usize, + #[serde(default = "data_layer_host_ip")] + pub host_ip: String, + #[serde(default = "data_layer_host_port")] + pub host_port: u16, + #[serde(default = "data_layer_manage_data_interval")] + pub manage_data_interval: usize, + #[serde(default = "selected_network")] + pub selected_network: String, + #[serde(default = "default_true")] + pub start_rpc_server: bool, + #[serde(default = "data_layer_rpc_port")] + pub rpc_port: u16, + #[serde(default = "data_layer_rpc_server_max_request_body_size")] + pub rpc_server_max_request_body_size: usize, + #[serde(default = "data_layer_fee")] + pub fee: usize, + #[serde(default)] + pub log_sqlite_cmds: bool, + #[serde(default)] + pub logging: LoggingConfig, + #[serde(default = "data_layer_ssl")] + pub ssl: CombinedSsl, + #[serde(default)] + pub plugins: DatalayerPlugins, +} +impl Default for DataLayerConfig { + fn default() -> Self { + DataLayerConfig { + wallet_peer: data_layer_wallet_peer(), + database_path: data_layer_database_path(), + server_files_location: data_layer_server_files_location(), + client_timeout: data_layer_client_timeout(), + host_ip: data_layer_host_ip(), + host_port: data_layer_host_port(), + manage_data_interval: data_layer_manage_data_interval(), + selected_network: selected_network(), + start_rpc_server: true, + rpc_port: data_layer_rpc_port(), + rpc_server_max_request_body_size: data_layer_rpc_server_max_request_body_size(), + fee: data_layer_fee(), + log_sqlite_cmds: false, + logging: Default::default(), + ssl: data_layer_ssl(), + plugins: Default::default(), + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub struct AutoClaimConfig { + pub enabled: bool, + pub tx_fee: usize, + pub min_amount: usize, + pub batch_size: usize, +} +impl Default for AutoClaimConfig { + fn default() -> Self { + AutoClaimConfig { + enabled: false, + tx_fee: 0, + min_amount: 0, + batch_size: 50, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub struct WalletConfig { + #[serde(default = "wallet_rpc_port")] + pub rpc_port: u16, + #[serde(default)] + pub enable_profiler: bool, + #[serde(default)] + pub enable_memory_profiler: bool, + #[serde(default = "wallet_db_sync")] + pub db_sync: String, + #[serde(default = "wallet_db_readers")] + pub db_readers: usize, + #[serde(default = "default_true")] + pub connect_to_unknown_peers: bool, + #[serde(default = "wallet_initial_num_public_keys")] + pub initial_num_public_keys: usize, + #[serde(default)] + pub reuse_public_key_for_change: HashMap, + #[serde(default = "dns_servers")] + pub dns_servers: Vec, + #[serde(default = "full_node_peers")] + pub full_node_peers: Vec, + #[serde(default = "wallet_nft_metadata_cache_path")] + pub nft_metadata_cache_path: String, + #[serde(default = "wallet_nft_metadata_cache_hash_length")] + pub nft_metadata_cache_hash_length: usize, + #[serde(default = "multiprocessing_start_method")] + pub multiprocessing_start_method: String, + #[serde(default)] + pub testing: bool, + #[serde(default = "wallet_database_path")] + pub database_path: String, + #[serde(default = "wallet_wallet_peers_path")] + pub wallet_peers_path: String, + #[serde(default = "wallet_wallet_peers_file_path")] + pub wallet_peers_file_path: String, + #[serde(default)] + pub log_sqlite_cmds: bool, + #[serde(default = "logging")] + pub logging: LoggingConfig, + #[serde(default = "network_overrides")] + pub network_overrides: NetworkOverrides, + #[serde(default = "selected_network")] + pub selected_network: String, + #[serde(default = "wallet_target_peer_count")] + pub target_peer_count: usize, + #[serde(default = "wallet_peer_connect_interval")] + pub peer_connect_interval: usize, + #[serde(default = "wallet_recent_peer_threshold")] + pub recent_peer_threshold: usize, + #[serde(default = "introducer_peer")] + pub introducer_peer: IntroducerPeer, + #[serde(default = "wallet_ssl")] + pub ssl: CombinedSsl, + #[serde(default)] + pub trusted_peers: HashMap, + #[serde(default = "wallet_short_sync_blocks_behind_threshold")] + pub short_sync_blocks_behind_threshold: usize, + #[serde(default = "wallet_inbound_rate_limit_percent")] + pub inbound_rate_limit_percent: usize, + #[serde(default = "wallet_outbound_rate_limit_percent")] + pub outbound_rate_limit_percent: usize, + #[serde(default = "wallet_weight_proof_timeout")] + pub weight_proof_timeout: usize, + #[serde(default)] + pub automatically_add_unknown_cats: bool, + #[serde(default = "wallet_tx_resend_timeout_secs")] + pub tx_resend_timeout_secs: usize, + #[serde(default)] + pub reset_sync_for_fingerprint: Option, + #[serde(default = "wallet_spam_filter_after_n_txs")] + pub spam_filter_after_n_txs: usize, + #[serde(default = "wallet_xch_spam_amount")] + pub xch_spam_amount: usize, + #[serde(default = "default_true")] + pub enable_notifications: bool, + #[serde(default = "wallet_required_notification_amount")] + pub required_notification_amount: usize, + #[serde(default)] + pub auto_claim: AutoClaimConfig, +} +impl Default for WalletConfig { + fn default() -> Self { + WalletConfig { + rpc_port: wallet_rpc_port(), + enable_profiler: false, + enable_memory_profiler: false, + db_sync: wallet_db_sync(), + db_readers: wallet_db_readers(), + connect_to_unknown_peers: true, + initial_num_public_keys: wallet_initial_num_public_keys(), + reuse_public_key_for_change: Default::default(), + dns_servers: dns_servers(), + full_node_peers: full_node_peers(), + nft_metadata_cache_path: wallet_nft_metadata_cache_path(), + nft_metadata_cache_hash_length: wallet_nft_metadata_cache_hash_length(), + multiprocessing_start_method: multiprocessing_start_method(), + testing: false, + database_path: wallet_database_path(), + wallet_peers_path: wallet_wallet_peers_path(), + wallet_peers_file_path: wallet_wallet_peers_file_path(), + log_sqlite_cmds: false, + logging: logging(), + network_overrides: network_overrides(), + selected_network: selected_network(), + target_peer_count: wallet_target_peer_count(), + peer_connect_interval: wallet_peer_connect_interval(), + recent_peer_threshold: wallet_recent_peer_threshold(), + introducer_peer: introducer_peer(), + ssl: wallet_ssl(), + trusted_peers: Default::default(), + short_sync_blocks_behind_threshold: wallet_short_sync_blocks_behind_threshold(), + inbound_rate_limit_percent: wallet_inbound_rate_limit_percent(), + outbound_rate_limit_percent: wallet_outbound_rate_limit_percent(), + weight_proof_timeout: wallet_weight_proof_timeout(), + automatically_add_unknown_cats: false, + tx_resend_timeout_secs: wallet_tx_resend_timeout_secs(), + reset_sync_for_fingerprint: None, + spam_filter_after_n_txs: wallet_spam_filter_after_n_txs(), + xch_spam_amount: wallet_xch_spam_amount(), + enable_notifications: true, + required_notification_amount: wallet_required_notification_amount(), + auto_claim: Default::default(), + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub struct IntroducerConfig { + #[serde(default = "self_hostname")] + pub host: String, + #[serde(default = "introducer_port")] + pub port: u16, + #[serde(default = "introducer_max_peers_to_send")] + pub max_peers_to_send: usize, + #[serde(default = "introducer_recent_peer_threshold")] + pub recent_peer_threshold: usize, + #[serde(default = "logging")] + pub logging: LoggingConfig, + #[serde(default = "network_overrides")] + pub network_overrides: NetworkOverrides, + #[serde(default = "selected_network")] + pub selected_network: String, + #[serde(default = "introducer_ssl")] + pub ssl: PublicSsl, +} +impl Default for IntroducerConfig { + fn default() -> Self { + IntroducerConfig { + host: self_hostname(), + port: introducer_port(), + max_peers_to_send: introducer_max_peers_to_send(), + recent_peer_threshold: introducer_recent_peer_threshold(), + logging: logging(), + network_overrides: network_overrides(), + selected_network: selected_network(), + ssl: introducer_ssl(), + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub struct UiConfig { + #[serde(default = "ui_rpc_port")] + pub rpc_port: u16, + #[serde(default = "ssh_filename")] + pub ssh_filename: String, + #[serde(default = "logging")] + pub logging: LoggingConfig, + #[serde(default = "network_overrides")] + pub network_overrides: NetworkOverrides, + #[serde(default = "selected_network")] + pub selected_network: String, + #[serde(default = "self_hostname")] + pub daemon_host: String, + #[serde(default = "daemon_port")] + pub daemon_port: u16, + #[serde(default = "daemon_ssl")] + pub daemon_ssl: PrivateSsl, +} +impl Default for UiConfig { + fn default() -> Self { + UiConfig { + rpc_port: ui_rpc_port(), + ssh_filename: ssh_filename(), + logging: logging(), + network_overrides: network_overrides(), + selected_network: selected_network(), + daemon_host: self_hostname(), + daemon_port: daemon_port(), + daemon_ssl: daemon_ssl(), + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub struct IntroducerPeer { + pub host: String, + pub port: u16, + pub enable_private_networks: bool, +} + +#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub struct FullnodeConfig { + #[serde(default = "full_node_port")] + pub port: u16, + #[serde(default = "full_node_db_sync")] + pub db_sync: String, + #[serde(default = "full_node_db_readers")] + pub db_readers: usize, + #[serde(default = "full_node_database_path")] + pub database_path: String, + #[serde(default = "full_node_peer_db_path")] + pub peer_db_path: String, + #[serde(default = "full_node_peers_file_path")] + pub peers_file_path: String, + #[serde(default = "multiprocessing_start_method")] + pub multiprocessing_start_method: String, + #[serde(default = "default_true")] + pub start_rpc_server: bool, + #[serde(default = "full_node_rpc_port")] + pub rpc_port: u16, + #[serde(default = "default_true")] + pub enable_upnp: bool, + #[serde(default = "full_node_sync_blocks_behind_threshold")] + pub sync_blocks_behind_threshold: usize, + #[serde(default = "full_node_short_sync_blocks_behind_threshold")] + pub short_sync_blocks_behind_threshold: usize, + #[serde(default = "full_node_bad_peak_cache_size")] + pub bad_peak_cache_size: usize, + #[serde(default)] + pub reserved_cores: usize, + #[serde(default)] + pub single_threaded: bool, + #[serde(default = "full_node_peer_connect_interval")] + pub peer_connect_interval: usize, + #[serde(default = "full_node_peer_connect_timeout")] + pub peer_connect_timeout: usize, + #[serde(default = "full_node_target_peer_count")] + pub target_peer_count: usize, + #[serde(default = "full_node_target_outbound_peer_count")] + pub target_outbound_peer_count: usize, + #[serde(default)] + pub exempt_peer_networks: Vec, + #[serde(default = "full_node_max_inbound_wallet")] + pub max_inbound_wallet: usize, + #[serde(default = "full_node_max_inbound_farmer")] + pub max_inbound_farmer: usize, + #[serde(default = "full_node_max_inbound_timelord")] + pub max_inbound_timelord: usize, + #[serde(default = "full_node_recent_peer_threshold")] + pub recent_peer_threshold: usize, + #[serde(default)] + pub send_uncompact_interval: usize, + #[serde(default = "full_node_target_uncompact_proofs")] + pub target_uncompact_proofs: usize, + #[serde(default)] + pub sanitize_weight_proof_only: bool, + #[serde(default = "full_node_weight_proof_timeout")] + pub weight_proof_timeout: usize, + #[serde(default = "full_node_max_sync_wait")] + pub max_sync_wait: usize, + #[serde(default)] + pub enable_profiler: bool, + #[serde(default)] + pub enable_memory_profiler: bool, + #[serde(default)] + pub log_sqlite_cmds: bool, + #[serde(default = "full_node_max_subscribe_items")] + pub max_subscribe_items: usize, + #[serde(default = "full_node_max_subscribe_response_items")] + pub max_subscribe_response_items: usize, + #[serde(default = "full_node_trusted_max_subscribe_items")] + pub trusted_max_subscribe_items: usize, + #[serde(default = "full_node_trusted_max_subscribe_response_items")] + pub trusted_max_subscribe_response_items: usize, + #[serde(default = "dns_servers")] + pub dns_servers: Vec, + #[serde(default = "introducer_peer")] + pub introducer_peer: IntroducerPeer, + #[serde(default = "logging")] + pub logging: LoggingConfig, + #[serde(default = "network_overrides")] + pub network_overrides: NetworkOverrides, + #[serde(default = "selected_network")] + pub selected_network: String, + #[serde(default)] + pub trusted_peers: HashMap, + #[serde(default = "full_node_ssl")] + pub ssl: CombinedSsl, + #[serde(default = "default_true")] + pub use_chia_loop_policy: bool, +} +impl Default for FullnodeConfig { + fn default() -> Self { + FullnodeConfig { + port: full_node_port(), + db_sync: full_node_db_sync(), + db_readers: full_node_db_readers(), + database_path: full_node_database_path(), + peer_db_path: full_node_peer_db_path(), + peers_file_path: full_node_peers_file_path(), + multiprocessing_start_method: multiprocessing_start_method(), + start_rpc_server: true, + rpc_port: full_node_rpc_port(), + enable_upnp: true, + sync_blocks_behind_threshold: full_node_sync_blocks_behind_threshold(), + short_sync_blocks_behind_threshold: full_node_short_sync_blocks_behind_threshold(), + bad_peak_cache_size: full_node_bad_peak_cache_size(), + reserved_cores: 0, + single_threaded: false, + peer_connect_interval: full_node_peer_connect_interval(), + peer_connect_timeout: full_node_peer_connect_timeout(), + target_peer_count: full_node_target_peer_count(), + target_outbound_peer_count: full_node_target_outbound_peer_count(), + exempt_peer_networks: vec![], + max_inbound_wallet: full_node_max_inbound_wallet(), + max_inbound_farmer: full_node_max_inbound_farmer(), + max_inbound_timelord: full_node_max_inbound_timelord(), + recent_peer_threshold: full_node_recent_peer_threshold(), + send_uncompact_interval: 0, + target_uncompact_proofs: full_node_target_uncompact_proofs(), + sanitize_weight_proof_only: false, + weight_proof_timeout: full_node_weight_proof_timeout(), + max_sync_wait: full_node_max_sync_wait(), + enable_profiler: false, + enable_memory_profiler: false, + log_sqlite_cmds: false, + max_subscribe_items: full_node_max_subscribe_items(), + max_subscribe_response_items: full_node_max_subscribe_response_items(), + trusted_max_subscribe_items: full_node_trusted_max_subscribe_items(), + trusted_max_subscribe_response_items: full_node_trusted_max_subscribe_response_items(), + dns_servers: dns_servers(), + introducer_peer: introducer_peer(), + logging: logging(), + network_overrides: network_overrides(), + selected_network: selected_network(), + trusted_peers: Default::default(), + ssl: full_node_ssl(), + use_chia_loop_policy: true, + } + } +} + +#[derive(Default, Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub struct VdfClients { + pub ip: Vec, + pub ips_estimate: usize, +} + +#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub struct TimelordConfig { + #[serde(default = "vdf_clients")] + pub vdf_clients: VdfClients, + #[serde(default = "full_node_peers")] + pub full_node_peers: Vec, + #[serde(default = "timelord_max_connection_time")] + pub max_connection_time: usize, + #[serde(default = "vdf_server")] + pub vdf_server: PeerConfig, + #[serde(default = "logging")] + pub logging: LoggingConfig, + #[serde(default = "network_overrides")] + pub network_overrides: NetworkOverrides, + #[serde(default = "selected_network")] + pub selected_network: String, + #[serde(default)] + pub fast_algorithm: bool, + #[serde(default)] + pub bluebox_mode: bool, + #[serde(default)] + pub slow_bluebox: bool, + #[serde(default = "timelord_slow_bluebox_process_count")] + pub slow_bluebox_process_count: usize, + #[serde(default = "multiprocessing_start_method")] + pub multiprocessing_start_method: String, + #[serde(default = "default_true")] + pub start_rpc_server: bool, + #[serde(default = "timelord_rpc_port")] + pub rpc_port: u16, + #[serde(default = "timelord_ssl")] + pub ssl: CombinedSsl, +} +impl Default for TimelordConfig { + fn default() -> Self { + TimelordConfig { + vdf_clients: vdf_clients(), + full_node_peers: full_node_peers(), + max_connection_time: timelord_max_connection_time(), + vdf_server: vdf_server(), + logging: logging(), + network_overrides: network_overrides(), + selected_network: selected_network(), + fast_algorithm: false, + bluebox_mode: false, + slow_bluebox: false, + slow_bluebox_process_count: timelord_slow_bluebox_process_count(), + multiprocessing_start_method: multiprocessing_start_method(), + start_rpc_server: true, + rpc_port: timelord_rpc_port(), + ssl: timelord_ssl(), + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub struct TimelordLauncherConfig { + #[serde(default = "self_hostname")] + pub host: String, + pub port: u16, + pub process_count: usize, + #[serde(default = "logging")] + pub logging: LoggingConfig, +} +impl Default for TimelordLauncherConfig { + fn default() -> Self { + TimelordLauncherConfig { + host: self_hostname(), + port: 8000, + process_count: 3, + logging: logging(), + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub struct FarmerConfig { + #[serde(default = "full_node_peers")] + pub full_node_peers: Vec, + #[serde(default = "farmer_port")] + pub port: u16, + #[serde(default)] + pub pool_public_keys: Vec, + #[serde(default)] + pub xch_target_address: Bytes32, + #[serde(default = "default_true")] + pub start_rpc_server: bool, + #[serde(default = "farmer_rpc_port")] + pub rpc_port: u16, + #[serde(default = "farmer_pool_share_threshold")] + pub pool_share_threshold: usize, + #[serde(default = "network_overrides")] + pub network_overrides: NetworkOverrides, + #[serde(default = "selected_network")] + pub selected_network: String, + #[serde(default = "logging")] + pub logging: LoggingConfig, + #[serde(default = "farmer_ssl")] + pub ssl: CombinedSsl, +} +impl Default for FarmerConfig { + fn default() -> Self { + FarmerConfig { + full_node_peers: full_node_peers(), + port: farmer_port(), + pool_public_keys: vec![], + xch_target_address: Default::default(), + start_rpc_server: true, + rpc_port: farmer_rpc_port(), + pool_share_threshold: farmer_pool_share_threshold(), + logging: logging(), + network_overrides: network_overrides(), + selected_network: selected_network(), + ssl: farmer_ssl(), + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub struct PoolWalletConfig { + #[serde(default)] + pub launcher_id: Bytes32, + #[serde(default)] + pub pool_url: String, + #[serde(default)] + pub target_puzzle_hash: Bytes32, + #[serde(default)] + pub payout_instructions: String, + #[serde(default)] + pub p2_singleton_puzzle_hash: Bytes32, + #[serde(default)] + pub owner_public_key: Bytes48, +} + +#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub struct PoolConfig { + #[serde(default = "network_overrides")] + pub network_overrides: NetworkOverrides, + #[serde(default = "selected_network")] + pub selected_network: String, + #[serde(default = "logging")] + pub logging: LoggingConfig, + #[serde(default)] + pub xch_target_address: Bytes32, + #[serde(default)] + pub pool_list: Vec, +} +impl Default for PoolConfig { + fn default() -> Self { + PoolConfig { + logging: logging(), + network_overrides: network_overrides(), + selected_network: selected_network(), + xch_target_address: Default::default(), + pool_list: vec![], + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub struct PlotRefreshParameter { + #[serde(default)] + pub interval_seconds: usize, + #[serde(default)] + pub retry_invalid_seconds: usize, + #[serde(default)] + pub batch_size: usize, + #[serde(default)] + pub batch_sleep_milliseconds: usize, +} +impl Default for PlotRefreshParameter { + fn default() -> Self { + PlotRefreshParameter { + interval_seconds: 120, + retry_invalid_seconds: 1200, + batch_size: 300, + batch_sleep_milliseconds: 1, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub struct HarvesterConfig { + #[serde(default = "harvester_farmer_peers")] + pub farmer_peers: Vec, + #[serde(default = "default_true")] + pub start_rpc_server: bool, + #[serde(default = "harvester_rpc_port")] + pub rpc_port: u16, + #[serde(default = "harvester_num_threads")] + pub num_threads: usize, + #[serde(default = "plots_refresh_parameter")] + pub plots_refresh_parameter: PlotRefreshParameter, + #[serde(default = "default_true")] + pub parallel_read: bool, + #[serde(default = "network_overrides")] + pub network_overrides: NetworkOverrides, + #[serde(default = "selected_network")] + pub selected_network: String, + #[serde(default = "logging")] + pub logging: LoggingConfig, + #[serde(default)] + pub plot_directories: Vec, + #[serde(default = "default_true")] + pub recursive_plot_scan: bool, + #[serde(default = "harvester_ssl")] + pub ssl: PrivateSsl, + #[serde(default = "private_ssl_ca")] + pub private_ssl_ca: CaSsl, + #[serde(default = "chia_ssl_ca")] + pub chia_ssl_ca: CaSsl, + #[serde(default)] + pub parallel_decompressor_count: u16, + #[serde(default)] + pub decompressor_thread_count: u16, + #[serde(default)] + pub disable_cpu_affinity: bool, + #[serde(default = "harvester_max_compression_level_allowed")] + pub max_compression_level_allowed: u8, + #[serde(default)] + pub use_gpu_harvesting: bool, + #[serde(default)] + pub gpu_index: u8, + #[serde(default)] + pub enforce_gpu_index: bool, + #[serde(default = "harvester_decompressor_timeout")] + pub decompressor_timeout: usize, +} +impl Default for HarvesterConfig { + fn default() -> Self { + HarvesterConfig { + farmer_peers: harvester_farmer_peers(), + start_rpc_server: true, + rpc_port: harvester_rpc_port(), + num_threads: harvester_num_threads(), + plots_refresh_parameter: Default::default(), + parallel_read: true, + logging: logging(), + network_overrides: network_overrides(), + selected_network: selected_network(), + plot_directories: vec![], + recursive_plot_scan: true, + ssl: harvester_ssl(), + private_ssl_ca: private_ssl_ca(), + chia_ssl_ca: chia_ssl_ca(), + parallel_decompressor_count: 0, + decompressor_thread_count: 0, + disable_cpu_affinity: false, + max_compression_level_allowed: harvester_max_compression_level_allowed(), + use_gpu_harvesting: false, + gpu_index: 0, + enforce_gpu_index: false, + decompressor_timeout: harvester_decompressor_timeout(), + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub struct CrawlerConfig { + #[serde(default = "default_true")] + pub start_rpc_server: bool, + #[serde(default = "crawler_port")] + pub rpc_port: u16, + #[serde(default = "crawler_ssl")] + pub ssl: PrivateSsl, +} +impl Default for CrawlerConfig { + fn default() -> Self { + CrawlerConfig { + start_rpc_server: true, + rpc_port: crawler_port(), + ssl: crawler_ssl(), + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub struct Soa { + pub rname: String, + pub serial_number: u32, + pub refresh: u32, + pub retry: u32, + pub expire: u32, + pub minimum: u32, +} +impl Default for Soa { + fn default() -> Self { + Soa { + rname: "hostmaster.example.com".to_string(), + serial_number: 1619105223, + refresh: 10800, + retry: 10800, + expire: 604800, + minimum: 1800, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub struct SeederConfig { + #[serde(default = "seeder_port")] + pub port: u16, + #[serde(default = "seeder_other_peers_port")] + pub other_peers_port: u16, + #[serde(default = "seeder_dns_port")] + pub dns_port: u16, + #[serde(default = "seeder_peer_connect_timeout")] + pub peer_connect_timeout: usize, + #[serde(default = "seeder_crawler_db_path")] + pub crawler_db_path: String, + #[serde(default = "seeder_bootstrap_peers")] + pub bootstrap_peers: Vec, + #[serde(default = "seeder_minimum_height")] + pub minimum_height: usize, + #[serde(default = "seeder_minimum_version_count")] + pub minimum_version_count: usize, + #[serde(default = "seeder_domain_name")] + pub domain_name: String, + #[serde(default = "seeder_nameserver")] + pub nameserver: String, + #[serde(default = "seeder_ttl")] + pub ttl: usize, + #[serde(default)] + pub soa: Soa, + #[serde(default = "network_overrides")] + pub network_overrides: NetworkOverrides, + #[serde(default = "selected_network")] + pub selected_network: String, + #[serde(default = "logging")] + pub logging: LoggingConfig, + #[serde(default = "crawler")] + pub crawler: CrawlerConfig, +} +impl Default for SeederConfig { + fn default() -> Self { + SeederConfig { + port: seeder_port(), + other_peers_port: seeder_other_peers_port(), + dns_port: seeder_dns_port(), + peer_connect_timeout: seeder_peer_connect_timeout(), + crawler_db_path: seeder_crawler_db_path(), + bootstrap_peers: seeder_bootstrap_peers(), + minimum_height: seeder_minimum_height(), + minimum_version_count: seeder_minimum_version_count(), + domain_name: seeder_domain_name(), + nameserver: seeder_nameserver(), + ttl: seeder_ttl(), + soa: Default::default(), + network_overrides: network_overrides(), + selected_network: selected_network(), + logging: logging(), + crawler: crawler(), + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub struct LoggingConfig { + pub log_stdout: bool, + pub log_filename: String, + pub log_level: String, + pub log_maxfilesrotation: u8, + pub log_maxbytesrotation: usize, + pub log_use_gzip: bool, + pub log_syslog: bool, + pub log_syslog_host: String, + pub log_syslog_port: u16, +} +impl Default for LoggingConfig { + fn default() -> Self { + LoggingConfig { + log_stdout: false, + log_filename: "log/debug.log".to_string(), + log_level: "WARNING".to_string(), + log_maxfilesrotation: 7, + log_maxbytesrotation: 52428800, + log_use_gzip: false, + log_syslog: false, + log_syslog_host: "localhost".to_string(), + log_syslog_port: 514, + } + } +} + +#[derive(Default, Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub struct ConfigOverride { + pub address_prefix: Option, + pub default_full_node_port: Option, +} + +#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub struct ConfigOverrides { + pub mainnet: Option, + pub testnet0: Option, + pub testnet2: Option, + pub testnet3: Option, + pub testnet4: Option, + pub testnet5: Option, + pub testnet7: Option, + pub testnet10: Option, + pub testnet11: Option, +} +impl Default for ConfigOverrides { + fn default() -> Self { + ConfigOverrides { + mainnet: Some(ConfigOverride { + address_prefix: Some("xch".to_string()), + default_full_node_port: Some(8444), + }), + testnet0: Some(ConfigOverride { + address_prefix: Some("txch".to_string()), + default_full_node_port: Some(58444), + }), + testnet2: Some(ConfigOverride { + address_prefix: Some("txch".to_string()), + default_full_node_port: None, + }), + testnet3: Some(ConfigOverride { + address_prefix: Some("txch".to_string()), + default_full_node_port: None, + }), + testnet4: Some(ConfigOverride { + address_prefix: Some("txch".to_string()), + default_full_node_port: None, + }), + testnet5: Some(ConfigOverride { + address_prefix: Some("txch".to_string()), + default_full_node_port: None, + }), + testnet7: Some(ConfigOverride { + address_prefix: Some("txch".to_string()), + default_full_node_port: Some(58444), + }), + testnet10: Some(ConfigOverride { + address_prefix: Some("txch".to_string()), + default_full_node_port: Some(58444), + }), + testnet11: Some(ConfigOverride { + address_prefix: Some("txch".to_string()), + default_full_node_port: Some(58444), + }), + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub struct ConstantsOverrides { + pub mainnet: Option, + pub testnet0: Option, + pub testnet2: Option, + pub testnet3: Option, + pub testnet4: Option, + pub testnet5: Option, + pub testnet7: Option, + pub testnet10: Option, + pub testnet11: Option, +} +impl Default for ConstantsOverrides { + fn default() -> Self { + ConstantsOverrides { + mainnet: Some(ConsensusOverrides { + genesis_challenge: Some(Bytes32::from( + "ccd5bb71183532bff220ba46c268991a3ff07eb358e8255a65c30a2dce0e5fbb", + )), + genesis_pre_farm_farmer_puzzle_hash: Some(Bytes32::from( + "3d8765d3a597ec1d99663f6c9816d915b9f68613ac94009884c4addaefcce6af", + )), + genesis_pre_farm_pool_puzzle_hash: Some(Bytes32::from( + "d23da14695a188ae5708dd152263c4db883eb27edeb936178d4d988b8f3ce5fc", + )), + ..Default::default() + }), + testnet0: Some(ConsensusOverrides { + genesis_challenge: Some(Bytes32::from( + "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + )), + genesis_pre_farm_farmer_puzzle_hash: Some(Bytes32::from( + "3d8765d3a597ec1d99663f6c9816d915b9f68613ac94009884c4addaefcce6af", + )), + genesis_pre_farm_pool_puzzle_hash: Some(Bytes32::from( + "d23da14695a188ae5708dd152263c4db883eb27edeb936178d4d988b8f3ce5fc", + )), + min_plot_size: Some(18), + ..Default::default() + }), + testnet2: Some(ConsensusOverrides { + difficulty_constant_factor: Some(10052721566054), + genesis_challenge: Some(Bytes32::from( + "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad", + )), + genesis_pre_farm_farmer_puzzle_hash: Some(Bytes32::from( + "3d8765d3a597ec1d99663f6c9816d915b9f68613ac94009884c4addaefcce6af", + )), + genesis_pre_farm_pool_puzzle_hash: Some(Bytes32::from( + "d23da14695a188ae5708dd152263c4db883eb27edeb936178d4d988b8f3ce5fc", + )), + min_plot_size: Some(18), + ..Default::default() + }), + testnet3: Some(ConsensusOverrides { + difficulty_constant_factor: Some(10052721566054), + genesis_challenge: Some(Bytes32::from( + "ca7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015af", + )), + genesis_pre_farm_farmer_puzzle_hash: Some(Bytes32::from( + "3d8765d3a597ec1d99663f6c9816d915b9f68613ac94009884c4addaefcce6af", + )), + genesis_pre_farm_pool_puzzle_hash: Some(Bytes32::from( + "d23da14695a188ae5708dd152263c4db883eb27edeb936178d4d988b8f3ce5fc", + )), + mempool_block_buffer: Some(BigInt::from(10)), + min_plot_size: Some(18), + ..Default::default() + }), + testnet4: Some(ConsensusOverrides { + difficulty_constant_factor: Some(10052721566054), + difficulty_starting: Some(30), + epoch_blocks: Some(768), + genesis_challenge: Some(Bytes32::from( + "dd7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015af", + )), + genesis_pre_farm_pool_puzzle_hash: Some(Bytes32::from( + "d23da14695a188ae5708dd152263c4db883eb27edeb936178d4d988b8f3ce5fc", + )), + genesis_pre_farm_farmer_puzzle_hash: Some(Bytes32::from( + "3d8765d3a597ec1d99663f6c9816d915b9f68613ac94009884c4addaefcce6af", + )), + mempool_block_buffer: Some(BigInt::from(10)), + min_plot_size: Some(18), + ..Default::default() + }), + testnet5: Some(ConsensusOverrides { + difficulty_constant_factor: Some(10052721566054), + difficulty_starting: Some(30), + epoch_blocks: Some(768), + genesis_challenge: Some(Bytes32::from( + "ee7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015af", + )), + genesis_pre_farm_pool_puzzle_hash: Some(Bytes32::from( + "d23da14695a188ae5708dd152263c4db883eb27edeb936178d4d988b8f3ce5fc", + )), + genesis_pre_farm_farmer_puzzle_hash: Some(Bytes32::from( + "3d8765d3a597ec1d99663f6c9816d915b9f68613ac94009884c4addaefcce6af", + )), + mempool_block_buffer: Some(BigInt::from(10)), + min_plot_size: Some(18), + ..Default::default() + }), + testnet7: Some(ConsensusOverrides { + difficulty_constant_factor: Some(10052721566054), + difficulty_starting: Some(30), + epoch_blocks: Some(768), + genesis_challenge: Some(Bytes32::from( + "117816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015af", + )), + genesis_pre_farm_pool_puzzle_hash: Some(Bytes32::from( + "d23da14695a188ae5708dd152263c4db883eb27edeb936178d4d988b8f3ce5fc", + )), + genesis_pre_farm_farmer_puzzle_hash: Some(Bytes32::from( + "3d8765d3a597ec1d99663f6c9816d915b9f68613ac94009884c4addaefcce6af", + )), + mempool_block_buffer: Some(BigInt::from(50)), + min_plot_size: Some(18), + ..Default::default() + }), + testnet10: Some(ConsensusOverrides { + agg_sig_me_additional_data: Some(Bytes32::from( + "ae83525ba8d1dd3f09b277de18ca3e43fc0af20d20c4b3e92ef2a48bd291ccb2", + )), + difficulty_constant_factor: Some(10052721566054), + difficulty_starting: Some(30), + epoch_blocks: Some(768), + genesis_challenge: Some(Bytes32::from( + "ccd5bb71183532bff220ba46c268991a3ff07eb358e8255a65c30a2dce0e5fbb", + )), + genesis_pre_farm_pool_puzzle_hash: Some(Bytes32::from( + "d23da14695a188ae5708dd152263c4db883eb27edeb936178d4d988b8f3ce5fc", + )), + genesis_pre_farm_farmer_puzzle_hash: Some(Bytes32::from( + "3d8765d3a597ec1d99663f6c9816d915b9f68613ac94009884c4addaefcce6af", + )), + mempool_block_buffer: Some(BigInt::from(10)), + min_plot_size: Some(18), + soft_fork2_height: Some(3000000), + hard_fork_height: Some(2997292), + hard_fork_fix_height: Some(3426000), + plot_filter_128_height: Some(3061804), + plot_filter_64_height: Some(8010796), + plot_filter_32_height: Some(13056556), + ..Default::default() + }), + testnet11: Some(ConsensusOverrides { + agg_sig_me_additional_data: Some(Bytes32::from( + "37a90eb5185a9c4439a91ddc98bbadce7b4feba060d50116a067de66bf236615", + )), + difficulty_constant_factor: Some(10052721566054), + difficulty_starting: Some(30), + epoch_blocks: Some(768), + genesis_challenge: Some(Bytes32::from( + "ccd5bb71183532bff220ba46c268991a3ff07eb358e8255a65c30a2dce0e5fbb", + )), + genesis_pre_farm_pool_puzzle_hash: Some(Bytes32::from( + "d23da14695a188ae5708dd152263c4db883eb27edeb936178d4d988b8f3ce5fc", + )), + genesis_pre_farm_farmer_puzzle_hash: Some(Bytes32::from( + "3d8765d3a597ec1d99663f6c9816d915b9f68613ac94009884c4addaefcce6af", + )), + mempool_block_buffer: Some(BigInt::from(10)), + min_plot_size: Some(18), + hard_fork_height: Some(0), + hard_fork_fix_height: Some(0), + plot_filter_128_height: Some(6029568), + plot_filter_64_height: Some(11075328), + plot_filter_32_height: Some(16121088), + ..Default::default() + }), + } + } +} + +#[derive(Default, Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub struct NetworkOverrides { + pub constants: ConstantsOverrides, + pub config: ConfigOverrides, +} + +#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub struct PeerConfig { + pub host: String, + pub port: u16, +} + +#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub struct CombinedSsl { + pub private_crt: String, + pub private_key: String, + pub public_crt: String, + pub public_key: String, +} +#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub struct CaSsl { + pub crt: String, + pub key: String, +} + +#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub struct PublicSsl { + pub public_crt: String, + pub public_key: String, +} + +#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub struct PrivateSsl { + pub private_crt: String, + pub private_key: String, +} diff --git a/core/src/consensus/constants.rs b/core/src/consensus/constants.rs index f586dde..d6f2e6c 100644 --- a/core/src/consensus/constants.rs +++ b/core/src/consensus/constants.rs @@ -3,7 +3,7 @@ use num_bigint::BigInt; use once_cell::sync::Lazy; use std::collections::HashMap; -#[derive(Clone, Debug)] +#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub struct ConsensusConstants { pub slot_blocks_target: u32, //How many blocks to target per sub-slot pub min_blocks_per_challenge_block: u8, //How many blocks must be created per slot (to make challenge sb) diff --git a/core/src/consensus/mod.rs b/core/src/consensus/mod.rs index b0bb9e4..1ff9197 100644 --- a/core/src/consensus/mod.rs +++ b/core/src/consensus/mod.rs @@ -1,3 +1,4 @@ pub mod coinbase; pub mod constants; +pub mod overrides; pub mod pot_iterations; diff --git a/core/src/consensus/overrides.rs b/core/src/consensus/overrides.rs new file mode 100644 index 0000000..b0a0d4c --- /dev/null +++ b/core/src/consensus/overrides.rs @@ -0,0 +1,51 @@ +use crate::blockchain::sized_bytes::Bytes32; +use num_bigint::BigInt; + +#[derive(Default, Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub struct ConsensusOverrides { + pub slot_blocks_target: Option, + pub min_blocks_per_challenge_block: Option, + pub max_sub_slot_blocks: Option, + pub num_sps_sub_slot: Option, + pub sub_slot_iters_starting: Option, + pub difficulty_constant_factor: Option, + pub difficulty_starting: Option, + pub difficulty_change_max_factor: Option, + pub sub_epoch_blocks: Option, + pub epoch_blocks: Option, + pub significant_bits: Option, + pub discriminant_size_bits: Option, + pub number_zero_bits_plot_filter: Option, + pub min_plot_size: Option, + pub max_plot_size: Option, + pub sub_slot_time_target: Option, + pub num_sp_intervals_extra: Option, + pub max_future_time: Option, + pub max_future_time2: Option, + pub number_of_timestamps: Option, + pub genesis_challenge: Option, + pub agg_sig_me_additional_data: Option, + pub genesis_pre_farm_pool_puzzle_hash: Option, + pub genesis_pre_farm_farmer_puzzle_hash: Option, + pub max_vdf_witness_size: Option, + pub mempool_block_buffer: Option, + pub max_coin_amount: Option, + pub max_block_cost_clvm: Option, + pub cost_per_byte: Option, + pub weight_proof_threshold: Option, + pub weight_proof_recent_blocks: Option, + pub max_block_count_per_requests: Option, + pub blocks_cache_size: Option, + pub max_generator_size: Option, + pub max_generator_ref_list_size: Option, + pub pool_sub_slot_iters: Option, + pub soft_fork2_height: Option, + pub soft_fork3_height: Option, + pub hard_fork_height: Option, + pub hard_fork_fix_height: Option, + pub plot_filter_128_height: Option, + pub plot_filter_64_height: Option, + pub plot_filter_32_height: Option, + pub bech32_prefix: Option, + pub is_testnet: Option, +} diff --git a/core/src/lib.rs b/core/src/lib.rs index 2489b89..c9f472f 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -1,11 +1,14 @@ pub mod blockchain; pub mod clvm; +pub mod config; pub mod consensus; pub mod curry_and_treehash; pub mod errors; pub mod plots; pub mod pool; +pub mod protocols; pub mod ssl; +pub mod utils; fn _version() -> &'static str { env!("CARGO_PKG_VERSION") diff --git a/core/src/plots.rs b/core/src/plots.rs index 2fc7f44..6cc288e 100644 --- a/core/src/plots.rs +++ b/core/src/plots.rs @@ -43,51 +43,41 @@ impl PlotTable { } pub trait PlotFile<'a, F: AsyncSeek + AsyncRead> { - fn k(&'a self) -> &'a u8 { + fn table_address(&'a self, plot_table: &PlotTable) -> u64 { match self.header() { - PlotHeader::V1(h) => &h.k, - PlotHeader::V2(h) => &h.k, - } - } - fn plot_id(&'a self) -> &'a Bytes32 { - match self.header() { - PlotHeader::V1(h) => &h.id, - PlotHeader::V2(h) => &h.id, - } - } - fn table_address(&'a self, plot_table: &PlotTable) -> &'a u64 { - match self.header() { - PlotHeader::V1(h) => &h.table_begin_pointers[*plot_table as usize], - PlotHeader::V2(h) => &h.table_begin_pointers[*plot_table as usize], + PlotHeader::V1(h) => h.table_begin_pointers[*plot_table as usize], + PlotHeader::V2(h) => h.table_begin_pointers[*plot_table as usize], + PlotHeader::GHv2_5(_) => 0, } } fn table_size(&'a self, plot_table: &PlotTable) -> u64 { let table_pointers = match self.header() { PlotHeader::V1(h) => &h.table_begin_pointers, PlotHeader::V2(h) => &h.table_begin_pointers, + PlotHeader::GHv2_5(_) => { + return 0; + } }; - let address = &table_pointers[*plot_table as usize]; - let mut end_address = self.plot_size(); - for a in table_pointers { - if a > address && a < end_address { - end_address = a; + let address = table_pointers[*plot_table as usize]; + if let Some(next) = table_pointers.get(*plot_table as usize + 1) { + if *next > address { + return next - address; } } - end_address - address + self.plot_size() - address + } + fn k(&'a self) -> u8 { + self.header().k() + } + fn plot_id(&'a self) -> Bytes32 { + self.header().id() } fn memo(&'a self) -> &'a PlotMemo { - match self.header() { - PlotHeader::V1(h) => &h.memo, - PlotHeader::V2(h) => &h.memo, - } + self.header().memo() } - fn compression_level(&'a self) -> &'a u8 { - match self.header() { - PlotHeader::V1(_) => &0, - PlotHeader::V2(h) => &h.compression_level, - } + fn compression_level(&'a self) -> u8 { + self.header().compression_level() } - //The Interface stuff fn header(&'a self) -> &'a PlotHeader; fn plot_size(&'a self) -> &'a u64; @@ -158,6 +148,109 @@ impl Display for PlotMemo { pub enum PlotHeader { V1(PlotHeaderV1), V2(PlotHeaderV2), + GHv2_5(PlotHeaderGHv2_5), +} +impl PlotHeader { + pub fn magic(&self) -> Vec { + match self { + PlotHeader::V1(h) => h.magic.to_vec(), + PlotHeader::V2(h) => h.magic.to_vec(), + PlotHeader::GHv2_5(h) => h.magic.to_vec(), + } + } + pub fn id(&self) -> Bytes32 { + match self { + PlotHeader::V1(h) => h.id, + PlotHeader::V2(h) => h.id, + PlotHeader::GHv2_5(h) => h.id, + } + } + pub fn k(&self) -> u8 { + match self { + PlotHeader::V1(h) => h.k, + PlotHeader::V2(h) => h.k, + PlotHeader::GHv2_5(h) => h.k, + } + } + pub fn memo_len(&self) -> u16 { + match self { + PlotHeader::V1(h) => h.memo_len, + PlotHeader::V2(h) => h.memo_len, + PlotHeader::GHv2_5(h) => h.memo_len, + } + } + pub fn memo(&self) -> &PlotMemo { + match self { + PlotHeader::V1(h) => &h.memo, + PlotHeader::V2(h) => &h.memo, + PlotHeader::GHv2_5(h) => &h.memo, + } + } + pub fn format_desc_len(&self) -> u16 { + match self { + PlotHeader::V1(h) => h.format_desc_len, + PlotHeader::V2(_) => 0, + PlotHeader::GHv2_5(h) => h.format_desc_len, + } + } + pub fn format_desc(&self) -> &[u8] { + match self { + PlotHeader::V1(h) => &h.format_desc, + PlotHeader::V2(_) => &[], + PlotHeader::GHv2_5(h) => &h.format_desc, + } + } + pub fn plot_flags(&self) -> u32 { + match self { + PlotHeader::V1(_) => 0, + PlotHeader::V2(h) => h.plot_flags, + PlotHeader::GHv2_5(_) => 0, + } + } + pub fn compression_level(&self) -> u8 { + match self { + PlotHeader::V1(_) => 0, + PlotHeader::V2(h) => h.compression_level, + PlotHeader::GHv2_5(h) => h.compression_level, + } + } +} + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct PlotHeaderV1 { + pub magic: [u8; 19], + pub id: Bytes32, + pub k: u8, + pub format_desc_len: u16, + pub format_desc: Vec, + pub memo_len: u16, + pub memo: PlotMemo, + pub table_begin_pointers: [u64; 10], +} +impl PlotHeaderV1 { + pub fn new() -> Self { + PlotHeaderV1 { + magic: [0; 19], + id: [0; 32].into(), + k: 0, + format_desc_len: 0, + format_desc: vec![], + memo_len: 0, + memo: PlotMemo { + pool_public_key: None, + pool_contract_puzzle_hash: None, + farmer_public_key: [0; 48].into(), + local_master_secret_key: [0; 32].into(), + }, + table_begin_pointers: [0; 10], + } + } +} + +impl Default for PlotHeaderV1 { + fn default() -> Self { + Self::new() + } } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] @@ -201,24 +294,22 @@ impl Default for PlotHeaderV2 { } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -pub struct PlotHeaderV1 { - pub magic: [u8; 19], +pub struct PlotHeaderGHv2_5 { + pub magic: [u8; 4], pub id: Bytes32, pub k: u8, pub format_desc_len: u16, pub format_desc: Vec, pub memo_len: u16, pub memo: PlotMemo, - pub table_begin_pointers: [u64; 10], + pub compression_level: u8, } -impl PlotHeaderV1 { +impl PlotHeaderGHv2_5 { pub fn new() -> Self { - PlotHeaderV1 { - magic: [0; 19], + PlotHeaderGHv2_5 { + magic: [0; 4], id: [0; 32].into(), k: 0, - format_desc_len: 0, - format_desc: vec![], memo_len: 0, memo: PlotMemo { pool_public_key: None, @@ -226,40 +317,17 @@ impl PlotHeaderV1 { farmer_public_key: [0; 48].into(), local_master_secret_key: [0; 32].into(), }, - table_begin_pointers: [0; 10], + format_desc_len: 0, + format_desc: vec![], + compression_level: 0, } } } -impl Default for PlotHeaderV1 { +impl Default for PlotHeaderGHv2_5 { fn default() -> Self { Self::new() } } -impl Display for PlotHeaderV1 { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!( - f, - "{{\n\ - \t\"magic\": {:?},\n\ - \t\"id\": {:?},\n\ - \t\"k\": {},\n\ - \t\"format_desc_len\": {},\n\ - \t\"format_desc\": {:?},\n\ - \t\"memo_len\": {},\n\ - \t\"memo\": {}\n\ - }}", - String::from_utf8(self.magic.to_vec()).map_err(|_| fmt::Error)?, - encode(self.id), - self.k, - self.format_desc_len, - String::from_utf8(self.format_desc.to_vec()).map_err(|_| fmt::Error)?, - self.memo_len, - format!("{}", self.memo) - .replace('\t', "\t\t") - .replace('}', "\t}") - ) - } -} #[derive(Debug, Serialize, Deserialize)] pub struct PlotNft { @@ -278,7 +346,6 @@ pub struct PlotNftExtraData { impl PlotNftExtraData { pub fn from_program(program: Program) -> Result { let pool_state = PoolState::from_extra_data_program(&program)?; - let extra_data_program_list = program.as_list(); let delay_time_programs: Vec = extra_data_program_list .iter() @@ -298,7 +365,6 @@ impl PlotNftExtraData { return Err(Error::new(ErrorKind::InvalidInput, "Invalid PlotNFT")); } let delay_time = delay_time_programs[0].rest()?.as_int()?; - let extra_data_programs: Vec = extra_data_program_list .into_iter() .filter(|p| { diff --git a/core/src/protocols/error.rs b/core/src/protocols/error.rs new file mode 100644 index 0000000..d10da25 --- /dev/null +++ b/core/src/protocols/error.rs @@ -0,0 +1,35 @@ +use serde::Serialize; +use std::collections::VecDeque; +use std::time::{Duration, SystemTime}; + +#[derive(Default, Clone)] +pub struct RecentErrors { + depth: usize, + cache_duration: Duration, + errors: VecDeque<(T, SystemTime)>, +} +impl RecentErrors { + pub fn new(depth: usize, cache_duration: Duration) -> Self { + Self { + depth, + cache_duration, + errors: Default::default(), + } + } + pub fn add(&mut self, t: T) { + self.errors.push_front((t, SystemTime::now())); + self.trim(); + } + pub fn get(&mut self) -> Vec<(T, SystemTime)> { + self.trim(); + self.errors.iter().cloned().collect() + } + pub fn trim(&mut self) { + self.errors.truncate(self.depth); + self.errors + .retain(|(_, d)| match SystemTime::now().duration_since(*d) { + Ok(dur) => dur < self.cache_duration, + Err(_) => false, + }); + } +} diff --git a/core/src/protocols/farmer.rs b/core/src/protocols/farmer.rs new file mode 100644 index 0000000..fa398e0 --- /dev/null +++ b/core/src/protocols/farmer.rs @@ -0,0 +1,276 @@ +use crate::blockchain::pool_target::PoolTarget; +use crate::blockchain::proof_of_space::ProofOfSpace; +use crate::blockchain::sized_bytes::{Bytes32, Bytes48, Bytes96}; +use crate::config::PoolWalletConfig; +use crate::protocols::error::RecentErrors; +use crate::protocols::PeerMap; +use blst::min_pk::SecretKey; +use dg_xch_macros::ChiaSerial; +use hyper::body::Buf; +use log::debug; +#[cfg(feature = "metrics")] +use prometheus::core::{AtomicU64, GenericCounter, GenericGauge}; +#[cfg(feature = "metrics")] +use prometheus::Registry; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::sync::Arc; +use std::time::Instant; +use tokio::sync::Mutex; + +#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)] +pub struct NewSignagePoint { + pub challenge_hash: Bytes32, + pub challenge_chain_sp: Bytes32, + pub reward_chain_sp: Bytes32, + pub difficulty: u64, + pub sub_slot_iters: u64, + pub signage_point_index: u8, + pub peak_height: u32, +} + +impl dg_xch_serialize::ChiaSerialize for NewSignagePoint { + fn to_bytes(&self) -> Vec { + let mut bytes = vec![]; + bytes.extend(dg_xch_serialize::ChiaSerialize::to_bytes( + &self.challenge_hash, + )); + bytes.extend(dg_xch_serialize::ChiaSerialize::to_bytes( + &self.challenge_chain_sp, + )); + bytes.extend(dg_xch_serialize::ChiaSerialize::to_bytes( + &self.reward_chain_sp, + )); + bytes.extend(dg_xch_serialize::ChiaSerialize::to_bytes(&self.difficulty)); + bytes.extend(dg_xch_serialize::ChiaSerialize::to_bytes( + &self.sub_slot_iters, + )); + bytes.extend(dg_xch_serialize::ChiaSerialize::to_bytes( + &self.signage_point_index, + )); + bytes.extend(dg_xch_serialize::ChiaSerialize::to_bytes(&self.peak_height)); + bytes + } + fn from_bytes>(bytes: &mut std::io::Cursor) -> Result + where + Self: Sized, + { + let challenge_hash = dg_xch_serialize::ChiaSerialize::from_bytes(bytes)?; + let challenge_chain_sp = dg_xch_serialize::ChiaSerialize::from_bytes(bytes)?; + let reward_chain_sp = dg_xch_serialize::ChiaSerialize::from_bytes(bytes)?; + let difficulty = dg_xch_serialize::ChiaSerialize::from_bytes(bytes)?; + let sub_slot_iters = dg_xch_serialize::ChiaSerialize::from_bytes(bytes)?; + let signage_point_index = dg_xch_serialize::ChiaSerialize::from_bytes(bytes)?; + let peak_height = if bytes.remaining() >= 4 { + //Maintain Compatibility with < Chia 2.X nodes for now + dg_xch_serialize::ChiaSerialize::from_bytes(bytes)? + } else { + debug!("You are connected to an old node version, Please update your Fullnode."); + 0u32 + }; + Ok(Self { + challenge_hash, + challenge_chain_sp, + reward_chain_sp, + difficulty, + sub_slot_iters, + signage_point_index, + peak_height, + }) + } +} + +#[derive(ChiaSerial, Clone, PartialEq, Eq, Serialize, Deserialize, Debug)] +pub struct DeclareProofOfSpace { + pub challenge_hash: Bytes32, + pub challenge_chain_sp: Bytes32, + pub signage_point_index: u8, + pub reward_chain_sp: Bytes32, + pub proof_of_space: ProofOfSpace, + pub challenge_chain_sp_signature: Bytes96, + pub reward_chain_sp_signature: Bytes96, + pub farmer_puzzle_hash: Bytes32, + pub pool_target: Option, + pub pool_signature: Option, +} + +#[derive(ChiaSerial, Clone, PartialEq, Eq, Serialize, Deserialize, Debug)] +pub struct RequestSignedValues { + pub quality_string: Bytes32, + pub foliage_block_data_hash: Bytes32, + pub foliage_transaction_block_hash: Bytes32, +} + +#[derive(ChiaSerial, Clone, PartialEq, Eq, Serialize, Deserialize, Debug)] +pub struct FarmingInfo { + pub challenge_hash: Bytes32, + pub sp_hash: Bytes32, + pub timestamp: u64, + pub passed: u32, + pub proofs: u32, + pub total_plots: u32, + pub lookup_time: u64, +} + +#[derive(ChiaSerial, Clone, PartialEq, Eq, Serialize, Deserialize, Debug)] +pub struct SignedValues { + pub quality_string: Bytes32, + pub foliage_block_data_signature: Bytes96, + pub foliage_transaction_block_signature: Bytes96, +} + +pub type ProofsMap = Arc>>>; + +#[derive(Debug)] +pub struct FarmerIdentifier { + pub plot_identifier: String, + pub challenge_hash: Bytes32, + pub sp_hash: Bytes32, + pub peer_node_id: Bytes32, +} + +#[derive(Debug, Clone)] +pub struct FarmerPoolState { + pub points_found_since_start: u64, + pub points_found_24h: Vec<(Instant, u64)>, + pub points_acknowledged_since_start: u64, + pub points_acknowledged_24h: Vec<(Instant, u64)>, + pub next_farmer_update: Instant, + pub next_pool_info_update: Instant, + pub current_points: u64, + pub current_difficulty: Option, + pub pool_config: Option, + pub pool_errors_24h: Vec<(Instant, String)>, + pub authentication_token_timeout: Option, +} +impl Default for FarmerPoolState { + fn default() -> Self { + Self { + points_found_since_start: 0, + points_found_24h: vec![], + points_acknowledged_since_start: 0, + points_acknowledged_24h: vec![], + next_farmer_update: Instant::now(), + next_pool_info_update: Instant::now(), + current_points: 0, + current_difficulty: None, + pool_config: None, + pool_errors_24h: vec![], + authentication_token_timeout: None, + } + } +} + +#[derive(Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize, Copy, Clone)] +pub enum FarmerRunningState { + #[default] + Starting, + NeedsConfig, + Running, + Stopped, + Failed, + PendingReload, + Migrating, +} + +pub struct MostRecentSignagePoint { + pub hash: Bytes32, + pub index: u8, + pub timestamp: Instant, +} +impl Default for MostRecentSignagePoint { + fn default() -> Self { + MostRecentSignagePoint { + hash: Default::default(), + index: 0, + timestamp: Instant::now(), + } + } +} + +#[derive(Default, Clone)] +pub struct FarmerSharedState { + pub quality_to_identifiers: Arc>>, + pub signage_points: Arc>>>, + pub pool_state: Arc>>, + pub cache_time: Arc>>, + pub proofs_of_space: ProofsMap, + pub farmer_public_keys: Arc>>, + pub farmer_private_keys: Arc>>, + pub pool_public_keys: Arc>>, + pub owner_secret_keys: Arc>>, + pub harvester_peers: PeerMap, + pub most_recent_sp: Arc>, + pub recent_errors: Arc>>, + pub running_state: Arc>, + #[cfg(feature = "metrics")] + pub metrics: Arc>>, +} + +#[cfg(feature = "metrics")] +#[derive(Debug, Clone)] +pub struct FarmerMetrics { + pub start_time: Arc, + pub uptime: Option>, + pub points_acknowledged_24h: Option>, + pub points_found_24h: Option>, + pub current_difficulty: Option>, + pub proofs_declared: Option>, + pub last_signage_point_index: Option>, +} +#[cfg(feature = "metrics")] +impl FarmerMetrics { + pub fn new(registry: &Registry) -> Self { + let uptime = GenericGauge::new("farmer_uptime", "Uptime of Farmer").map_or( + None, + |g: GenericGauge| { + registry.register(Box::new(g.clone())).unwrap_or(()); + Some(g) + }, + ); + let points_acknowledged_24h = GenericGauge::new( + "points_acknowledged_24h", + "Total points acknowledged by pool for all plot nfts", + ) + .map_or(None, |g: GenericGauge| { + registry.register(Box::new(g.clone())).unwrap_or(()); + Some(g) + }); + let points_found_24h = GenericGauge::new( + "points_found_24h", + "Total points fount for all plot nfts", + ) + .map_or(None, |g: GenericGauge| { + registry.register(Box::new(g.clone())).unwrap_or(()); + Some(g) + }); + let current_difficulty = GenericGauge::new("current_difficulty", "Current Difficulty") + .map_or(None, |g: GenericGauge| { + registry.register(Box::new(g.clone())).unwrap_or(()); + Some(g) + }); + let proofs_declared = + GenericCounter::new("proofs_declared", "Proofs of Space declared by this farmer") + .map_or(None, |g: GenericCounter| { + registry.register(Box::new(g.clone())).unwrap_or(()); + Some(g) + }); + let last_signage_point_index = GenericGauge::new( + "last_signage_point_index", + "Index of Last Signage Point", + ) + .map_or(None, |g: GenericGauge| { + registry.register(Box::new(g.clone())).unwrap_or(()); + Some(g) + }); + FarmerMetrics { + start_time: Arc::new(Instant::now()), + uptime, + points_acknowledged_24h, + points_found_24h, + current_difficulty, + proofs_declared, + last_signage_point_index, + } + } +} diff --git a/clients/src/protocols/full_node.rs b/core/src/protocols/full_node.rs similarity index 91% rename from clients/src/protocols/full_node.rs rename to core/src/protocols/full_node.rs index c271e37..f903e57 100644 --- a/clients/src/protocols/full_node.rs +++ b/core/src/protocols/full_node.rs @@ -1,12 +1,12 @@ -use dg_xch_core::blockchain::end_of_subslot_bundle::EndOfSubSlotBundle; -use dg_xch_core::blockchain::full_block::FullBlock; -use dg_xch_core::blockchain::peer_info::TimestampedPeerInfo; -use dg_xch_core::blockchain::sized_bytes::Bytes32; -use dg_xch_core::blockchain::spend_bundle::SpendBundle; -use dg_xch_core::blockchain::unfinished_block::UnfinishedBlock; -use dg_xch_core::blockchain::vdf_info::VdfInfo; -use dg_xch_core::blockchain::vdf_proof::VdfProof; -use dg_xch_core::blockchain::weight_proof::WeightProof; +use crate::blockchain::end_of_subslot_bundle::EndOfSubSlotBundle; +use crate::blockchain::full_block::FullBlock; +use crate::blockchain::peer_info::TimestampedPeerInfo; +use crate::blockchain::sized_bytes::Bytes32; +use crate::blockchain::spend_bundle::SpendBundle; +use crate::blockchain::unfinished_block::UnfinishedBlock; +use crate::blockchain::vdf_info::VdfInfo; +use crate::blockchain::vdf_proof::VdfProof; +use crate::blockchain::weight_proof::WeightProof; use dg_xch_macros::ChiaSerial; use serde::{Deserialize, Serialize}; diff --git a/clients/src/protocols/harvester.rs b/core/src/protocols/harvester.rs similarity index 60% rename from clients/src/protocols/harvester.rs rename to core/src/protocols/harvester.rs index d26247f..690b136 100644 --- a/clients/src/protocols/harvester.rs +++ b/core/src/protocols/harvester.rs @@ -1,8 +1,13 @@ -use dg_xch_core::blockchain::proof_of_space::ProofOfSpace; -use dg_xch_core::blockchain::sized_bytes::{Bytes32, Bytes48, Bytes96}; +use crate::blockchain::proof_of_space::ProofOfSpace; +use crate::blockchain::sized_bytes::{Bytes32, Bytes48, Bytes96}; use dg_xch_macros::ChiaSerial; use serde::{Deserialize, Serialize}; +use std::collections::{HashMap, HashSet}; use std::fmt::{Display, Formatter}; +#[cfg(feature = "metrics")] +use std::sync::Arc; +#[cfg(feature = "metrics")] +use std::time::Instant; #[derive(ChiaSerial, Clone, PartialEq, Eq, Serialize, Deserialize, Debug)] pub struct PoolDifficulty { @@ -139,3 +144,79 @@ pub struct PlotSyncResponse { pub message_type: i16, pub error: Option, } + +#[cfg(feature = "metrics")] +use prometheus::core::{AtomicU64, GenericGauge}; +#[cfg(feature = "metrics")] +use prometheus::Registry; + +#[derive(Debug, Default, Clone)] +pub struct HarvesterState { + pub og_plot_count: usize, + pub nft_plot_count: usize, + pub compressed_plot_count: usize, + pub invalid_plot_count: usize, + pub plot_space: u64, + #[cfg(feature = "metrics")] + pub metrics: Option, + pub missing_keys: HashSet, + pub missing_pool_info: HashMap, +} + +#[cfg(feature = "metrics")] +#[derive(Debug, Clone)] +pub struct HarvesterMetrics { + pub start_time: Arc, + pub uptime: Option>, + pub reported_space: Option>, + pub og_plot_count: Option>, + pub nft_plot_count: Option>, + pub compressed_plot_count: Option>, +} +#[cfg(feature = "metrics")] + +impl HarvesterMetrics { + pub fn new(registry: &Registry) -> Self { + let uptime = GenericGauge::new("harvester_uptime", "Uptime of Harvester").map_or( + None, + |g: GenericGauge| { + registry.register(Box::new(g.clone())).unwrap_or(()); + Some(g) + }, + ); + let reported_space = GenericGauge::new("reported_space", "Reported Space in Bytes").map_or( + None, + |g: GenericGauge| { + registry.register(Box::new(g.clone())).unwrap_or(()); + Some(g) + }, + ); + let og_plot_count = GenericGauge::new("og_plot_count", "OG Plot Count").map_or( + None, + |g: GenericGauge| { + registry.register(Box::new(g.clone())).unwrap_or(()); + Some(g) + }, + ); + let nft_plot_count = GenericGauge::new("nft_plot_count", "NFT Plot Count").map_or( + None, + |g: GenericGauge| { + registry.register(Box::new(g.clone())).unwrap_or(()); + Some(g) + }, + ); + let compressed_plot_count = GenericGauge::new("compressed_plot_count", "OG Plot Count") + .map_or(None, |g: GenericGauge| { + registry.register(Box::new(g.clone())).unwrap_or(()); + Some(g) + }); + HarvesterMetrics { + start_time: Arc::new(Instant::now()), + uptime, + reported_space, + og_plot_count, + nft_plot_count, + compressed_plot_count, + } + } +} diff --git a/clients/src/protocols/introducer.rs b/core/src/protocols/introducer.rs similarity index 84% rename from clients/src/protocols/introducer.rs rename to core/src/protocols/introducer.rs index 9a6e602..daf31f6 100644 --- a/clients/src/protocols/introducer.rs +++ b/core/src/protocols/introducer.rs @@ -1,4 +1,4 @@ -use dg_xch_core::blockchain::peer_info::TimestampedPeerInfo; +use crate::blockchain::peer_info::TimestampedPeerInfo; use dg_xch_macros::ChiaSerial; use serde::{Deserialize, Serialize}; diff --git a/clients/src/protocols/mod.rs b/core/src/protocols/mod.rs similarity index 57% rename from clients/src/protocols/mod.rs rename to core/src/protocols/mod.rs index f6eee65..cf9cf7d 100644 --- a/clients/src/protocols/mod.rs +++ b/core/src/protocols/mod.rs @@ -1,3 +1,4 @@ +pub mod error; pub mod farmer; pub mod full_node; pub mod harvester; @@ -6,7 +7,32 @@ pub mod pool; pub mod shared; pub mod timelord; pub mod wallet; + +use crate::blockchain::sized_bytes::Bytes32; +use crate::utils::await_termination; +use async_trait::async_trait; use dg_xch_macros::ChiaSerial; +use dg_xch_serialize::ChiaSerialize; +use futures_util::stream::{FusedStream, SplitSink, SplitStream}; +use futures_util::SinkExt; +use futures_util::{Sink, Stream, StreamExt}; +use hyper::upgrade::Upgraded; +use hyper_util::rt::TokioIo; +use log::{debug, error, info}; +use std::collections::HashMap; +use std::io::{Cursor, Error, ErrorKind}; +use std::pin::Pin; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; +use std::task::{Context, Poll}; +use std::time::Duration; +use tokio::net::TcpStream; +use tokio::select; +use tokio::sync::Mutex; +use tokio_tungstenite::tungstenite::error::ProtocolError; +use tokio_tungstenite::tungstenite::Message; +use tokio_tungstenite::{MaybeTlsStream, WebSocketStream}; +use uuid::Uuid; #[repr(u8)] #[derive(ChiaSerial, Copy, Clone, Debug, PartialEq, Eq)] @@ -392,3 +418,307 @@ impl From for ProtocolMessageTypes { pub const INVALID_PROTOCOL_BAN_SECONDS: u8 = 10; pub const API_EXCEPTION_BAN_SECONDS: u8 = 10; pub const INTERNAL_PROTOCOL_ERROR_BAN_SECONDS: u8 = 10; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum NodeType { + Unknown = 0, + FullNode = 1, + Harvester = 2, + Farmer = 3, + Timelord = 4, + Introducer = 5, + Wallet = 6, + DataLayer = 7, +} +impl From for NodeType { + fn from(byte: u8) -> Self { + match byte { + i if i == NodeType::Unknown as u8 => NodeType::Unknown, + i if i == NodeType::FullNode as u8 => NodeType::FullNode, + i if i == NodeType::Harvester as u8 => NodeType::Harvester, + i if i == NodeType::Farmer as u8 => NodeType::Farmer, + i if i == NodeType::Timelord as u8 => NodeType::Timelord, + i if i == NodeType::Introducer as u8 => NodeType::Introducer, + i if i == NodeType::Wallet as u8 => NodeType::Wallet, + i if i == NodeType::DataLayer as u8 => NodeType::DataLayer, + _ => NodeType::Unknown, + } + } +} + +#[async_trait] +pub trait MessageHandler { + async fn handle( + &self, + msg: Arc, + peer_id: Arc, + peers: PeerMap, + ) -> Result<(), Error>; +} + +#[derive(ChiaSerial, Debug, Clone)] +pub struct ChiaMessage { + pub msg_type: ProtocolMessageTypes, + pub id: Option, + pub data: Vec, +} +impl ChiaMessage { + pub fn new(msg_type: ProtocolMessageTypes, msg: &T, id: Option) -> Self { + ChiaMessage { + msg_type, + id, + data: msg.to_bytes(), + } + } +} +impl From for Message { + fn from(val: ChiaMessage) -> Self { + Message::Binary(val.to_bytes()) + } +} + +#[derive(Debug)] +pub struct ChiaMessageFilter { + pub msg_type: Option, + pub id: Option, +} +impl ChiaMessageFilter { + pub fn matches(&self, msg: Arc) -> bool { + if self.id.is_some() && self.id != msg.id { + return false; + } + if let Some(s) = &self.msg_type { + if *s != msg.msg_type { + return false; + } + } + true + } +} + +pub struct ChiaMessageHandler { + pub filter: Arc, + pub handle: Arc, +} +impl ChiaMessageHandler { + pub fn new( + filter: Arc, + handle: Arc, + ) -> Self { + ChiaMessageHandler { filter, handle } + } +} + +pub type PeerMap = Arc>>>; + +pub struct SocketPeer { + pub node_type: Arc>, + pub websocket: Arc>, +} + +pub enum WebsocketMsgStream { + TokioIo(WebSocketStream>), + Tls(WebSocketStream>), +} +impl Stream for WebsocketMsgStream { + type Item = Result; + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + match self.get_mut() { + WebsocketMsgStream::TokioIo(ref mut s) => Pin::new(s).poll_next(cx), + WebsocketMsgStream::Tls(ref mut s) => Pin::new(s).poll_next(cx), + } + } +} +impl FusedStream for WebsocketMsgStream { + fn is_terminated(&self) -> bool { + match self { + WebsocketMsgStream::TokioIo(s) => s.is_terminated(), + WebsocketMsgStream::Tls(s) => s.is_terminated(), + } + } +} +impl Sink for WebsocketMsgStream { + type Error = tokio_tungstenite::tungstenite::error::Error; + fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + match self.get_mut() { + WebsocketMsgStream::TokioIo(ref mut s) => Pin::new(s).poll_ready(cx), + WebsocketMsgStream::Tls(ref mut s) => Pin::new(s).poll_ready(cx), + } + } + fn start_send(self: Pin<&mut Self>, item: Message) -> Result<(), Self::Error> { + match self.get_mut() { + WebsocketMsgStream::TokioIo(ref mut s) => Pin::new(s).start_send(item), + WebsocketMsgStream::Tls(ref mut s) => Pin::new(s).start_send(item), + } + } + fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + match self.get_mut() { + WebsocketMsgStream::TokioIo(ref mut s) => Pin::new(s).poll_flush(cx), + WebsocketMsgStream::Tls(ref mut s) => Pin::new(s).poll_flush(cx), + } + } + fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + match self.get_mut() { + WebsocketMsgStream::TokioIo(ref mut s) => Pin::new(s).poll_close(cx), + WebsocketMsgStream::Tls(ref mut s) => Pin::new(s).poll_close(cx), + } + } +} + +pub struct WebsocketConnection { + write: SplitSink, + message_handlers: Arc>>>, +} +impl WebsocketConnection { + pub fn new( + websocket: WebsocketMsgStream, + message_handlers: Arc>>>, + peer_id: Arc, + peers: PeerMap, + ) -> (Self, ReadStream) { + let (write, read) = websocket.split(); + let websocket = WebsocketConnection { + write, + message_handlers: message_handlers.clone(), + }; + let stream = ReadStream { + read, + message_handlers, + peer_id, + peers, + }; + (websocket, stream) + } + pub async fn send(&mut self, msg: Message) -> Result<(), Error> { + self.write + .send(msg) + .await + .map_err(|e| Error::new(ErrorKind::Other, e)) + } + + pub async fn subscribe(&self, uuid: Uuid, handle: ChiaMessageHandler) { + self.message_handlers + .lock() + .await + .insert(uuid, Arc::new(handle)); + } + + pub async fn unsubscribe(&self, uuid: Uuid) -> Option> { + self.message_handlers.lock().await.remove(&uuid) + } + + pub async fn clear(&self) { + self.message_handlers.lock().await.clear(); + } + + pub async fn close(&mut self, msg: Option) -> Result<(), Error> { + if let Some(msg) = msg { + let _ = self + .write + .send(msg) + .await + .map_err(|e| Error::new(ErrorKind::Other, e)); + self.write + .close() + .await + .map_err(|e| Error::new(ErrorKind::Other, e)) + } else { + self.write + .close() + .await + .map_err(|e| Error::new(ErrorKind::Other, e)) + } + } + + pub async fn shutdown(&mut self) -> Result<(), Error> { + self.clear().await; + self.close(None).await + } +} + +pub struct ReadStream { + read: SplitStream, + message_handlers: Arc>>>, + peer_id: Arc, + peers: PeerMap, +} +impl ReadStream { + pub async fn run(&mut self, run: Arc) { + loop { + select! { + msg = self.read.next() => { + match msg { + Some(Ok(msg)) => { + match msg { + Message::Binary(bin_data) => { + let mut cursor = Cursor::new(bin_data); + match ChiaMessage::from_bytes(&mut cursor) { + Ok(chia_msg) => { + let msg_arc: Arc = Arc::new(chia_msg); + let mut matched = false; + for v in self.message_handlers.lock().await.values() + .cloned().collect::>>() { + if v.filter.matches(msg_arc.clone()) { + let msg_arc_c = msg_arc.clone(); + let peer_id = self.peer_id.clone(); + let peers = self.peers.clone(); + let v_arc_c = v.handle.clone(); + tokio::spawn(async move { v_arc_c.handle(msg_arc_c, peer_id, peers).await }); + matched = true; + } + } + if !matched{ + error!("No Matches for Message: {:?}", &msg_arc); + } + debug!("Processed Message: {:?}", &msg_arc.msg_type); + } + Err(e) => { + error!("Invalid Message: {:?}", e); + } + } + } + Message::Close(e) => { + debug!("Server Got Close Message: {:?}", e); + return; + }, + _ => { + error!("Invalid Message: {:?}", msg); + } + } + } + Some(Err(msg)) => { + match msg { + tokio_tungstenite::tungstenite::Error::Protocol(ProtocolError::ResetWithoutClosingHandshake) => { + debug!("Server Stream Closed without Handshake"); + }, + others => { + error!("Server Stream Error: {:?}", others); + } + } + return; + } + None => { + info!("End of server read Stream"); + return; + } + } + } + _ = await_termination() => { + return; + } + _ = async { + loop { + if !run.load(Ordering::Relaxed){ + debug!("Server is exiting"); + return; + } else { + tokio::time::sleep(Duration::from_millis(100)).await + } + } + } => { + return; + } + } + } + } +} diff --git a/clients/src/protocols/pool.rs b/core/src/protocols/pool.rs similarity index 68% rename from clients/src/protocols/pool.rs rename to core/src/protocols/pool.rs index 0d97316..54b01a2 100644 --- a/clients/src/protocols/pool.rs +++ b/core/src/protocols/pool.rs @@ -1,12 +1,8 @@ -use crate::api::pool::{DefaultPoolClient, PoolClient}; -use blst::min_pk::{AggregateSignature, SecretKey, Signature}; -use dg_xch_core::blockchain::proof_of_space::ProofOfSpace; -use dg_xch_core::blockchain::sized_bytes::{Bytes32, Bytes48, Bytes96, SizedBytes}; -use dg_xch_core::clvm::bls_bindings::sign; +use crate::blockchain::proof_of_space::ProofOfSpace; +use crate::blockchain::sized_bytes::{Bytes32, Bytes48, Bytes96}; + use dg_xch_macros::ChiaSerial; -use dg_xch_serialize::{hash_256, ChiaSerialize}; use serde::{Deserialize, Serialize}; -use std::io::{Error, ErrorKind}; use time::OffsetDateTime; pub const POOL_PROTOCOL_VERSION: u8 = 1; @@ -198,71 +194,3 @@ pub fn validate_authentication_token(token: u64, timeout: u8) -> bool { }; dif <= timeout as u64 } - -#[derive(serde::Serialize, serde::Deserialize, Debug)] -pub struct PoolLoginParts { - pub auth_token: u64, - pub aggregate_signature: String, -} - -pub async fn create_pool_login_url( - target_pool: &str, - keys_and_launcher_ids: &[(SecretKey, Bytes32)], -) -> Result { - let parts = create_pool_login_parts(target_pool, keys_and_launcher_ids).await?; - let mut ids = String::new(); - for (index, (_, launcher_id)) in keys_and_launcher_ids.iter().enumerate() { - if index != 0 { - ids.push(',') - } - ids.push_str(&hex::encode(launcher_id.as_slice())); - } - Ok(format!( - "{target_pool}/login?launcher_id={ids}&authentication_token={}&signature={})", - parts.auth_token, parts.aggregate_signature - )) -} - -pub async fn create_pool_login_parts( - target_pool: &str, - keys_and_launcher_ids: &[(SecretKey, Bytes32)], -) -> Result { - let pool_client = DefaultPoolClient::new(); - let pool_info = pool_client - .get_pool_info(target_pool) - .await - .map_err(|e| Error::new(ErrorKind::Other, format!("{:?}", e)))?; - let current_auth_token = - get_current_authentication_token(pool_info.authentication_token_timeout); - let mut sigs = vec![]; - for (sec_key, launcher_id) in keys_and_launcher_ids { - let payload = AuthenticationPayload { - method_name: String::from("get_login"), - launcher_id: *launcher_id, - target_puzzle_hash: pool_info.target_puzzle_hash, - authentication_token: current_auth_token, - }; - let to_sign = hash_256(payload.to_bytes()); - let sig = sign(sec_key, &to_sign); - sigs.push(sig); - } - if !sigs.is_empty() { - let aggregate_signature = - AggregateSignature::aggregate(sigs.iter().collect::>().as_ref(), true) - .map_err(|e| { - Error::new( - ErrorKind::InvalidInput, - format!("Failed to calculate signature: {:?}", e), - ) - })?; - Ok(PoolLoginParts { - auth_token: current_auth_token, - aggregate_signature: hex::encode(aggregate_signature.to_signature().to_bytes()), - }) - } else { - Err(Error::new( - ErrorKind::NotFound, - "No Launcher IDs with Keys found", - )) - } -} diff --git a/core/src/protocols/shared.rs b/core/src/protocols/shared.rs new file mode 100644 index 0000000..a9daf1c --- /dev/null +++ b/core/src/protocols/shared.rs @@ -0,0 +1,65 @@ +use dg_xch_macros::ChiaSerial; +use rustls::client::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier}; +use rustls::{Certificate, DigitallySignedStruct, ServerName}; +use serde::{Deserialize, Serialize}; +use std::time::SystemTime; + +pub const PROTOCOL_VERSION: &str = "0.0.34"; + +pub enum Capability { + Base = 1, + BlockHeaders = 2, + RateLimitsV2 = 3, + NoneResponse = 4, +} + +#[derive(ChiaSerial, Serialize, Deserialize, Debug, Clone)] +pub struct Handshake { + pub network_id: String, + pub protocol_version: String, + pub software_version: String, + pub server_port: u16, + pub node_type: u8, + pub capabilities: Vec<(u16, String)>, +} + +pub const CAPABILITIES: [(u16, &str); 3] = [ + (Capability::Base as u16, "1"), + (Capability::BlockHeaders as u16, "1"), + (Capability::RateLimitsV2 as u16, "1"), + //(Capability::NoneResponse as u16, "1"), //This is not currently supported, Causes the Fullnode to close the connection +]; + +pub struct NoCertificateVerification; + +impl ServerCertVerifier for NoCertificateVerification { + fn verify_server_cert( + &self, + _end_entity: &Certificate, + _intermediates: &[Certificate], + _server_name: &ServerName, + _scts: &mut dyn Iterator, + _ocsp_response: &[u8], + _now: SystemTime, + ) -> Result { + Ok(ServerCertVerified::assertion()) + } + + fn verify_tls12_signature( + &self, + _message: &[u8], + _cert: &Certificate, + _dss: &DigitallySignedStruct, + ) -> Result { + Ok(HandshakeSignatureValid::assertion()) + } + + fn verify_tls13_signature( + &self, + _message: &[u8], + _cert: &Certificate, + _dss: &DigitallySignedStruct, + ) -> Result { + Ok(HandshakeSignatureValid::assertion()) + } +} diff --git a/clients/src/protocols/timelord.rs b/core/src/protocols/timelord.rs similarity index 81% rename from clients/src/protocols/timelord.rs rename to core/src/protocols/timelord.rs index fabea74..1a72e88 100644 --- a/clients/src/protocols/timelord.rs +++ b/core/src/protocols/timelord.rs @@ -1,11 +1,11 @@ -use dg_xch_core::blockchain::end_of_subslot_bundle::EndOfSubSlotBundle; -use dg_xch_core::blockchain::foliage::Foliage; -use dg_xch_core::blockchain::reward_chain_block::RewardChainBlock; -use dg_xch_core::blockchain::reward_chain_block_unfinished::RewardChainBlockUnfinished; -use dg_xch_core::blockchain::sized_bytes::Bytes32; -use dg_xch_core::blockchain::sub_epoch_summary::SubEpochSummary; -use dg_xch_core::blockchain::vdf_info::VdfInfo; -use dg_xch_core::blockchain::vdf_proof::VdfProof; +use crate::blockchain::end_of_subslot_bundle::EndOfSubSlotBundle; +use crate::blockchain::foliage::Foliage; +use crate::blockchain::reward_chain_block::RewardChainBlock; +use crate::blockchain::reward_chain_block_unfinished::RewardChainBlockUnfinished; +use crate::blockchain::sized_bytes::Bytes32; +use crate::blockchain::sub_epoch_summary::SubEpochSummary; +use crate::blockchain::vdf_info::VdfInfo; +use crate::blockchain::vdf_proof::VdfProof; use dg_xch_macros::ChiaSerial; use serde::{Deserialize, Serialize}; diff --git a/clients/src/protocols/wallet.rs b/core/src/protocols/wallet.rs similarity index 96% rename from clients/src/protocols/wallet.rs rename to core/src/protocols/wallet.rs index 67e1779..a5d0c3f 100644 --- a/clients/src/protocols/wallet.rs +++ b/core/src/protocols/wallet.rs @@ -1,8 +1,8 @@ -use dg_xch_core::blockchain::coin::Coin; -use dg_xch_core::blockchain::header_block::HeaderBlock; -use dg_xch_core::blockchain::sized_bytes::Bytes32; -use dg_xch_core::blockchain::spend_bundle::SpendBundle; -use dg_xch_core::clvm::program::SerializedProgram; +use crate::blockchain::coin::Coin; +use crate::blockchain::header_block::HeaderBlock; +use crate::blockchain::sized_bytes::Bytes32; +use crate::blockchain::spend_bundle::SpendBundle; +use crate::clvm::program::SerializedProgram; use dg_xch_macros::ChiaSerial; use serde::{Deserialize, Serialize}; diff --git a/core/src/ssl.rs b/core/src/ssl.rs index 5509e3a..51d6a44 100644 --- a/core/src/ssl.rs +++ b/core/src/ssl.rs @@ -6,14 +6,19 @@ use rand::Rng; use rsa::pkcs1::{DecodeRsaPrivateKey, EncodeRsaPrivateKey}; use rsa::pkcs1v15::SigningKey; use rsa::pkcs8::EncodePublicKey; +use rustls::server::{ClientCertVerified, ClientCertVerifier}; +use rustls::{DistinguishedName, PrivateKey, RootCertStore}; +use rustls_pemfile::{certs, read_one, Item}; +use serde::{Deserialize, Serialize}; use sha2::Sha256; use std::collections::HashMap; use std::fs; -use std::fs::{create_dir_all, OpenOptions}; -use std::io::{Error, ErrorKind, Write}; +use std::fs::{create_dir_all, File, OpenOptions}; +use std::io::{BufReader, Error, ErrorKind, Write}; use std::ops::{Add, Sub}; use std::path::Path; use std::str::FromStr; +use std::sync::Arc; use std::time::{Duration, SystemTime}; use x509_cert::builder::{Builder, CertificateBuilder, Profile}; use x509_cert::der::DecodePem; @@ -25,6 +30,30 @@ use x509_cert::spki::SubjectPublicKeyInfo; use x509_cert::time::{Time, Validity}; use x509_cert::Certificate; +pub struct AllowAny { + _roots: RootCertStore, +} +impl AllowAny { + pub fn new(_roots: RootCertStore) -> Arc { + Arc::new(Self { _roots }) + } +} + +impl ClientCertVerifier for AllowAny { + fn client_auth_root_subjects(&self) -> &[DistinguishedName] { + info!("In Farmer client_auth_root_subjects"); + &[] + } + fn verify_client_cert( + &self, + _end_entity: &rustls::Certificate, + _intermediates: &[rustls::Certificate], + _now: SystemTime, + ) -> Result { + info!("In Farmer verify_client_cert"); + Ok(ClientCertVerified::assertion()) + } +} pub const CHIA_CA_CRT: &str = r"-----BEGIN CERTIFICATE----- MIIDKTCCAhGgAwIBAgIUXIpxI5MoZQ65/vhc7DK/d5ymoMUwDQYJKoZIhvcNAQEL BQAwRDENMAsGA1UECgwEQ2hpYTEQMA4GA1UEAwwHQ2hpYSBDQTEhMB8GA1UECwwY @@ -73,6 +102,80 @@ EejO8oPWcb9AbqgPtrWaiJi17KiKv4Oyba5+y36IEtyjolWt0AB6F3oDK0X+Etw8 j/xlvBNuzDL6gRJHQg1+d4dO8Lz54NDUbKW8jGl+N/7afGVpGmX9 -----END RSA PRIVATE KEY-----"; +const ALL_PRIVATE_NODE_NAMES: [&str; 8] = [ + "full_node", + "wallet", + "farmer", + "harvester", + "timelord", + "crawler", + "data_layer", + "daemon", +]; + +const ALL_PUBLIC_NODE_NAMES: [&str; 6] = [ + "full_node", + "wallet", + "farmer", + "introducer", + "timelord", + "data_layer", +]; + +pub fn load_certs(filename: &str) -> Result, Error> { + let cert_file = File::open(filename)?; + let mut reader = BufReader::new(cert_file); + let certs = certs(&mut reader)?; + Ok(certs.into_iter().map(rustls::Certificate).collect()) +} + +pub fn load_certs_from_bytes(bytes: &[u8]) -> Result, Error> { + let mut reader = BufReader::new(bytes); + let certs = certs(&mut reader)?; + Ok(certs.into_iter().map(rustls::Certificate).collect()) +} + +pub fn load_private_key(filename: &str) -> Result { + let keyfile = File::open(filename)?; + let mut reader = BufReader::new(keyfile); + for item in std::iter::from_fn(|| read_one(&mut reader).transpose()) { + match item? { + Item::X509Certificate(_) => error!("Found Certificate, not Private Key"), + Item::RSAKey(key) => { + return Ok(PrivateKey(key)); + } + Item::PKCS8Key(key) => { + return Ok(PrivateKey(key)); + } + Item::ECKey(key) => { + return Ok(PrivateKey(key)); + } + _ => error!("Unknown Item while loading private key"), + } + } + Err(Error::new(ErrorKind::NotFound, "Private Key Not Found")) +} + +pub fn load_private_key_from_bytes(bytes: &[u8]) -> Result { + let mut reader = BufReader::new(bytes); + for item in std::iter::from_fn(|| read_one(&mut reader).transpose()) { + match item? { + Item::X509Certificate(_) => error!("Found Certificate, not Private Key"), + Item::RSAKey(key) => { + return Ok(PrivateKey(key)); + } + Item::PKCS8Key(key) => { + return Ok(PrivateKey(key)); + } + Item::ECKey(key) => { + return Ok(PrivateKey(key)); + } + _ => error!("Unknown Item while loading private key"), + } + } + Err(Error::new(ErrorKind::NotFound, "Private Key Not Found")) +} + pub fn generate_ca_signed_cert( cert_path: &Path, cert_data: &str, @@ -236,26 +339,6 @@ fn make_ca_cert_data() -> Result<(String, String), Error> { )) } -const ALL_PRIVATE_NODE_NAMES: [&str; 8] = [ - "full_node", - "wallet", - "farmer", - "harvester", - "timelord", - "crawler", - "data_layer", - "daemon", -]; - -const ALL_PUBLIC_NODE_NAMES: [&str; 6] = [ - "full_node", - "wallet", - "farmer", - "introducer", - "timelord", - "data_layer", -]; - pub struct MemorySSL { pub public: HashMap, pub private: HashMap, @@ -387,6 +470,23 @@ pub fn generate_ssl_for_nodes_in_memory( Ok(map) } +#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct SslCertInfo { + #[serde(default)] + pub public_crt: Option, + #[serde(default)] + pub public_key: Option, + pub private_crt: String, + pub private_key: String, +} + +#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct SslInfo { + pub root_path: String, + pub certs: SslCertInfo, + pub ca: SslCertInfo, +} + #[test] pub fn test_ssl() { use simple_logger::SimpleLogger; diff --git a/core/src/utils.rs b/core/src/utils.rs new file mode 100644 index 0000000..e4207a5 --- /dev/null +++ b/core/src/utils.rs @@ -0,0 +1,40 @@ +use std::io::Error; +use tokio::select; +#[cfg(not(target_os = "windows"))] +use tokio::signal::unix::{signal, SignalKind}; +#[cfg(target_os = "windows")] +use tokio::signal::windows::{ctrl_break, ctrl_c, ctrl_close, ctrl_logoff, ctrl_shutdown}; + +#[cfg(not(target_os = "windows"))] +pub async fn await_termination() -> Result<(), Error> { + let mut term_signal = signal(SignalKind::terminate())?; + let mut int_signal = signal(SignalKind::interrupt())?; + let mut quit_signal = signal(SignalKind::quit())?; + let mut alarm_signal = signal(SignalKind::alarm())?; + let mut hup_signal = signal(SignalKind::hangup())?; + select! { + _ = term_signal.recv() => (), + _ = int_signal.recv() => (), + _ = quit_signal.recv() => (), + _ = alarm_signal.recv() => (), + _ = hup_signal.recv() => () + } + Ok(()) +} + +#[cfg(target_os = "windows")] +pub async fn await_termination() -> Result<(), Error> { + let mut ctrl_break_signal = ctrl_break()?; + let mut ctrl_c_signal = ctrl_c()?; + let mut ctrl_close_signal = ctrl_close()?; + let mut ctrl_logoff_signal = ctrl_logoff()?; + let mut ctrl_shutdown_signal = ctrl_shutdown()?; + select! { + _ = ctrl_break_signal.recv() => (), + _ = ctrl_c_signal.recv() => (), + _ = ctrl_close_signal.recv() => (), + _ = ctrl_logoff_signal.recv() => (), + _ = ctrl_shutdown_signal.recv() => () + } + Ok(()) +} diff --git a/keys/Cargo.toml b/keys/Cargo.toml index 9953b95..4269621 100644 --- a/keys/Cargo.toml +++ b/keys/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dg_xch_keys" -version = "1.2.1" +version = "2.0.0" edition = "2021" authors = ["James Hoerr"] description = "Key Management Utils the Chia Blockchain" @@ -12,7 +12,7 @@ repository = "https://github.com/GalactechsLLC/dg_xch_utils/keys" bech32 = "0.9.1" blst = "0.3.11" bip39 = {version= "2.0.0", features=["rand"] } -dg_xch_core = {path = "../core", version = "1.2.1", features = ["paperclip"] } -dg_xch_puzzles = {path = "../puzzles", version="1.2.1"} +dg_xch_core = {path = "../core", version = "2.0.0", features = ["paperclip"] } +dg_xch_puzzles = {path = "../puzzles", version="2.0.0"} hkdf = "0.12.3" sha2 = "0.10.8" \ No newline at end of file diff --git a/macros/Cargo.toml b/macros/Cargo.toml index 3a2cdb9..f9b329b 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dg_xch_macros" -version = "1.2.1" +version = "2.0.0" edition = "2021" description = "Derive Marcos for Chia Serialization" license = "Apache-2.0" diff --git a/proof_of_space/Cargo.toml b/proof_of_space/Cargo.toml index 89c72c5..5a4cc90 100644 --- a/proof_of_space/Cargo.toml +++ b/proof_of_space/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dg_xch_pos" -version = "1.2.1" +version = "2.0.0" edition = "2021" authors = ["James Hoerr"] description = "Proof of Space library for the Chia Blockchain" @@ -9,9 +9,10 @@ homepage = "https://github.com/GalactechsLLC/dg_xch_utils" repository = "https://github.com/GalactechsLLC/dg_xch_utils/proof_of_space" [dependencies] +async-trait = "0.1.74" blake3 = "1.5.0" -dg_xch_core = {path = "../core", version = "1.2.1", features = ["paperclip"] } -dg_xch_serialize = {path = "../serialize", version="1.2.1"} +dg_xch_core = {path = "../core", version = "2.0.0", features = ["paperclip"] } +dg_xch_serialize = {path = "../serialize", version="2.0.0"} futures-util = "0.3.29" hex = "0.4.3" lazy_static = "1.4.0" @@ -28,3 +29,10 @@ sha2 = "0.10.8" simple_logger = "4.3.0" tokio = {version = "1.34.0", features=["rt-multi-thread", "sync", "signal", "macros", "process", "time", "fs", "net", "io-util"]} winapi = "0.3.9" + +[dev-dependencies] +criterion = { version = "0.5.1", features = ["stable"] } + +[[bench]] +name = "compression" +harness = false \ No newline at end of file diff --git a/proof_of_space/benches/compression.rs b/proof_of_space/benches/compression.rs index cfcdd92..9cc2109 100644 --- a/proof_of_space/benches/compression.rs +++ b/proof_of_space/benches/compression.rs @@ -10,7 +10,6 @@ use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::Arc; use std::thread::available_parallelism; use tokio::runtime::{Builder, Runtime}; -use tokio::sync::Mutex; fn proof_benchmark(c: &mut Criterion, runtime: &Runtime) { SimpleLogger::new().env().init().unwrap_or_default(); @@ -37,11 +36,11 @@ fn proof_benchmark(c: &mut Criterion, runtime: &Runtime) { hex::decode("00000000ff04b8ee9355068689bd558eafe07cc7af47ad1574b074fc34d6913a") .unwrap(); let _f7 = f7.load(Ordering::Relaxed); - let f7size = ucdiv_t(*reader.plot_file().k() as usize, 8); + let f7size = ucdiv_t(reader.plot_file().k() as usize, 8); for (i, v) in challenge[0..f7size].iter_mut().enumerate() { *v = (_f7 >> ((f7size - i - 1) * 8)) as u8; } - let _ = reader.fetch_proof_for_challenge(&challenge).await; + let _ = reader.fetch_proofs_for_challenge(&challenge).await; f7.fetch_add(1, Ordering::Relaxed); }) }); @@ -72,17 +71,18 @@ fn quality_then_proof_benchmark(c: &mut Criterion, runtime: &Runtime) { hex::decode("00000000ff04b8ee9355068689bd558eafe07cc7af47ad1574b074fc34d6913a") .unwrap(); let _f7 = f7.load(Ordering::Relaxed); - let f7size = ucdiv_t(*reader.plot_file().k() as usize, 8); + let f7size = ucdiv_t(reader.plot_file().k() as usize, 8); for (i, v) in challenge[0..f7size].iter_mut().enumerate() { *v = (_f7 >> ((f7size - i - 1) * 8)) as u8; } for (index, _) in reader .fetch_qualities_for_challenge(&challenge) .await - .unwrap() + .unwrap_or_default() { - reader.fetch_ordered_proof(index).await.unwrap(); + let _ = reader.fetch_ordered_proof(index).await; } + f7.fetch_add(1, Ordering::Relaxed); }) }); } @@ -103,7 +103,7 @@ fn quality_benchmark(c: &mut Criterion, runtime: &Runtime) { .await .unwrap() }); - let reader = Arc::new(Mutex::new(plot_reader)); + let reader = Arc::new(plot_reader); let f7 = Arc::new(AtomicU64::new(0)); c.bench_function("Quality Bench", |b| { let reader = reader.clone(); @@ -112,13 +112,11 @@ fn quality_benchmark(c: &mut Criterion, runtime: &Runtime) { hex::decode("00000000ff04b8ee9355068689bd558eafe07cc7af47ad1574b074fc34d6913a") .unwrap(); let _f7 = f7.load(Ordering::Relaxed); - let f7size = ucdiv_t(*reader.lock().await.plot_file().k() as usize, 8); + let f7size = ucdiv_t(reader.plot_file().k() as usize, 8); for (i, v) in challenge[0..f7size].iter_mut().enumerate() { *v = (_f7 >> ((f7size - i - 1) * 8)) as u8; } reader - .lock() - .await .fetch_qualities_for_challenge(&challenge) .await .unwrap(); @@ -128,9 +126,11 @@ fn quality_benchmark(c: &mut Criterion, runtime: &Runtime) { pub fn benches(runtime: Runtime) { let mut criterion = Criterion::default().configure_from_args(); + let mut criterion = criterion.sample_size(50); quality_benchmark(&mut criterion, &runtime); - // quality_then_proof_benchmark(&mut criterion, &runtime); - // proof_benchmark(&mut criterion, &runtime); + let mut criterion = criterion.sample_size(10); + proof_benchmark(&mut criterion, &runtime); + quality_then_proof_benchmark(&mut criterion, &runtime); criterion.final_summary(); } diff --git a/proof_of_space/src/constants.rs b/proof_of_space/src/constants.rs index 472f907..62c3f83 100644 --- a/proof_of_space/src/constants.rs +++ b/proof_of_space/src/constants.rs @@ -136,7 +136,7 @@ pub const VERSION: u16 = 1; pub const HEADER_MAGIC: [u8; 19] = [ 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x20, 0x6f, 0x66, 0x20, 0x53, 0x70, 0x61, 0x63, 0x65, 0x20, 0x50, 0x6c, 0x6f, 0x74, -]; +]; // pub const HEADER_V2_MAGIC: [u8; 4] = [0x50, 0x4c, 0x4f, 0x54]; diff --git a/proof_of_space/src/encoding.rs b/proof_of_space/src/encoding.rs index cc58a84..b2d7f0c 100644 --- a/proof_of_space/src/encoding.rs +++ b/proof_of_space/src/encoding.rs @@ -90,19 +90,16 @@ pub fn square_to_line_point128(x: u64, y: u64) -> u128 { pub fn line_point_to_square(index: u128) -> (u64, u64) { // Performs a square root, without the use of doubles, to use the precision of the u128. let mut x = 0; - let mut i = 63; - while i >= 0 { + for i in (0..=63).rev() { let new_x = x + (1u64 << i); if get_x_enc128(&new_x) <= index { x = new_x; } - i -= 1; } (x, (index - get_x_enc128(&x)) as u64) } pub fn line_point_to_square64(index: u64) -> (u64, u64) { - // Performs a square root, without the use of doubles, to use the precision of the u128. let mut x = 0; let mut i = 63; while i >= 0 { diff --git a/proof_of_space/src/lib.rs b/proof_of_space/src/lib.rs index 451960d..d431620 100644 --- a/proof_of_space/src/lib.rs +++ b/proof_of_space/src/lib.rs @@ -1,12 +1,22 @@ -extern crate core; - +use crate::plots::disk_plot::DiskPlot; +use crate::plots::plot_reader::PlotReader; use crate::verifier::validate_proof; +use async_trait::async_trait; use dg_xch_core::blockchain::proof_of_space::{ calculate_pos_challenge, calculate_prefix_bits, passes_plot_filter, ProofOfSpace, }; -use dg_xch_core::blockchain::sized_bytes::Bytes32; +use dg_xch_core::blockchain::sized_bytes::{Bytes32, Bytes48}; use dg_xch_core::consensus::constants::ConsensusConstants; +use dg_xch_core::protocols::harvester::HarvesterState; use log::warn; +use std::collections::HashMap; +use std::hash::{Hash, Hasher}; +use std::io::Error; +use std::path::PathBuf; +use std::sync::atomic::AtomicBool; +use std::sync::Arc; +use tokio::fs::File; +use tokio::sync::Mutex; pub mod chacha8; pub mod constants; @@ -99,3 +109,41 @@ pub fn get_quality_string(pos: &ProofOfSpace, plot_id: &Bytes32) -> Option(&self, state: &mut H) { + self.file_name.hash(state) + } +} +impl Eq for PathInfo {} +impl PartialEq for PathInfo { + fn eq(&self, other: &Self) -> bool { + self.file_name == other.file_name + } +} + +#[derive(Debug)] +pub struct PlotInfo { + pub reader: PlotReader>, + pub pool_public_key: Option, + pub pool_contract_puzzle_hash: Option, + pub plot_public_key: Bytes48, + pub file_size: u64, + pub time_modified: u64, +} + +#[async_trait] +pub trait PlotManagerAsync { + fn set_public_keys(&mut self, farmer_public_keys: Vec, pool_public_keys: Vec); + async fn load_plots( + &mut self, + harvester_state: Arc>, + ) -> Result<(), Error>; + fn plots(&self) -> &HashMap>; + fn plots_ready(&self) -> Arc; +} diff --git a/proof_of_space/src/plots/decompressor.rs b/proof_of_space/src/plots/decompressor.rs index e981b4f..72ba61f 100644 --- a/proof_of_space/src/plots/decompressor.rs +++ b/proof_of_space/src/plots/decompressor.rs @@ -98,6 +98,7 @@ pub struct CompressedQualitiesRequest<'a> { pub compression_level: u8, pub challenge: &'a [u8], pub line_points: [LinePoint; 2], + pub f1_generator: Option>, } unsafe impl<'a> Send for CompressedQualitiesRequest<'a> {} unsafe impl<'a> Sync for CompressedQualitiesRequest<'a> {} @@ -109,7 +110,7 @@ pub struct TableContext<'a> { out_y: Span, out_meta: Span, out_pairs: Span, - pub f1_generator: F1Generator, + pub f1_generator: Arc, } unsafe impl<'a> Send for TableContext<'a> {} unsafe impl<'a> Sync for TableContext<'a> {} @@ -120,6 +121,7 @@ pub struct ProofRequest { full_proof: Vec, //[u64; POST_PROOF_X_COUNT], c_level: u8, plot_id: Bytes32, + f1_generator: Option>, } #[derive(Debug)] @@ -344,41 +346,39 @@ impl Decompressor { let num_groups = POST_PROOF_CMP_X_COUNT; let entries_per_bucket = get_entries_per_bucket_for_compression_level(k, req.c_level); assert!(entries_per_bucket <= 0xFFFFFFFF); - let mut proof_might_be_dropped = false; let mut tables = Span::new(self.tables.as_mut_ptr(), self.tables.len()); let mut x_groups = [0u32; POST_PROOF_X_COUNT]; - let mut x_groups = Span::new(x_groups.as_mut_ptr(), POST_PROOF_X_COUNT); if req.c_level < 9 { - let mut i = 0usize; let mut j = 0usize; - while i < num_groups { - let x_line_point = req.compressed_proof[i]; - let xs = line_point_to_square64(x_line_point); - proof_might_be_dropped = proof_might_be_dropped || (xs.0 == 0 || xs.1 == 0); + for xs in req + .compressed_proof + .iter() + .take(num_groups) + .copied() + .map(line_point_to_square64) + { x_groups[j] = xs.1 as u32; x_groups[j + 1] = xs.0 as u32; - i += 1; j += 2; } } else { - let mut i = 0usize; + let entry_bits = get_compression_info_for_level(req.c_level).entry_size_bits; + let mask = (1 << entry_bits) - 1; let mut j = 0usize; - while i < num_groups / 2 { - let x_line_point = req.compressed_proof[i]; - let xs = line_point_to_square64(x_line_point); - let entry_bits = get_compression_info_for_level(req.c_level).entry_size_bits; - let mask = (1 << entry_bits) - 1; - proof_might_be_dropped = proof_might_be_dropped || (xs.0 == 0 || xs.1 == 0); + for xs in req + .compressed_proof + .iter() + .take(num_groups / 2) + .copied() + .map(line_point_to_square64) + { x_groups[j] = (xs.1 as u32) & mask; x_groups[j + 1] = (xs.1 as u32) >> entry_bits; x_groups[j + 2] = (xs.0 as u32) & mask; x_groups[j + 3] = (xs.0 as u32) >> entry_bits; - i += 1; j += 4; } } - let mut i = 0usize; - let mut j = 0usize; let out_y = Span::new(self.y_buffer_tmp.as_mut_ptr(), self.y_buffer_tmp.len()); let out_meta = Span::new( self.meta_buffer_tmp.as_mut_ptr(), @@ -389,13 +389,17 @@ impl Decompressor { let thread_count = self.config.thread_count; let mut table_context = TableContext { context: self, - f1_generator: F1Generator::new(k, thread_count, req.plot_id.as_ref()), + f1_generator: if let Some(f1) = &req.f1_generator { + f1.clone() + } else { + Arc::new(F1Generator::new(k, thread_count, req.plot_id.as_ref())) + }, entries_per_bucket: entries_per_bucket as isize, out_y, out_meta, out_pairs, }; - while i < num_groups { + for (i, j) in (0..num_groups).zip((0..x_groups.len()).step_by(2)) { let x1 = x_groups[j] as u64; let x2 = x_groups[j + 1] as u64; let group_index = i / 2; @@ -403,12 +407,6 @@ impl Decompressor { if i % 2 == 0 { table.begin_group(group_index); } - debug!( - "Processing Table Group: {}/{num_groups}: ({},{})", - i + 1, - x1, - x2 - ); if let Err(e) = Self::process_table1bucket(k, req.c_level, &mut table_context, x1, x2, group_index) { @@ -418,8 +416,6 @@ impl Decompressor { format!("Error Processing Table1 Bucket: {:?}", e), )); } - i += 1; - j += 2; } // #NOTE: Sanity check, but should never happen w/ our starting compression levels. @@ -1729,12 +1725,14 @@ impl Decompressor { k: u8, c_level: u8, compressed_proof: &[u64], + f1_generator: Option>, ) -> Result, Error> { let mut req = ProofRequest { compressed_proof: vec![0u64; k as usize], full_proof: vec![0u64; k as usize * 2], c_level, plot_id: *plot_id, + f1_generator, }; let compressed_proof_count = if c_level < 9 { PROOF_X_COUNT / 2 @@ -1823,28 +1821,24 @@ impl Decompressor { let thread_count = self.config.thread_count; let mut table_context = TableContext { context: self, - f1_generator: F1Generator::new(k, thread_count, req.plot_id.as_ref()), + f1_generator: if let Some(f1) = req.f1_generator { + f1.clone() + } else { + Arc::new(F1Generator::new(k, thread_count, req.plot_id.as_ref())) + }, entries_per_bucket: entries_per_bucket as isize, out_y, out_meta, out_pairs, }; - let mut i = 0; - let mut j = 0; let table: &mut ProofTable = &mut tables[1isize]; - while i < num_xgroups { + for (i, j) in (0..num_xgroups).zip((0..x_groups.len()).step_by(2)) { let x1 = x_groups[j] as u64; let x2 = x_groups[j + 1] as u64; let group_index = i / 2; if i % 2 == 0 { table.begin_group(group_index); } - debug!( - "Processing Table Group: {}/{num_xgroups}: ({},{})", - i + 1, - x1, - x2 - ); if let Err(e) = Self::process_table1bucket( k, req.compression_level, @@ -1859,8 +1853,6 @@ impl Decompressor { format!("Error Processing Table1 Bucket: {:?}", e), )); } - i += 1; - j += 2; } } // #NOTE: Sanity check, but should never happen w/ our starting compression levels. diff --git a/proof_of_space/src/plots/plot_reader.rs b/proof_of_space/src/plots/plot_reader.rs index c7acb17..fbf1326 100644 --- a/proof_of_space/src/plots/plot_reader.rs +++ b/proof_of_space/src/plots/plot_reader.rs @@ -10,6 +10,7 @@ use crate::plots::compression::{create_compression_dtable, get_compression_info_ use crate::plots::decompressor::{ CompressedQualitiesRequest, Decompressor, DecompressorPool, LinePoint, }; +use crate::plots::fx_generator::F1Generator; use crate::plots::PROOF_X_COUNT; use crate::utils::bit_reader::BitReader; use crate::utils::{bytes_to_u64, open_read_only, open_read_only_async, slice_u128from_bytes}; @@ -26,8 +27,10 @@ use std::fmt::Display; use std::io::{Error, ErrorKind, Seek, SeekFrom}; use std::marker::PhantomData; use std::mem::{size_of, swap}; +use std::num::NonZeroUsize; use std::path::{Path, PathBuf}; use std::sync::Arc; +use std::thread::available_parallelism; use tokio::io::{AsyncRead, AsyncReadExt, AsyncSeek, AsyncSeekExt}; use tokio::sync::Mutex; @@ -53,6 +56,7 @@ pub struct PlotReader< pub p7_entries: Mutex>, fx: Mutex>, meta: Mutex>, + f1_generator: Arc, } impl< F: AsyncSeek + AsyncRead + AsyncSeekExt + AsyncReadExt + Unpin, @@ -64,6 +68,15 @@ impl< proof_decompressor: Option>, quality_decompressor: Option>, ) -> Result { + let f1_generator = Arc::new(F1Generator::new( + t.k(), + available_parallelism() + .unwrap_or_else(|_| { + NonZeroUsize::new(8).expect("Safe Value Expected for Non Zero Usize") + }) + .get() as u8, + &t.plot_id().bytes, + )); let mut reader = Self { proof_decompressor, quality_decompressor, @@ -74,6 +87,7 @@ impl< p7_entries: Mutex::new(vec![0u64; K_ENTRIES_PER_PARK as usize]), fx: Mutex::new(vec![0u64; PROOF_X_COUNT]), meta: Mutex::new(Vec::with_capacity(PROOF_X_COUNT)), + f1_generator, }; reader.load_c2entries().await?; Ok(reader) @@ -111,6 +125,7 @@ impl< PlotTable::Table2 } } + PlotHeader::GHv2_5(_) => PlotTable::Table1, } } @@ -124,17 +139,18 @@ impl< *table == self.get_lowest_stored_table() } } + PlotHeader::GHv2_5(_) => false, } } pub fn get_park_size_for_table(&self, table: &PlotTable) -> u64 { if self.is_compressed_table(table) { - get_compression_info_for_level(*self.plot_file().compression_level()).table_park_size + get_compression_info_for_level(self.plot_file().compression_level()).table_park_size as u64 } else if (*table as u8) < self.get_lowest_stored_table() as u8 { 0 } else { - EntrySizes::calculate_park_size(table, *self.plot_file().k() as u32) as u64 + EntrySizes::calculate_park_size(table, self.plot_file().k() as u32) as u64 } } @@ -153,14 +169,14 @@ impl< | PlotTable::Table5 | PlotTable::Table6 => { self.file.table_size(table) as usize - / EntrySizes::calculate_park_size(table, *self.file.k() as u32) as usize + / EntrySizes::calculate_park_size(table, self.file.k() as u32) as usize } } } pub fn get_maximum_c1_entries(&self) -> u64 { let c1table_size = self.file.table_size(&PlotTable::C1); - let f7size = ucdiv64(*self.file.k() as u64, 8); + let f7size = ucdiv64(self.file.k() as u64, 8); let c3park_count = max(c1table_size / f7size, 1); // -1 because an extra 0 entry is added at the end c3park_count - 1 @@ -171,8 +187,8 @@ impl< if max_c1entries < 1 { return Ok(0); } - let f7size_bytes = ucdiv64(*self.file.k() as u64, 8); - let c1address = *self.file.table_address(&PlotTable::C1); + let f7size_bytes = ucdiv64(self.file.k() as u64, 8); + let c1address = self.file.table_address(&PlotTable::C1); let c1table_size = self.file.table_size(&PlotTable::C1); let mut c1read_address = c1address + c1table_size - f7size_bytes; // Read entries from the end of the table until the start, until we find an entry that is @@ -209,10 +225,10 @@ impl< Ok((c1read_address - c1address) / f7size_bytes) } pub async fn read_c3park(&self, park_index: u64) -> Result, Error> { - let f7size_bytes: u64 = ucdiv64(*self.file.k() as u64, 8); - let c3park_size: u64 = EntrySizes::calculate_c3size(*self.file.k() as u32) as u64; - let c1address: u64 = *self.file.table_address(&PlotTable::C1); - let c3address: u64 = *self.file.table_address(&PlotTable::C3); + let f7size_bytes: u64 = ucdiv64(self.file.k() as u64, 8); + let c3park_size: u64 = EntrySizes::calculate_c3size(self.file.k() as u32) as u64; + let c1address: u64 = self.file.table_address(&PlotTable::C1); + let c3address: u64 = self.file.table_address(&PlotTable::C3); let c1table_size: u64 = self.file.table_size(&PlotTable::C1); let c3table_size: u64 = self.file.table_size(&PlotTable::C3); let c1entry_address: u64 = c1address + park_index * f7size_bytes; @@ -236,7 +252,7 @@ impl< file_lock.read_exact(&mut c1_entry_bytes).await?; } let mut f7_reader = BitReader::from_bytes_be(&c1_entry_bytes, f7size_bytes as usize * 8); - let c1 = f7_reader.read_u64(*self.plot_file().k() as usize)?; + let c1 = f7_reader.read_u64(self.plot_file().k() as usize)?; // Ensure we can read this park. If it's not present, it means // the C1 entry is the only entry in the park, so just return it. @@ -284,10 +300,10 @@ impl< } pub async fn read_p7park(&self, park_index: usize) -> Result<(), Error> { - let entry_size = 1 + *self.plot_file().k() as usize; - let table_address = *self.file.table_address(&PlotTable::Table7); + let entry_size = 1 + self.plot_file().k() as usize; + let table_address = self.file.table_address(&PlotTable::Table7); let max_table_size = self.file.table_size(&PlotTable::Table7); - let park_size = EntrySizes::calculate_park7_size(*self.plot_file().k() as u32) as u64; + let park_size = EntrySizes::calculate_park7_size(self.plot_file().k() as u32) as u64; let max_parks = max_table_size / park_size; if park_index >= max_parks as usize { return Err(Error::new( @@ -333,6 +349,12 @@ impl< let compression_level = match self.header() { PlotHeader::V1(_) => 0, PlotHeader::V2(h) => h.compression_level, + PlotHeader::GHv2_5(_) => { + return Err(Error::new( + ErrorKind::InvalidData, + "Gigahorse Plots are Not Supported", + )) + } }; let tables = if compression_level == 0 { vec![ @@ -359,12 +381,12 @@ impl< PlotTable::Table3, ] }; - for table in tables.iter() { + for table in tables { let (mut i, mut dst) = (0, 0); while i < lookup_count { let idx = lp_idx_src[i]; - let lp = self.read_line_point(table, idx).await?; - let (x, y) = if *self.file.k() <= 32 && *table != PlotTable::Table6 { + let lp = self.read_line_point(&table, idx).await?; + let (x, y) = if self.file.k() <= 32 && table != PlotTable::Table6 { line_point_to_square64(lp as u64) } else { line_point_to_square(lp) @@ -378,15 +400,21 @@ impl< swap(lp_idx_src, lp_idx_dst); } if compression_level > 0 { - let plot_id = *self.plot_id(); - let k = *self.file.k(); + let plot_id = self.plot_id(); + let k = self.file.k(); let c = self.compression_level(); if let Some(pool) = self.proof_decompressor.as_ref() { match pool.pull_wait(10000).await { Ok(mut rede) => { debug!("Search for proof at index {index} in plot {}", self.file); rede.prealloc_for_clevel(k, c); - match rede.decompress_proof(&plot_id, k, c, lp_idx_src) { + match rede.decompress_proof( + &plot_id, + k, + c, + lp_idx_src, + Some(self.f1_generator.clone()), + ) { Ok(p) => { pool.push(rede).await; Ok(p) @@ -407,7 +435,13 @@ impl< debug!("Search for proof at index {index} in plot {}", self.file); let mut d = Decompressor::default(); d.prealloc_for_clevel(k, c); - d.decompress_proof(&plot_id, k, compression_level, lp_idx_src) + d.decompress_proof( + &plot_id, + k, + compression_level, + lp_idx_src, + Some(self.f1_generator.clone()), + ) } } else { Ok(first) @@ -468,7 +502,7 @@ impl< index: u64, challenge: &[u8], ) -> Result<(u64, u64), Error> { - let compression_level = *self.file.compression_level(); + let compression_level = self.file.compression_level(); let last5bits = challenge[31] & 0x1f; let mut lp_index = index; let mut alt_index = 0; @@ -501,7 +535,7 @@ impl< }; for table in tables { let lp = self.read_line_point(&table, lp_index).await?; - let (x, y) = if *self.file.k() <= 32 { + let (x, y) = if self.file.k() <= 32 { line_point_to_square64(lp as u64) } else { line_point_to_square(lp) @@ -527,7 +561,7 @@ impl< }; } let req = CompressedQualitiesRequest { - plot_id: *self.plot_id(), + plot_id: self.plot_id(), compression_level, challenge, line_points: [ @@ -537,9 +571,10 @@ impl< }, x_lp1, ], + f1_generator: Some(self.f1_generator.clone()), }; - let k = *self.file.k(); - let c = *self.file.compression_level(); + let k = self.file.k(); + let c = self.file.compression_level(); if let Some(pool) = &self.quality_decompressor { match pool.pull_wait(10000).await { Ok(mut rede) => { @@ -568,11 +603,11 @@ impl< } else { let mut d = Decompressor::default(); d.prealloc_for_clevel(k, c); - d.get_fetch_qualties_x_pair(*self.file.k(), req) + d.get_fetch_qualties_x_pair(self.file.k(), req) } } else { let lp = self.read_line_point(&end_table, lp_index).await?; - Ok(if *self.file.k() <= 32 { + Ok(if self.file.k() <= 32 { line_point_to_square64(lp as u64) } else { line_point_to_square(lp) @@ -591,8 +626,8 @@ impl< let mut hash_input = Vec::with_capacity(HASH_SIZE_MAX); hash_input.extend(challenge); let mut bits = BitReader::default(); - bits.append_value(x2, *self.file.k() as usize); - bits.append_value(x1, *self.file.k() as usize); + bits.append_value(x2, self.file.k() as usize); + bits.append_value(x1, self.file.k() as usize); hash_input.extend(bits.to_bytes()); Ok(Bytes32::new(&hash_256(hash_input))) } @@ -601,7 +636,7 @@ impl< &self, challenge: &[u8], ) -> Result, Error> { - let k = *self.plot_file().k() as usize; + let k = self.plot_file().k() as usize; let mut challenge_reader = BitReader::from_bytes_be(&challenge[0..8], 64); let f7 = challenge_reader.read_u64(k)?; let (match_count, p7base_index) = self.get_p7indices_for_f7(f7).await?; @@ -643,8 +678,8 @@ impl< let mut fx = self.fx.lock().await; let mut meta = self.meta.lock().await; meta.clear(); - let k = *self.plot_file().k(); - let bytes = *self.plot_file().plot_id(); + let k = self.plot_file().k(); + let bytes = self.plot_file().plot_id(); reorder_proof(k, bytes.to_sized_bytes(), proof, &mut fx, &mut meta) } @@ -653,7 +688,7 @@ impl< challenge: &[u8], ) -> Result>, Error> { let mut challenge_reader = BitReader::from_bytes_be(&challenge[0..8], 64); - let f7 = challenge_reader.read_u64(*self.plot_file().k() as usize)?; + let f7 = challenge_reader.read_u64(self.plot_file().k() as usize)?; let (match_count, p7base_index) = self.get_p7indices_for_f7(f7).await?; if match_count == 0 { Err(Error::new( @@ -702,10 +737,10 @@ impl< c2index -= 1; } let c1start_index = (c2index as u64) * K_CHECKPOINT2INTERVAL as u64; - let k = *self.file.k() as usize; + let k = self.file.k() as usize; let f7size_bytes = ucdiv_t(k, 8); let f7bit_count = f7size_bytes * 8; - let c1table_address = *self.file.table_address(&PlotTable::C1); + let c1table_address = self.file.table_address(&PlotTable::C1); let c1table_size = self.file.table_size(&PlotTable::C1); let c1table_end = c1table_address + c1table_size; let c1entry_address = c1table_address + c1start_index * f7size_bytes as u64; @@ -784,17 +819,17 @@ impl< pub fn header(&self) -> &PlotHeader { self.file.header() } - pub fn plot_id(&self) -> &Bytes32 { + pub fn plot_id(&self) -> Bytes32 { self.file.plot_id() } pub fn compression_level(&self) -> u8 { - *self.file.compression_level() + self.file.compression_level() } pub fn calculate_max_deltas_size(&self, table: &PlotTable) -> u32 { if !self.is_compressed_table(table) { EntrySizes::calculate_max_deltas_size(table) } else { - let info = get_compression_info_for_level(*self.file.compression_level()); + let info = get_compression_info_for_level(self.file.compression_level()); let lp_size = ucdiv((self.file.k() * 2) as u32, 8); let stub_byte_size = self.calculate_lp_stubs_size(table); info.table_park_size as u32 - (lp_size + stub_byte_size) @@ -807,7 +842,7 @@ impl< if !self.is_compressed_table(table) { (self.file.k() - K_STUB_MINUS_BITS) as u32 } else { - get_compression_info_for_level(*self.file.compression_level()).stub_size_bits + get_compression_info_for_level(self.file.compression_level()).stub_size_bits } } pub fn calculate_lp_stubs_size(&self, table: &PlotTable) -> u32 { @@ -822,7 +857,7 @@ impl< park_index: u64, ) -> Result { let park_size = self.get_park_size_for_table(table); - let k = *self.plot_file().k() as u32; + let k = self.plot_file().k() as u32; let table_max_size = self.plot_file().table_size(table); let table_address = self.plot_file().table_address(table); let max_parks = table_max_size / park_size; @@ -894,18 +929,18 @@ impl< let r = K_RVALUES[*table as usize]; encoding::get_d_table(r) } else { - create_compression_dtable(*self.file.compression_level()) + create_compression_dtable(self.file.compression_level()) } } async fn load_c2entries(&mut self) -> Result<(), Error> { let c2size = self.file.table_size(&PlotTable::C2) as usize; if c2size != 0 { - let k = *self.file.k() as usize; + let k = self.file.k() as usize; let f7byte_size = ucdiv_t(k, 8); let c2max_entries = c2size / f7byte_size; if c2max_entries > 0 { - let address = *self.file.table_address(&PlotTable::C2); - let file = self.file.file().clone(); + let address = self.file.table_address(&PlotTable::C2); + let file = self.file.file(); let mut file_lock = file.lock().await; file_lock.seek(SeekFrom::Start(address)).await?; let mut buffer = vec![0; c2size]; @@ -952,6 +987,7 @@ pub fn read_plot_header(file: &mut std::fs::File) -> Result { if HEADER_V2_MAGIC == full_buffer[0..4] { Ok(PlotHeader::V2(parse_v2(&full_buffer)?)) } else if HEADER_MAGIC == full_buffer[0..19] { + //TODO Gigahorse plots also follow this format but cant be actually read, memo is encrypted. Ok(PlotHeader::V1(parse_v1(&full_buffer)?)) } else { Err(Error::new( diff --git a/proof_of_space/src/verifier.rs b/proof_of_space/src/verifier.rs index 51b6eac..cd26485 100644 --- a/proof_of_space/src/verifier.rs +++ b/proof_of_space/src/verifier.rs @@ -163,7 +163,7 @@ async fn validate_disk( Ok(proof) => { // Now we can validate the proof match get_f7_from_proof( - *reader.plot_file().k() as u32, + reader.plot_file().k() as u32, reader.plot_id().to_sized_bytes(), &proof, &mut fx, @@ -335,21 +335,25 @@ pub async fn check_plot>( ) -> Result<(usize, usize), Error> { debug!("Testing plot {:?}", path.as_ref()); let reader = PlotReader::new(DiskPlot::new(path.as_ref()).await?, None, None).await?; - if *reader.plot_file().compression_level() > 0 { + if reader.plot_file().compression_level() > 0 { warn!( "Plot Check skipped for plot at compression level {}", reader.plot_file().compression_level() ); return Ok((challenges, 0)); } - let k = match reader.header() { - PlotHeader::V1(h) => h.k, - PlotHeader::V2(h) => h.k, - }; let id = match reader.header() { + //This is used to filter out GH plots PlotHeader::V1(h) => h.id, PlotHeader::V2(h) => h.id, + PlotHeader::GHv2_5(_) => { + return Err(Error::new( + ErrorKind::InvalidData, + "Gigahorse Plots are Not Supported", + )) + } }; + let k = reader.header().k(); let mut total_proofs = 0; let mut bad_proofs = 0; for i in 0..challenges { @@ -453,7 +457,7 @@ pub async fn test_qualities() { ) .await .unwrap(); - let k = *compressed_reader.plot_file().k(); //They are both k32 + let k = compressed_reader.plot_file().k(); let mut challenge = hex::decode("00000000ff04b8ee9355068689bd558eafe07cc7af47ad1574b074fc34d6913a").unwrap(); let f7_size = ucdiv_t(k as usize, 8); diff --git a/puzzles/Cargo.toml b/puzzles/Cargo.toml index 8352bde..b45f713 100644 --- a/puzzles/Cargo.toml +++ b/puzzles/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dg_xch_puzzles" -version = "1.2.1" +version = "2.0.0" edition = "2021" authors = ["James Hoerr"] description = "Core library for working with CLVM puzzles" @@ -10,8 +10,8 @@ repository = "https://github.com/GalactechsLLC/dg_xch_utils/puzzles" [dependencies] blst = "0.3.11" -dg_xch_core = {path = "../core", version = "1.2.1", features = ["paperclip"] } -dg_xch_serialize = {path = "../serialize", version="1.2.1"} +dg_xch_core = {path = "../core", version = "2.0.0", features = ["paperclip"] } +dg_xch_serialize = {path = "../serialize", version="2.0.0"} hex = "0.4.3" lazy_static = "1.4.0" num-bigint = "0.4.4" diff --git a/serialize/Cargo.toml b/serialize/Cargo.toml index b35e538..23e08f7 100644 --- a/serialize/Cargo.toml +++ b/serialize/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dg_xch_serialize" -version = "1.2.1" +version = "2.0.0" edition = "2021" authors = ["James Hoerr"] description = "Low Level Support Package for dg_xch_utils" diff --git a/servers/Cargo.toml b/servers/Cargo.toml new file mode 100644 index 0000000..be125e5 --- /dev/null +++ b/servers/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "dg_xch_servers" +version = "2.0.0" +edition = "2021" +authors = ["James Hoerr"] +description = "Library for Creating Servers compatible with Chia's RPC and Websocket Protocols" +license = "Apache-2.0" +homepage = "https://github.com/GalactechsLLC/dg_xch_utils" +repository = "https://github.com/GalactechsLLC/dg_xch_utils/servers" + +[dependencies] +async-trait = "0.1.74" +blst = "0.3.11" +dg_xch_core = {path = "../core", version = "2.0.0", features = ["paperclip"] } +dg_xch_clients = {path = "../clients", version = "2.0.0" } +dg_xch_keys = {path = "../keys", version = "2.0.0" } +dg_xch_pos = {path = "../proof_of_space", version = "2.0.0" } +dg_xch_serialize = {path = "../serialize", version = "2.0.0" } +http-body-util = { version = "0.1"} +hex = "0.4.3" +hyper = {version="1.0.1", features=["full"]} +hyper-tungstenite = "0.12.0" +hyper-util = {version="0.1.1", features=["full"]} +log = "0.4.20" +prometheus = {version="0.13.3", features=["protobuf"], optional = true} +rustls = {version = "0.21.9", features = ["dangerous_configuration"] } +serde = { version = "1.0.193", features = ["derive"] } +tokio = {version = "1.34.0", features=["rt-multi-thread", "sync", "signal", "macros", "process", "time", "fs", "net"]} +tokio-rustls = {version = "0.24.1", features = [] } +tokio-tungstenite = {version = "0.20.1", features = ["rustls-tls-webpki-roots", "rustls"] } +uuid = {version="1.6.1", features=["v4"]} + +[features] +metrics = ["dep:prometheus", "dg_xch_core/metrics", "dg_xch_clients/metrics"] +default = [] \ No newline at end of file diff --git a/servers/src/lib.rs b/servers/src/lib.rs new file mode 100644 index 0000000..5c81f21 --- /dev/null +++ b/servers/src/lib.rs @@ -0,0 +1,17 @@ +pub mod websocket; + +fn _version() -> &'static str { + env!("CARGO_PKG_VERSION") +} +fn _pkg_name() -> &'static str { + env!("CARGO_PKG_NAME") +} + +pub fn version() -> String { + format!("{}: {}", _pkg_name(), _version()) +} + +#[test] +fn test_version() { + println!("{}", version()); +} diff --git a/servers/src/rpc/mod.rs b/servers/src/rpc/mod.rs new file mode 100644 index 0000000..e69de29 diff --git a/servers/src/websocket/farmer/handshake.rs b/servers/src/websocket/farmer/handshake.rs new file mode 100644 index 0000000..044bc9c --- /dev/null +++ b/servers/src/websocket/farmer/handshake.rs @@ -0,0 +1,94 @@ +use crate::version; +use crate::websocket::farmer::FarmerServerConfig; +use async_trait::async_trait; +use blst::min_pk::SecretKey; +use dg_xch_core::blockchain::sized_bytes::{Bytes32, Bytes48}; +use dg_xch_core::protocols::harvester::HarvesterHandshake; +use dg_xch_core::protocols::shared::{Handshake, CAPABILITIES, PROTOCOL_VERSION}; +use dg_xch_core::protocols::{ + ChiaMessage, MessageHandler, NodeType, PeerMap, ProtocolMessageTypes, +}; +use dg_xch_serialize::ChiaSerialize; +use hyper_tungstenite::tungstenite::Message; +use log::{debug, info}; +use std::collections::HashMap; +use std::io::{Cursor, Error}; +use std::sync::Arc; +use tokio::sync::Mutex; + +pub struct HandshakeHandle { + pub config: Arc, + pub farmer_private_keys: Arc>>, + pub pool_public_keys: Arc>>, +} +#[async_trait] +impl MessageHandler for HandshakeHandle { + async fn handle( + &self, + msg: Arc, + peer_id: Arc, + peers: PeerMap, + ) -> Result<(), Error> { + let mut cursor = Cursor::new(&msg.data); + let handshake = Handshake::from_bytes(&mut cursor)?; + debug!("New Peer: {}", &peer_id); + if let Some(peer) = peers.lock().await.get(&peer_id).cloned() { + let (network_id, server_port) = { + let cfg = self.config.clone(); + (cfg.network.clone(), cfg.websocket.port) + }; + *peer.node_type.lock().await = NodeType::from(handshake.node_type); + peer.websocket + .lock() + .await + .send(Message::Binary( + ChiaMessage::new( + ProtocolMessageTypes::Handshake, + &Handshake { + network_id, + protocol_version: PROTOCOL_VERSION.to_string(), + software_version: version(), + server_port, + node_type: NodeType::Farmer as u8, + capabilities: CAPABILITIES + .iter() + .map(|e| (e.0, e.1.to_string())) + .collect(), + }, + msg.id, + ) + .to_bytes(), + )) + .await + .unwrap_or_default(); + if NodeType::Harvester as u8 == handshake.node_type { + let farmer_public_keys = self + .farmer_private_keys + .lock() + .await + .iter() + .map(|k| k.sk_to_pk().to_bytes().into()) + .collect(); + let pool_public_keys = self.pool_public_keys.lock().await.keys().cloned().collect(); + info! {"Harvester Connected. Sending Keys: ({:?}n {:?})", &farmer_public_keys, &pool_public_keys} + peer.websocket + .lock() + .await + .send(Message::Binary( + ChiaMessage::new( + ProtocolMessageTypes::HarvesterHandshake, + &HarvesterHandshake { + farmer_public_keys, + pool_public_keys, + }, + None, + ) + .to_bytes(), + )) + .await + .unwrap_or_default(); + } + } + Ok(()) + } +} diff --git a/servers/src/websocket/farmer/mod.rs b/servers/src/websocket/farmer/mod.rs new file mode 100644 index 0000000..5716fc9 --- /dev/null +++ b/servers/src/websocket/farmer/mod.rs @@ -0,0 +1,322 @@ +use crate::websocket::{WebsocketServer, WebsocketServerConfig}; +use blst::min_pk::SecretKey; +use dg_xch_clients::api::pool::PoolClient; +use dg_xch_clients::websocket::farmer::FarmerClient; +use dg_xch_core::blockchain::sized_bytes::{hex_to_bytes, Bytes32, Bytes48}; +use dg_xch_core::clvm::bls_bindings::{sign, verify_signature}; +use dg_xch_core::config::PoolWalletConfig; +use dg_xch_core::protocols::farmer::{FarmerPoolState, FarmerSharedState}; +use dg_xch_core::protocols::pool::{ + get_current_authentication_token, AuthenticationPayload, GetFarmerRequest, GetFarmerResponse, + PoolError, PoolErrorCode, PostFarmerPayload, PostFarmerRequest, PostFarmerResponse, + PutFarmerPayload, PutFarmerRequest, PutFarmerResponse, +}; +use dg_xch_core::protocols::{ChiaMessageFilter, ChiaMessageHandler, ProtocolMessageTypes}; +use dg_xch_keys::decode_puzzle_hash; +use dg_xch_serialize::{hash_256, ChiaSerialize}; +use log::{error, info}; +use std::collections::HashMap; +use std::io::Error; +use std::sync::atomic::AtomicBool; +use std::sync::Arc; +use tokio::sync::Mutex; +use uuid::Uuid; + +mod handshake; +mod new_proof_or_space; +mod respond_signatures; + +use crate::websocket::farmer::new_proof_or_space::NewProofOfSpaceHandle; +use crate::websocket::farmer::respond_signatures::RespondSignaturesHandle; +use handshake::HandshakeHandle; + +pub struct FarmerServerConfig { + pub network: String, + pub websocket: WebsocketServerConfig, + pub farmer_reward_payout_address: Bytes32, + pub pool_rewards_payout_address: Bytes32, +} + +pub struct FarmerServer { + pub server: WebsocketServer, + pub shared_state: Arc, + pub pool_client: Arc, + pub config: Arc, +} +impl FarmerServer { + pub fn new( + config: FarmerServerConfig, + pool_client: Arc, + shared_state: Arc, + full_node_client: Arc>>, + additional_headers: Arc>, + ) -> Result { + let config = Arc::new(config); + let handles = Arc::new(Mutex::new(Self::handles( + config.clone(), + pool_client.clone(), + shared_state.clone(), + full_node_client, + additional_headers, + ))); + Ok(Self { + server: WebsocketServer::new( + &config.websocket, + shared_state.harvester_peers.clone(), + handles, + )?, + shared_state, + pool_client, + config, + }) + } + + fn handles( + config: Arc, + pool_client: Arc, + shared_state: Arc, + full_node_client: Arc>>, + additional_headers: Arc>, + ) -> HashMap> { + HashMap::from([ + ( + Uuid::new_v4(), + Arc::new(ChiaMessageHandler::new( + Arc::new(ChiaMessageFilter { + msg_type: Some(ProtocolMessageTypes::Handshake), + id: None, + }), + Arc::new(HandshakeHandle { + config: config.clone(), + farmer_private_keys: shared_state.farmer_private_keys.clone(), + pool_public_keys: shared_state.pool_public_keys.clone(), + }), + )), + ), + ( + Uuid::new_v4(), + Arc::new(ChiaMessageHandler::new( + Arc::new(ChiaMessageFilter { + msg_type: Some(ProtocolMessageTypes::NewProofOfSpace), + id: None, + }), + Arc::new(NewProofOfSpaceHandle { + pool_client: pool_client.clone(), + signage_points: shared_state.signage_points.clone(), + quality_to_identifiers: shared_state.quality_to_identifiers.clone(), + proofs_of_space: shared_state.proofs_of_space.clone(), + cache_time: shared_state.cache_time.clone(), + farmer_private_keys: shared_state.farmer_private_keys.clone(), + owner_secret_keys: shared_state.owner_secret_keys.clone(), + pool_state: shared_state.pool_state.clone(), + config: config.clone(), + headers: additional_headers.clone(), + #[cfg(feature = "metrics")] + metrics: shared_state.metrics.clone(), + }), + )), + ), + ( + Uuid::new_v4(), + Arc::new(ChiaMessageHandler::new( + Arc::new(ChiaMessageFilter { + msg_type: Some(ProtocolMessageTypes::RespondSignatures), + id: None, + }), + Arc::new(RespondSignaturesHandle { + signage_points: shared_state.signage_points.clone(), + quality_to_identifiers: shared_state.quality_to_identifiers.clone(), + proofs_of_space: shared_state.proofs_of_space.clone(), + cache_time: shared_state.cache_time.clone(), + pool_public_keys: shared_state.pool_public_keys.clone(), + farmer_private_keys: shared_state.farmer_private_keys.clone(), + owner_secret_keys: shared_state.owner_secret_keys.clone(), + pool_state: shared_state.pool_state.clone(), + full_node_client: full_node_client.clone(), + config, + headers: additional_headers.clone(), + #[cfg(feature = "metrics")] + metrics: shared_state.metrics.clone(), + }), + )), + ), + ]) + } + + pub async fn run(&self, run: Arc) -> Result<(), Error> { + self.server.run(run).await + } +} + +fn parse_payout_address(s: String) -> Result { + Ok(if s.starts_with("xch") || s.starts_with("txch") { + hex::encode(decode_puzzle_hash(&s)?) + } else if s.len() == 64 { + match hex_to_bytes(&s) { + Ok(h) => hex::encode(h), + Err(_) => s, + } + } else { + s + }) +} + +pub async fn get_farmer( + pool_config: &PoolWalletConfig, + authentication_token_timeout: u8, + authentication_sk: &SecretKey, + client: Arc, + additional_headers: Arc>, +) -> Result { + let authentication_token = get_current_authentication_token(authentication_token_timeout); + let msg = AuthenticationPayload { + method_name: "get_farmer".to_string(), + launcher_id: pool_config.launcher_id, + target_puzzle_hash: pool_config.target_puzzle_hash, + authentication_token, + } + .to_bytes(); + let to_sign = hash_256(&msg); + let signature = sign(authentication_sk, &to_sign); + if !verify_signature(&authentication_sk.sk_to_pk(), &to_sign, &signature) { + error!("Farmer GET Failed to Validate Signature"); + return Err(PoolError { + error_code: PoolErrorCode::InvalidSignature as u8, + error_message: "Local Failed to Validate Signature".to_string(), + }); + } + client + .get_farmer( + &pool_config.pool_url, + GetFarmerRequest { + launcher_id: pool_config.launcher_id, + authentication_token, + signature: signature.to_bytes().into(), + }, + &Some(additional_headers.as_ref().clone()), + ) + .await +} + +async fn do_auth( + pool_config: &PoolWalletConfig, + owner_sk: &SecretKey, +) -> Result { + if owner_sk.sk_to_pk().to_bytes() != *pool_config.owner_public_key.to_sized_bytes() { + return Err(PoolError { + error_code: PoolErrorCode::ServerException as u8, + error_message: "Owner Keys Mismatch".to_string(), + }); + } + Ok(owner_sk.sk_to_pk().to_bytes().into()) +} + +pub async fn post_farmer( + pool_config: &PoolWalletConfig, + payout_instructions: &str, + authentication_token_timeout: u8, + owner_sk: &SecretKey, + suggested_difficulty: Option, + client: Arc, + additional_headers: Arc>, +) -> Result { + let payload = PostFarmerPayload { + launcher_id: pool_config.launcher_id, + authentication_token: get_current_authentication_token(authentication_token_timeout), + authentication_public_key: do_auth(pool_config, owner_sk).await?, + payout_instructions: parse_payout_address(payout_instructions.to_string()).map_err( + |e| PoolError { + error_code: PoolErrorCode::InvalidPayoutInstructions as u8, + error_message: format!( + "Failed to Parse Payout Instructions: {}, {:?}", + payout_instructions, e + ), + }, + )?, + suggested_difficulty, + }; + let to_sign = hash_256(payload.to_bytes()); + let signature = sign(owner_sk, &to_sign); + if !verify_signature(&owner_sk.sk_to_pk(), &to_sign, &signature) { + error!("Farmer POST Failed to Validate Signature"); + return Err(PoolError { + error_code: PoolErrorCode::InvalidSignature as u8, + error_message: "Local Failed to Validate Signature".to_string(), + }); + } + client + .post_farmer( + &pool_config.pool_url, + PostFarmerRequest { + payload, + signature: signature.to_bytes().into(), + }, + &Some(additional_headers.as_ref().clone()), + ) + .await +} + +pub async fn put_farmer( + pool_config: &PoolWalletConfig, + payout_instructions: &str, + authentication_token_timeout: u8, + owner_sk: &SecretKey, + suggested_difficulty: Option, + client: Arc, + additional_headers: Arc>, +) -> Result { + let authentication_public_key = do_auth(pool_config, owner_sk).await?; + let payload = PutFarmerPayload { + launcher_id: pool_config.launcher_id, + authentication_token: get_current_authentication_token(authentication_token_timeout), + authentication_public_key: Some(authentication_public_key), + payout_instructions: parse_payout_address(payout_instructions.to_string()).ok(), + suggested_difficulty, + }; + let to_sign = hash_256(payload.to_bytes()); + let signature = sign(owner_sk, &to_sign); + if !verify_signature(&owner_sk.sk_to_pk(), &to_sign, &signature) { + error!("Local Failed to Validate Signature"); + return Err(PoolError { + error_code: PoolErrorCode::InvalidSignature as u8, + error_message: "Local Failed to Validate Signature".to_string(), + }); + } + let request = PutFarmerRequest { + payload, + signature: signature.to_bytes().into(), + }; + client + .put_farmer( + &pool_config.pool_url, + request, + &Some(additional_headers.as_ref().clone()), + ) + .await +} + +pub async fn update_pool_farmer_info( + pool_state: &mut FarmerPoolState, + pool_config: &PoolWalletConfig, + authentication_token_timeout: u8, + authentication_sk: &SecretKey, + client: Arc, + additional_headers: Arc>, +) -> Result { + let response = get_farmer( + pool_config, + authentication_token_timeout, + authentication_sk, + client, + additional_headers, + ) + .await?; + pool_state.current_difficulty = Some(response.current_difficulty); + pool_state.current_points = response.current_points; + info!( + "Updating Pool Difficulty: {:?} ", + pool_state.current_difficulty + ); + info!("Updating Current Points: {:?} ", pool_state.current_points); + Ok(response) +} diff --git a/servers/src/websocket/farmer/new_proof_or_space.rs b/servers/src/websocket/farmer/new_proof_or_space.rs new file mode 100644 index 0000000..73f63e9 --- /dev/null +++ b/servers/src/websocket/farmer/new_proof_or_space.rs @@ -0,0 +1,504 @@ +use crate::websocket::farmer::{update_pool_farmer_info, FarmerServerConfig}; +use async_trait::async_trait; +use blst::min_pk::{AggregateSignature, PublicKey, SecretKey, Signature}; +use blst::BLST_ERROR; +use dg_xch_clients::api::pool::PoolClient; +use dg_xch_clients::websocket::oneshot; +use dg_xch_core::blockchain::proof_of_space::{generate_plot_public_key, generate_taproot_sk}; +use dg_xch_core::blockchain::sized_bytes::{Bytes32, Bytes48, SizedBytes}; +use dg_xch_core::clvm::bls_bindings::{sign, sign_prepend, AUG_SCHEME_DST}; +use dg_xch_core::consensus::constants::CONSENSUS_CONSTANTS_MAP; +use dg_xch_core::consensus::pot_iterations::{ + calculate_iterations_quality, calculate_sp_interval_iters, +}; +#[cfg(feature = "metrics")] +use dg_xch_core::protocols::farmer::FarmerMetrics; +use dg_xch_core::protocols::farmer::{ + FarmerIdentifier, FarmerPoolState, NewSignagePoint, ProofsMap, +}; +use dg_xch_core::protocols::harvester::{NewProofOfSpace, RequestSignatures, RespondSignatures}; +use dg_xch_core::protocols::pool::{ + get_current_authentication_token, PoolErrorCode, PostPartialPayload, PostPartialRequest, +}; +use dg_xch_core::protocols::{ChiaMessage, MessageHandler, PeerMap, ProtocolMessageTypes}; +use dg_xch_pos::verify_and_get_quality_string; +use dg_xch_serialize::hash_256; +use dg_xch_serialize::ChiaSerialize; +use hyper_tungstenite::tungstenite::Message; +use log::{debug, error, info, warn}; +use std::collections::HashMap; +use std::io::{Cursor, Error, ErrorKind}; +use std::sync::atomic::{AtomicU16, Ordering}; +use std::sync::Arc; +use std::time::Instant; +use tokio::sync::Mutex; + +static ONE_SHOT_COUNTER: AtomicU16 = AtomicU16::new(0); + +pub struct NewProofOfSpaceHandle { + pub pool_client: Arc, + pub signage_points: Arc>>>, + pub quality_to_identifiers: Arc>>, + pub proofs_of_space: ProofsMap, + pub cache_time: Arc>>, + pub farmer_private_keys: Arc>>, + pub owner_secret_keys: Arc>>, + pub pool_state: Arc>>, + pub config: Arc, + pub headers: Arc>, + #[cfg(feature = "metrics")] + pub metrics: Arc>>, +} +#[async_trait] +impl MessageHandler for NewProofOfSpaceHandle { + async fn handle( + &self, + msg: Arc, + peer_id: Arc, + peers: PeerMap, + ) -> Result<(), Error> { + let exists; + { + exists = peers.lock().await.get(&peer_id).is_some(); + } + if exists { + let mut cursor = Cursor::new(&msg.data); + let new_pos = NewProofOfSpace::from_bytes(&mut cursor)?; + if let Some(sps) = self.signage_points.lock().await.get(&new_pos.sp_hash) { + let constants = CONSENSUS_CONSTANTS_MAP + .get(&self.config.network) + .cloned() + .unwrap_or_default(); + for sp in sps { + if let Some(qs) = verify_and_get_quality_string( + &new_pos.proof, + &constants, + &new_pos.challenge_hash, + &new_pos.sp_hash, + sp.peak_height, + ) { + let required_iters = calculate_iterations_quality( + constants.difficulty_constant_factor, + &qs, + new_pos.proof.size, + sp.difficulty, + &new_pos.sp_hash, + ); + if required_iters + < calculate_sp_interval_iters(&constants, sp.sub_slot_iters)? + { + let request = RequestSignatures { + plot_identifier: new_pos.plot_identifier.clone(), + challenge_hash: new_pos.challenge_hash, + sp_hash: new_pos.sp_hash, + messages: vec![sp.challenge_chain_sp, sp.reward_chain_sp], + }; + let mut farmer_pos = self.proofs_of_space.lock().await; + if farmer_pos.get(&new_pos.sp_hash).is_none() { + farmer_pos.insert(new_pos.sp_hash, vec![]); + } + farmer_pos + .get_mut(&new_pos.sp_hash) + .expect("Should not happen, item created above") + .push((new_pos.plot_identifier.clone(), new_pos.proof.clone())); + self.cache_time + .lock() + .await + .insert(new_pos.sp_hash, Instant::now()); + self.quality_to_identifiers.lock().await.insert( + qs, + FarmerIdentifier { + plot_identifier: new_pos.plot_identifier.clone(), + challenge_hash: new_pos.challenge_hash, + sp_hash: new_pos.sp_hash, + peer_node_id: *peer_id, + }, + ); + self.cache_time.lock().await.insert(qs, Instant::now()); + if let Some(p) = peers.lock().await.get(&peer_id).cloned() { + let _ = p + .websocket + .lock() + .await + .send(Message::Binary( + ChiaMessage::new( + ProtocolMessageTypes::RequestSignatures, + &request, + None, + ) + .to_bytes(), + )) + .await; + } + } + if let Some(p2_singleton_puzzle_hash) = + &new_pos.proof.pool_contract_puzzle_hash + { + if let Some(pool_state) = self + .pool_state + .lock() + .await + .get_mut(p2_singleton_puzzle_hash) + { + if let Some(pool_config) = pool_state.pool_config.clone() { + let (pool_url, launcher_id) = + (pool_config.pool_url.as_str(), pool_config.launcher_id); + if pool_url.is_empty() { + return Ok(()); + } + if let Some(pool_dif) = pool_state.current_difficulty { + let required_iters = calculate_iterations_quality( + constants.difficulty_constant_factor, + &qs, + new_pos.proof.size, + pool_dif, + &new_pos.sp_hash, + ); + if required_iters + >= calculate_sp_interval_iters( + &constants, + constants.pool_sub_slot_iters, + )? + { + debug!( + "Proof of space not good enough for pool {}: {:?}", + pool_url, pool_state.current_difficulty + ); + return Ok(()); + } + if let Some(auth_token_timeout) = + pool_state.authentication_token_timeout + { + let is_eos = new_pos.signage_point_index == 0; + let payload = PostPartialPayload { + launcher_id, + authentication_token: + get_current_authentication_token( + auth_token_timeout, + ), + proof_of_space: new_pos.proof.clone(), + sp_hash: new_pos.sp_hash, + end_of_sub_slot: is_eos, + harvester_id: *peer_id, + }; + let to_sign = hash_256(payload.to_bytes()); + let request = RequestSignatures { + plot_identifier: new_pos.plot_identifier.clone(), + challenge_hash: new_pos.challenge_hash, + sp_hash: new_pos.sp_hash, + messages: vec![Bytes32::new(&to_sign)], + }; + if let Some(peer) = + peers.lock().await.get(&peer_id).cloned() + { + let msg_id = Some( + ONE_SHOT_COUNTER.fetch_add(1, Ordering::SeqCst), + ); + let respond_sigs: RespondSignatures = oneshot( + peer.websocket.clone(), + ChiaMessage::new( + ProtocolMessageTypes::RequestSignatures, + &request, + msg_id, + ), + Some(ProtocolMessageTypes::RespondSignatures), + msg_id, + Some(15000), + ) + .await?; + let response_msg_sig = if let Some(f) = + respond_sigs.message_signatures.first() + { + Signature::from_bytes(f.1.to_sized_bytes()) + .map_err(|e| { + Error::new( + ErrorKind::InvalidInput, + format!("{:?}", e), + ) + })? + } else { + return Err(Error::new( + ErrorKind::InvalidInput, + "No Signature in Response", + )); + }; + let mut plot_sig = None; + let local_pk = PublicKey::from_bytes( + respond_sigs.local_pk.to_sized_bytes(), + ) + .map_err(|e| { + Error::new( + ErrorKind::InvalidInput, + format!("{:?}", e), + ) + })?; + for sk in + self.farmer_private_keys.lock().await.iter() + { + let pk = sk.sk_to_pk(); + if pk.to_bytes() + == *respond_sigs.farmer_pk.to_sized_bytes() + { + let agg_pk = generate_plot_public_key( + &local_pk, &pk, true, + )?; + if agg_pk.to_bytes() + != *new_pos + .proof + .plot_public_key + .to_sized_bytes() + { + return Err(Error::new( + ErrorKind::InvalidInput, + "Key Mismatch", + )); + } + let sig_farmer = + sign_prepend(sk, &to_sign, &agg_pk); + let taproot_sk = + generate_taproot_sk(&local_pk, &pk)?; + let taproot_sig = sign_prepend( + &taproot_sk, + &to_sign, + &agg_pk, + ); + + let p_sig = AggregateSignature::aggregate( + &[ + &sig_farmer, + &response_msg_sig, + &taproot_sig, + ], + true, + ) + .map_err(|e| { + Error::new( + ErrorKind::InvalidInput, + format!("{:?}", e), + ) + })?; + if p_sig.to_signature().verify( + true, + to_sign.as_ref(), + AUG_SCHEME_DST, + &agg_pk.to_bytes(), + &agg_pk, + true, + ) != BLST_ERROR::BLST_SUCCESS + { + warn!( + "Failed to validate partial signature {:?}", + p_sig.to_signature() + ); + continue; + } + plot_sig = Some(p_sig); + } + } + if let Some(auth_key) = self + .owner_secret_keys + .lock() + .await + .get(&pool_config.owner_public_key) + { + let auth_sig = sign(auth_key, &to_sign); + if let Some(plot_sig) = plot_sig { + let agg_sig = + AggregateSignature::aggregate( + &[ + &plot_sig.to_signature(), + &auth_sig, + ], + true, + ) + .map_err(|e| { + Error::new( + ErrorKind::InvalidInput, + format!("{:?}", e), + ) + })?; + let post_request = PostPartialRequest { + payload, + aggregate_signature: agg_sig + .to_signature() + .to_bytes() + .into(), + }; + debug!( + "Submitting partial for {} to {}", + post_request + .payload + .launcher_id + .to_string(), + pool_url + ); + pool_state.points_found_since_start += + pool_state + .current_difficulty + .unwrap_or_default(); + pool_state.points_found_24h.push(( + Instant::now(), + pool_state + .current_difficulty + .unwrap_or_default(), + )); + #[cfg(feature = "metrics")] + if let Some(r) = + self.metrics.lock().await.as_mut() + { + use std::time::Duration; + let now = Instant::now(); + if let Some(c) = &mut r.points_found_24h + { + c.set( + pool_state + .points_found_24h.iter().filter(|v| now.duration_since(v.0) < Duration::from_secs(60 * 60 * 24) ).map(|v| v.1).sum() + ) + } + } + debug!( + "POST /partial request {:?}", + &post_request + ); + match self + .pool_client + .post_partial( + pool_url, + post_request, + &Some( + self.headers.as_ref().clone(), + ), + ) + .await + { + Ok(resp) => { + pool_state + .points_acknowledged_since_start += + resp.new_difficulty; + pool_state.current_points += + resp.new_difficulty; + pool_state + .points_acknowledged_24h + .push(( + Instant::now(), + pool_state + .current_difficulty + .unwrap_or_default(), + )); + #[cfg(feature = "metrics")] + if let Some(r) = self + .metrics + .lock() + .await + .as_mut() + { + use std::time::Duration; + let now = Instant::now(); + if let Some(c) = &mut r + .points_acknowledged_24h + { + c.set( + pool_state + .points_acknowledged_24h.iter().filter(|v| now.duration_since(v.0) < Duration::from_secs(60 * 60 * 24) ).map(|v| v.1).sum() + ) + } + } + if pool_state + .current_difficulty + .unwrap_or_default() + != resp.new_difficulty + { + info!( + "New Pool Difficulty: {:?} ", + pool_state.current_difficulty + ); + } + pool_state.current_difficulty = + Some(resp.new_difficulty); + #[cfg(feature = "metrics")] + if let Some(r) = self + .metrics + .lock() + .await + .as_mut() + { + if let Some(c) = + &mut r.current_difficulty + { + c.set(resp.new_difficulty); + } + } + debug!( + "Current Points: {:?} ", + pool_state.current_points + ); + } + Err(e) => { + error!("Error in pooling: {:?}", e); + pool_state.pool_errors_24h.push(( + Instant::now(), + format!("{:?}", e), + )); + if e.error_code + == PoolErrorCode::ProofNotGoodEnough as u8 + { + error!("Partial not good enough, forcing pool farmer update to get our current difficulty."); + pool_state.next_farmer_update = Instant::now(); + let _ = update_pool_farmer_info( + pool_state, + &pool_config, + auth_token_timeout, + auth_key, + self.pool_client.clone(), + self.headers.clone() + ) + .await; + } + if e.error_code + == PoolErrorCode::InvalidSignature as u8 + { + error!("Invalid Signature, Forcing Pool Update"); + pool_state.next_farmer_update = Instant::now(); + } + return Ok(()); + } + } + } + } else { + warn!("No authentication sk for {p2_singleton_puzzle_hash}"); + return Ok(()); + } + } + } else { + warn!("No pool specific authentication_token_timeout has been set for {p2_singleton_puzzle_hash}, check communication with the pool."); + return Ok(()); + } + } else { + warn!("No pool specific difficulty has been set for {p2_singleton_puzzle_hash}, check communication with the pool, skipping this partial to {}.", pool_url); + return Ok(()); + } + } else { + warn!("No Pool Config for {p2_singleton_puzzle_hash}"); + return Ok(()); + } + } else { + warn!("Did not find pool info for {p2_singleton_puzzle_hash}"); + return Ok(()); + } + } else { + debug!("Not a pooling proof of space"); + } + } else { + warn!("Invalid proof of space {:?}", new_pos); + } + } + } else { + warn!( + "Received response for a signage point that we do not have {}", + &new_pos.sp_hash + ); + } + } + Ok(()) + } +} diff --git a/servers/src/websocket/farmer/respond_signatures.rs b/servers/src/websocket/farmer/respond_signatures.rs new file mode 100644 index 0000000..9f50860 --- /dev/null +++ b/servers/src/websocket/farmer/respond_signatures.rs @@ -0,0 +1,463 @@ +use crate::websocket::farmer::FarmerServerConfig; +use async_trait::async_trait; +use blst::min_pk::{AggregateSignature, SecretKey}; +use blst::BLST_ERROR; +use dg_xch_clients::websocket::farmer::FarmerClient; +use dg_xch_core::blockchain::pool_target::PoolTarget; +use dg_xch_core::blockchain::proof_of_space::{generate_plot_public_key, generate_taproot_sk}; +use dg_xch_core::blockchain::sized_bytes::{Bytes32, Bytes48}; +use dg_xch_core::clvm::bls_bindings::{sign, sign_prepend, AUG_SCHEME_DST}; +use dg_xch_core::consensus::constants::{CONSENSUS_CONSTANTS_MAP, MAINNET}; +#[cfg(feature = "metrics")] +use dg_xch_core::protocols::farmer::FarmerMetrics; +use dg_xch_core::protocols::farmer::{ + DeclareProofOfSpace, FarmerIdentifier, FarmerPoolState, NewSignagePoint, ProofsMap, + SignedValues, +}; +use dg_xch_core::protocols::harvester::RespondSignatures; +use dg_xch_core::protocols::{ChiaMessage, MessageHandler, PeerMap, ProtocolMessageTypes}; +use dg_xch_pos::verify_and_get_quality_string; +use dg_xch_serialize::ChiaSerialize; +use hyper_tungstenite::tungstenite::Message; +use log::{debug, error, info, warn}; +use std::collections::HashMap; +use std::io::{Cursor, Error, ErrorKind}; +use std::sync::Arc; +use std::time::Instant; +use tokio::sync::Mutex; + +pub struct RespondSignaturesHandle { + pub signage_points: Arc>>>, + pub quality_to_identifiers: Arc>>, + pub proofs_of_space: ProofsMap, + pub cache_time: Arc>>, + pub pool_public_keys: Arc>>, + pub farmer_private_keys: Arc>>, + pub owner_secret_keys: Arc>>, + pub pool_state: Arc>>, + pub full_node_client: Arc>>, + pub config: Arc, + pub headers: Arc>, + #[cfg(feature = "metrics")] + pub metrics: Arc>>, +} +#[async_trait] +impl MessageHandler for RespondSignaturesHandle { + async fn handle( + &self, + msg: Arc, + _peer_id: Arc, + _peers: PeerMap, + ) -> Result<(), Error> { + let mut cursor = Cursor::new(&msg.data); + let response = RespondSignatures::from_bytes(&mut cursor)?; + if let Some(sps) = self.signage_points.lock().await.get(&response.sp_hash) { + if sps.is_empty() { + error!("Missing Signage Points for {}", &response.sp_hash); + } else { + let sp_index = sps + .first() + .expect("Sps was empty, Should have been caught above") + .signage_point_index; + let mut is_sp_signatures = false; + let mut found_sp_hash_debug = false; + let peak_height = sps[0].peak_height; + for sp_candidate in sps { + if response.sp_hash == response.message_signatures[0].0 { + found_sp_hash_debug = true; + if sp_candidate.reward_chain_sp == response.message_signatures[1].0 { + is_sp_signatures = true; + } + } + } + if found_sp_hash_debug { + assert!(is_sp_signatures); + } + let mut pospace = None; + { + let locked = self.proofs_of_space.lock().await; + let proofs = locked.get(&response.sp_hash); + if let Some(proofs) = proofs { + for (plot_identifier, candidate_pospace) in proofs { + if *plot_identifier == response.plot_identifier { + pospace = Some(candidate_pospace.clone()); + break; + } + } + } else { + debug!("Failed to load farmer proofs for {}", &response.sp_hash); + return Ok(()); + } + } + if let Some(pospace) = pospace { + let include_taproot = pospace.pool_contract_puzzle_hash.is_some(); + let constants = CONSENSUS_CONSTANTS_MAP + .get(&self.config.network) + .unwrap_or(&MAINNET); + if let Some(computed_quality_string) = verify_and_get_quality_string( + &pospace, + constants, + &response.challenge_hash, + &response.sp_hash, + peak_height, + ) { + if is_sp_signatures { + let (challenge_chain_sp, challenge_chain_sp_harv_sig) = + &response.message_signatures[0]; + let challenge_chain_sp_harv_sig = + challenge_chain_sp_harv_sig.try_into()?; + let (reward_chain_sp, reward_chain_sp_harv_sig) = + &response.message_signatures[1]; + let reward_chain_sp_harv_sig = reward_chain_sp_harv_sig.try_into()?; + let local_pk = response.local_pk.into(); + for sk in self.farmer_private_keys.lock().await.iter() { + let pk = sk.sk_to_pk(); + if pk.to_bytes() == *response.farmer_pk.to_sized_bytes() { + let agg_pk = + generate_plot_public_key(&local_pk, &pk, include_taproot)?; + if agg_pk.to_bytes() + != *pospace.plot_public_key.to_sized_bytes() + { + warn!( + "Key Mismatch {:?} != {:?}", + pospace.plot_public_key, agg_pk + ); + return Ok(()); + } + let (taproot_share_cc_sp, taproot_share_rc_sp) = + if include_taproot { + let taproot_sk = generate_taproot_sk(&local_pk, &pk)?; + ( + Some(sign_prepend( + &taproot_sk, + challenge_chain_sp.as_ref(), + &agg_pk, + )), + Some(sign_prepend( + &taproot_sk, + reward_chain_sp.as_ref(), + &agg_pk, + )), + ) + } else { + (None, None) + }; + let farmer_share_cc_sp = + sign_prepend(sk, challenge_chain_sp.as_ref(), &agg_pk); + let cc_sigs_to_agg = + if let Some(taproot_share_cc_sp) = &taproot_share_cc_sp { + vec![ + &challenge_chain_sp_harv_sig, + &farmer_share_cc_sp, + taproot_share_cc_sp, + ] + } else { + vec![&challenge_chain_sp_harv_sig, &farmer_share_cc_sp] + }; + let agg_sig_cc_sp = + AggregateSignature::aggregate(&cc_sigs_to_agg, true) + .map_err(|e| { + Error::new( + ErrorKind::InvalidInput, + format!("{:?}", e), + ) + })?; + if agg_sig_cc_sp.to_signature().verify( + true, + challenge_chain_sp.as_ref(), + AUG_SCHEME_DST, + &agg_pk.to_bytes(), + &agg_pk, + true, + ) != BLST_ERROR::BLST_SUCCESS + { + warn!( + "Failed to validate cc signature {:?}", + agg_sig_cc_sp.to_signature() + ); + return Ok(()); + } + + let farmer_share_rc_sp = + sign_prepend(sk, reward_chain_sp.as_ref(), &agg_pk); + let rc_sigs_to_agg = + if let Some(taproot_share_rc_sp) = &taproot_share_rc_sp { + vec![ + &reward_chain_sp_harv_sig, + &farmer_share_rc_sp, + taproot_share_rc_sp, + ] + } else { + vec![&reward_chain_sp_harv_sig, &farmer_share_rc_sp] + }; + let agg_sig_rc_sp = + AggregateSignature::aggregate(&rc_sigs_to_agg, true) + .map_err(|e| { + Error::new( + ErrorKind::InvalidInput, + format!("{:?}", e), + ) + })?; + if agg_sig_rc_sp.to_signature().verify( + true, + reward_chain_sp.as_ref(), + AUG_SCHEME_DST, + &agg_pk.to_bytes(), + &agg_pk, + true, + ) != BLST_ERROR::BLST_SUCCESS + { + warn!( + "Failed to validate rc signature {:?}", + agg_sig_rc_sp.to_signature() + ); + return Ok(()); + } + let (pool_target, pool_target_signature) = if let Some( + pool_public_key, + ) = + &pospace.pool_public_key + { + if let Some(sk) = + self.pool_public_keys.lock().await.get(pool_public_key) + { + let pool_target = PoolTarget { + max_height: 0, + puzzle_hash: self + .config + .pool_rewards_payout_address, + }; + let pool_target_signature = + sign(sk, &pool_target.to_bytes()); + (Some(pool_target), Some(pool_target_signature)) + } else { + error!("Don't have the private key for the pool key used by harvester: {pool_public_key}"); + return Ok(()); + } + } else { + (None, None) + }; + let request = DeclareProofOfSpace { + challenge_hash: response.challenge_hash, + challenge_chain_sp: *challenge_chain_sp, + signage_point_index: sp_index, + reward_chain_sp: *reward_chain_sp, + proof_of_space: pospace.clone(), + challenge_chain_sp_signature: agg_sig_cc_sp + .to_signature() + .to_bytes() + .into(), + reward_chain_sp_signature: agg_sig_rc_sp + .to_signature() + .to_bytes() + .into(), + farmer_puzzle_hash: self + .config + .farmer_reward_payout_address, + pool_target, + pool_signature: pool_target_signature + .map(|s| s.to_bytes().into()), + }; + if let Some(client) = + self.full_node_client.lock().await.as_mut() + { + let _ = client + .client + .connection + .lock() + .await + .send(Message::Binary( + ChiaMessage::new( + ProtocolMessageTypes::DeclareProofOfSpace, + &request, + None, + ) + .to_bytes(), + )) + .await; + info!("Declaring Proof of Space: {:?}", request); + #[cfg(feature = "metrics")] + if let Some(r) = self.metrics.lock().await.as_mut() { + if let Some(c) = &mut r.proofs_declared { + c.inc(); + } + } + } else { + error!( + "Failed to declare Proof of Space: {:?} No Client", + request + ); + } + } + } + } else if response.message_signatures.len() > 1 { + let (foliage_block_data_hash, foliage_sig_harvester) = + &response.message_signatures[0]; + let foliage_sig_harvester = foliage_sig_harvester.try_into()?; + let ( + foliage_transaction_block_hash, + foliage_transaction_block_sig_harvester, + ) = &response.message_signatures[1]; + let foliage_transaction_block_sig_harvester = + foliage_transaction_block_sig_harvester.try_into()?; + let local_pk = response.local_pk.into(); + for sk in self.farmer_private_keys.lock().await.iter() { + let pk = sk.sk_to_pk(); + if pk.to_bytes() == *response.farmer_pk.to_sized_bytes() { + let agg_pk = + generate_plot_public_key(&local_pk, &pk, include_taproot)?; + let ( + foliage_sig_taproot, + foliage_transaction_block_sig_taproot, + ) = if include_taproot { + let taproot_sk = generate_taproot_sk(&local_pk, &pk)?; + ( + Some(sign_prepend( + &taproot_sk, + foliage_block_data_hash.as_ref(), + &agg_pk, + )), + Some(sign_prepend( + &taproot_sk, + foliage_transaction_block_hash.as_ref(), + &agg_pk, + )), + ) + } else { + (None, None) + }; + let foliage_sig_farmer = + sign_prepend(sk, foliage_block_data_hash.as_ref(), &agg_pk); + let foliage_transaction_block_sig_farmer = sign_prepend( + sk, + foliage_transaction_block_hash.as_ref(), + &agg_pk, + ); + let foliage_sigs_to_agg = + if let Some(foliage_sig_taproot) = &foliage_sig_taproot { + vec![ + &foliage_sig_harvester, + &foliage_sig_farmer, + foliage_sig_taproot, + ] + } else { + vec![&foliage_sig_harvester, &foliage_sig_farmer] + }; + let foliage_agg_sig = + AggregateSignature::aggregate(&foliage_sigs_to_agg, true) + .map_err(|e| { + Error::new(ErrorKind::InvalidInput, format!("{:?}", e)) + })?; + + let foliage_block_sigs_to_agg = + if let Some(foliage_transaction_block_sig_taproot) = + &foliage_transaction_block_sig_taproot + { + vec![ + &foliage_transaction_block_sig_harvester, + &foliage_transaction_block_sig_farmer, + foliage_transaction_block_sig_taproot, + ] + } else { + vec![ + &foliage_transaction_block_sig_harvester, + &foliage_transaction_block_sig_farmer, + ] + }; + let foliage_block_agg_sig = AggregateSignature::aggregate( + &foliage_block_sigs_to_agg, + true, + ) + .map_err(|e| { + Error::new(ErrorKind::InvalidInput, format!("{:?}", e)) + })?; + if foliage_agg_sig.to_signature().verify( + true, + foliage_block_data_hash.as_ref(), + AUG_SCHEME_DST, + &agg_pk.to_bytes(), + &agg_pk, + true, + ) != BLST_ERROR::BLST_SUCCESS + { + warn!( + "Failed to validate foliage signature {:?}", + foliage_agg_sig.to_signature() + ); + return Ok(()); + } + if foliage_block_agg_sig.to_signature().verify( + true, + foliage_transaction_block_hash.as_ref(), + AUG_SCHEME_DST, + &agg_pk.to_bytes(), + &agg_pk, + true, + ) != BLST_ERROR::BLST_SUCCESS + { + warn!( + "Failed to validate foliage_block signature {:?}", + foliage_block_agg_sig.to_signature() + ); + return Ok(()); + } + let request = SignedValues { + quality_string: computed_quality_string, + foliage_block_data_signature: foliage_agg_sig + .to_signature() + .to_bytes() + .into(), + foliage_transaction_block_signature: foliage_block_agg_sig + .to_signature() + .to_bytes() + .into(), + }; + + if let Some(client) = + self.full_node_client.lock().await.as_mut() + { + let _ = client + .client + .connection + .lock() + .await + .send(Message::Binary( + ChiaMessage::new( + ProtocolMessageTypes::SignedValues, + &request, + None, + ) + .to_bytes(), + )) + .await; + info!("Sending Signed Values: {:?}", request); + } else { + error!( + "Failed to Sending Signed Values: {:?} No Client", + request + ); + } + } + } + } else if msg.id.is_some() { + debug!("Detected Partial Signatures Request {:?}", pospace); + return Ok(()); + } else { + warn!("Detected Possible invalid PoSpace {:?}", pospace); + return Ok(()); + } + } else { + warn!("Have invalid PoSpace {:?}", pospace); + return Ok(()); + } + } else { + debug!("Failed to find Proof for {}", &response.sp_hash); + return Ok(()); + } + } + } else { + error!("Do not have challenge hash {}", &response.challenge_hash); + } + Ok(()) + } +} diff --git a/servers/src/websocket/harvester/handshake.rs b/servers/src/websocket/harvester/handshake.rs new file mode 100644 index 0000000..9c54cf0 --- /dev/null +++ b/servers/src/websocket/harvester/handshake.rs @@ -0,0 +1,55 @@ +use crate::version; +use crate::websocket::harvester::HarvesterServerConfig; +use async_trait::async_trait; +use dg_xch_core::blockchain::sized_bytes::Bytes32; +use dg_xch_core::protocols::shared::{Handshake, CAPABILITIES, PROTOCOL_VERSION}; +use dg_xch_core::protocols::{ + ChiaMessage, MessageHandler, NodeType, PeerMap, ProtocolMessageTypes, +}; +use dg_xch_serialize::ChiaSerialize; +use hyper_tungstenite::tungstenite::Message; +use std::io::{Cursor, Error, ErrorKind}; +use std::sync::Arc; + +pub struct HandshakeHandle { + pub config: Arc, +} +#[async_trait] +impl MessageHandler for HandshakeHandle { + async fn handle( + &self, + msg: Arc, + peer_id: Arc, + peers: PeerMap, + ) -> Result<(), Error> { + let mut cursor = Cursor::new(&msg.data); + let handshake = Handshake::from_bytes(&mut cursor)?; + if let Some(peer) = peers.lock().await.get_mut(&peer_id).cloned() { + *peer.node_type.lock().await = NodeType::from(handshake.node_type); + peer.websocket + .lock() + .await + .send(Message::Binary( + ChiaMessage::new( + ProtocolMessageTypes::Handshake, + &Handshake { + network_id: self.config.network.clone(), + protocol_version: PROTOCOL_VERSION.to_string(), + software_version: version(), + server_port: self.config.websocket.port, + node_type: NodeType::Harvester as u8, + capabilities: CAPABILITIES + .iter() + .map(|e| (e.0, e.1.to_string())) + .collect(), + }, + msg.id, + ) + .to_bytes(), + )) + .await + } else { + Err(Error::new(ErrorKind::NotFound, "Failed to find peer")) + } + } +} diff --git a/servers/src/websocket/harvester/mod.rs b/servers/src/websocket/harvester/mod.rs new file mode 100644 index 0000000..1bf31b7 --- /dev/null +++ b/servers/src/websocket/harvester/mod.rs @@ -0,0 +1,50 @@ +use crate::websocket::harvester::handshake::HandshakeHandle; +use crate::websocket::{WebsocketServer, WebsocketServerConfig}; +use dg_xch_core::protocols::{ChiaMessageFilter, ChiaMessageHandler, ProtocolMessageTypes}; +use std::collections::HashMap; +use std::io::Error; +use std::sync::atomic::AtomicBool; +use std::sync::Arc; +use tokio::sync::Mutex; +use uuid::Uuid; + +pub mod handshake; + +pub struct HarvesterServerConfig { + pub network: String, + pub websocket: WebsocketServerConfig, +} + +pub struct HarvesterServer { + pub server: WebsocketServer, + pub config: Arc, +} +impl HarvesterServer { + pub fn new(config: HarvesterServerConfig) -> Result { + let config = Arc::new(config); + let handles = Arc::new(Mutex::new(Self::handles(config.clone()))); + Ok(Self { + server: WebsocketServer::new(&config.websocket, Default::default(), handles)?, + config, + }) + } + + fn handles(config: Arc) -> HashMap> { + HashMap::from([( + Uuid::new_v4(), + Arc::new(ChiaMessageHandler::new( + Arc::new(ChiaMessageFilter { + msg_type: Some(ProtocolMessageTypes::Handshake), + id: None, + }), + Arc::new(HandshakeHandle { + config: config.clone(), + }), + )), + )]) + } + + pub async fn run(&self, run: Arc) -> Result<(), Error> { + self.server.run(run).await + } +} diff --git a/servers/src/websocket/mod.rs b/servers/src/websocket/mod.rs new file mode 100644 index 0000000..84a7ef3 --- /dev/null +++ b/servers/src/websocket/mod.rs @@ -0,0 +1,240 @@ +pub mod farmer; +pub mod harvester; + +use dg_xch_core::blockchain::sized_bytes::{Bytes32, SizedBytes}; +use dg_xch_core::protocols::{ + ChiaMessageHandler, NodeType, PeerMap, SocketPeer, WebsocketConnection, WebsocketMsgStream, +}; +use dg_xch_core::ssl::{load_certs, load_private_key, AllowAny, SslInfo}; +use dg_xch_serialize::hash_256; +use http_body_util::Full; +use hyper::body::{Bytes, Incoming}; +use hyper::server::conn::http1::Builder; +use hyper::service::service_fn; +use hyper::{Request, Response}; +use hyper_tungstenite::{is_upgrade_request, upgrade, HyperWebsocket}; +use hyper_util::rt::TokioIo; +use log::{debug, error, info}; +use rustls::{RootCertStore, ServerConfig}; +use std::collections::HashMap; +use std::io::{Error, ErrorKind}; +use std::net::{Ipv4Addr, SocketAddr}; +use std::str::FromStr; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; +use std::time::Duration; +use tokio::net::TcpListener; +use tokio::select; +use tokio::sync::Mutex; +use tokio_rustls::TlsAcceptor; +use tokio_tungstenite::tungstenite; +use uuid::Uuid; + +pub struct WebsocketServerConfig { + pub host: String, + pub port: u16, + pub ssl_info: SslInfo, +} + +pub struct WebsocketServer { + pub socket_address: SocketAddr, + pub server_config: Arc, + pub peers: PeerMap, + pub message_handlers: Arc>>>, +} +impl WebsocketServer { + pub fn new( + config: &WebsocketServerConfig, + peers: PeerMap, + message_handlers: Arc>>>, + ) -> Result { + let server_config = Self::init(config) + .map_err(|e| Error::new(ErrorKind::InvalidInput, format!("Invalid Cert: {:?}", e)))?; + let socket_address = Self::init_socket(config)?; + Ok(WebsocketServer { + socket_address, + server_config, + peers, + message_handlers, + }) + } + + pub async fn run(&self, run: Arc) -> Result<(), Error> { + let listener = TcpListener::bind(self.socket_address).await?; + let acceptor = TlsAcceptor::from(self.server_config.clone()); + let mut http = Builder::new(); + http.keep_alive(true); + while run.load(Ordering::Relaxed) { + let run = run.clone(); + let peers = self.peers.clone(); + let handlers = self.message_handlers.clone(); + select!( + res = listener.accept() => { + match res { + Ok((stream, _)) => { + info!("New Client Connection"); + let peers = peers.clone(); + let message_handlers = handlers.clone(); + let stream = acceptor.accept(stream).await?; + let addr = stream.get_ref().0.peer_addr().ok(); + let mut peer_id = None; + if let Some(certs) = stream.get_ref().1.peer_certificates() { + if !certs.is_empty() { + peer_id = Some(Bytes32::new(&hash_256(&certs[0].0))); + } + } + let peer_id = Arc::new(peer_id); + let service = service_fn(move |req| { + let data = ConnectionData { + addr, + peer_id: peer_id.clone(), + req, + peers: peers.clone(), + message_handlers: message_handlers.clone(), + run: run.clone() + }; + connection_handler(data) + }); + let connection = http.serve_connection(TokioIo::new(stream), service).with_upgrades(); + tokio::spawn( async move { + if let Err(err) = connection.await { + println!("Error serving connection: {:?}", err); + } + Ok::<(), Error>(()) + }); + } + Err(e) => { + error!("Error accepting connection: {:?}", e); + } + } + }, + _ = tokio::time::sleep(Duration::from_millis(10)) => {} + ) + } + Ok(()) + } + + pub fn init(config: &WebsocketServerConfig) -> Result, Error> { + let certs = load_certs(&format!( + "{}/{}", + &config.ssl_info.root_path, &config.ssl_info.certs.private_crt + ))?; + let key = load_private_key(&format!( + "{}/{}", + &config.ssl_info.root_path, &config.ssl_info.certs.private_key + ))?; + let mut root_cert_store = RootCertStore::empty(); + for cert in load_certs(&format!( + "{}/{}", + &config.ssl_info.root_path, &config.ssl_info.ca.private_crt + ))? { + root_cert_store.add(&cert).map_err(|e| { + Error::new( + ErrorKind::InvalidInput, + format!("Invalid Root Cert for Server: {:?}", e), + ) + })?; + } + Ok(Arc::new( + ServerConfig::builder() + .with_safe_defaults() + .with_client_cert_verifier(AllowAny::new(root_cert_store)) + .with_single_cert(certs, key) + .map_err(|e| { + Error::new( + ErrorKind::InvalidInput, + format!("Invalid Cert for Server: {:?}", e), + ) + })?, + )) + } + + pub fn init_socket(config: &WebsocketServerConfig) -> Result { + Ok(SocketAddr::from(( + Ipv4Addr::from_str(if config.host == "localhost" { + "127.0.0.1" + } else { + &config.host + }) + .map_err(|e| { + Error::new( + ErrorKind::InvalidInput, + format!("Failed to parse Host: {:?}", e), + ) + })?, + config.port, + ))) + } +} + +struct ConnectionData { + pub addr: Option, + pub peer_id: Arc>, + pub req: Request, + pub peers: PeerMap, + pub message_handlers: Arc>>>, + pub run: Arc, +} + +async fn connection_handler( + mut data: ConnectionData, +) -> Result>, tungstenite::error::Error> { + if is_upgrade_request(&data.req) { + let (response, websocket) = upgrade(&mut data.req, None)?; + let addr = data + .addr + .ok_or_else(|| Error::new(ErrorKind::Other, "Invalid SocketAddr"))?; + let peer_id = Arc::new( + data.peer_id + .ok_or_else(|| Error::new(ErrorKind::Other, "Invalid Peer"))?, + ); + tokio::spawn(async move { + if let Err(e) = handle_connection( + addr, + peer_id, + websocket, + data.peers, + data.message_handlers.clone(), + data.run.clone(), + ) + .await + { + error!("Error in websocket connection: {}", e); + } + }); + Ok(response) + } else { + Ok(Response::new(Full::new(Bytes::from( + "HTTP NOT SUPPORTED ON THIS ENDPOINT", + )))) + } +} + +async fn handle_connection( + _peer_addr: SocketAddr, + peer_id: Arc, + websocket: HyperWebsocket, + peers: PeerMap, + message_handlers: Arc>>>, + run: Arc, +) -> Result<(), tungstenite::error::Error> { + let (websocket, mut stream) = WebsocketConnection::new( + WebsocketMsgStream::TokioIo(websocket.await?), + message_handlers, + peer_id.clone(), + peers.clone(), + ); + let removed = peers.lock().await.insert( + *peer_id, + Arc::new(SocketPeer { + node_type: Arc::new(Mutex::new(NodeType::Unknown)), + websocket: Arc::new(Mutex::new(websocket)), + }), + ); + if let Some(removed) = removed { + debug!("Sending Close to Peer"); + let _ = removed.websocket.lock().await.close(None).await; + } + let _ = stream.run(run).await; + Ok(()) +} diff --git a/tests/Cargo.toml b/tests/Cargo.toml index 83d753e..d632258 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dg_xch_tests" -version = "1.2.1" +version = "2.0.0" edition = "2021" authors = ["James Hoerr"] description = "Low Level Support Package for dg_xch_utils" @@ -12,14 +12,14 @@ repository = "https://github.com/GalactechsLLC/dg_xch_utils/serialize" async-trait = "0.1.74" blst = "0.3.11" bytes = "1.5.0" -dg_xch_core = {path = "../core", version = "1.2.1", features = ["paperclip"] } -dg_xch_cli = {path = "../cli", version="1.2.1"} -dg_xch_clients = {path = "../clients", version="1.2.1"} -dg_xch_keys = {path = "../keys", version="1.2.1"} -dg_xch_macros = {path = "../macros", version="1.2.1"} -dg_xch_pos = {path = "../proof_of_space", version="1.2.1"} -dg_xch_puzzles = {path = "../puzzles", version="1.2.1"} -dg_xch_serialize = {path = "../serialize", version="1.2.1"} +dg_xch_core = {path = "../core", version = "2.0.0", features = ["paperclip"] } +dg_xch_cli = {path = "../cli", version="2.0.0"} +dg_xch_clients = {path = "../clients", version="2.0.0"} +dg_xch_keys = {path = "../keys", version="2.0.0"} +dg_xch_macros = {path = "../macros", version="2.0.0"} +dg_xch_pos = {path = "../proof_of_space", version="2.0.0"} +dg_xch_puzzles = {path = "../puzzles", version="2.0.0"} +dg_xch_serialize = {path = "../serialize", version="2.0.0"} futures-util = "0.3.29" hex = "0.4.3" lazy_static = "1.4.0" diff --git a/tests/src/clients/full_node.rs b/tests/src/clients/full_node.rs index c19ab9c..c0c2015 100644 --- a/tests/src/clients/full_node.rs +++ b/tests/src/clients/full_node.rs @@ -48,9 +48,8 @@ pub async fn test_full_node_client() { #[tokio::test] pub async fn test_farmer_ws_client() { - use dg_xch_clients::protocols::ProtocolMessageTypes; use dg_xch_clients::websocket::farmer::FarmerClient; - use dg_xch_clients::websocket::{ChiaMessageFilter, ChiaMessageHandler, Websocket}; + use dg_xch_core::protocols::{ChiaMessageFilter, ChiaMessageHandler, ProtocolMessageTypes}; use futures_util::future::try_join_all; use log::{error, info}; use simple_logger::SimpleLogger; @@ -99,10 +98,10 @@ pub async fn test_farmer_ws_client() { .subscribe( signage_handle_id, ChiaMessageHandler::new( - ChiaMessageFilter { + Arc::new(ChiaMessageFilter { msg_type: Some(ProtocolMessageTypes::NewSignagePoint), id: None, - }, + }), signage_handle, ), ) @@ -139,18 +138,25 @@ pub async fn test_farmer_ws_client() { } use async_trait::async_trait; -use dg_xch_clients::protocols::farmer::NewSignagePoint; +use dg_xch_core::protocols::farmer::NewSignagePoint; use std::io::{Cursor, Error}; use std::sync::Arc; use uuid::Uuid; -use dg_xch_clients::websocket::{ChiaMessage, MessageHandler}; +use dg_xch_core::blockchain::sized_bytes::Bytes32; +use dg_xch_core::protocols::{ChiaMessage, MessageHandler, PeerMap}; + pub struct NewSignagePointEcho { pub id: Uuid, } #[async_trait] impl MessageHandler for NewSignagePointEcho { - async fn handle(&self, msg: Arc) -> Result<(), Error> { + async fn handle( + &self, + msg: Arc, + _: Arc, + _: PeerMap, + ) -> Result<(), Error> { use dg_xch_serialize::ChiaSerialize; let mut cursor = Cursor::new(&msg.data); let sp = NewSignagePoint::from_bytes(&mut cursor)?;