From d1c744010dd2d622cf5eb08e1c0b041768cefe6e Mon Sep 17 00:00:00 2001 From: Lucas Balieiro Date: Wed, 22 Oct 2025 16:25:03 -0400 Subject: [PATCH] remove some files that were commited by mistake during a rebase process --- .../src/channel_logic/channel_factory.rs | 1368 ----------------- roles/stratum-apps/src/job_creator.rs | 716 --------- 2 files changed, 2084 deletions(-) delete mode 100644 roles/stratum-apps/src/channel_logic/channel_factory.rs delete mode 100644 roles/stratum-apps/src/job_creator.rs diff --git a/roles/stratum-apps/src/channel_logic/channel_factory.rs b/roles/stratum-apps/src/channel_logic/channel_factory.rs deleted file mode 100644 index 069a83b71c..0000000000 --- a/roles/stratum-apps/src/channel_logic/channel_factory.rs +++ /dev/null @@ -1,1368 +0,0 @@ -//! # Channel Factory -//! -//! This module contains logic for creating and managing channels. - -use crate::{ - job_creator::{self, JobsCreators}, - utils::{GroupId, Id, Mutex}, - Error, -}; - -use codec_sv2::binary_sv2; -use mining_sv2::{ - ExtendedExtranonce, NewExtendedMiningJob, OpenExtendedMiningChannelSuccess, - OpenMiningChannelError, SetCustomMiningJob, SetCustomMiningJobSuccess, SetNewPrevHash, - SubmitSharesError, SubmitSharesExtended, SubmitSharesStandard, -}; -use parsers_sv2::Mining; - -use hex::DisplayHex; -use nohash_hasher::BuildNoHashHasher; -use std::{collections::HashMap, convert::TryInto, sync::Arc}; -use template_distribution_sv2::{NewTemplate, SetNewPrevHash as SetNewPrevHashFromTp}; - -use tracing::{debug, error, info, trace, warn}; - -use bitcoin::{ - block::{Header, Version}, - hash_types, - hashes::sha256d::Hash, - CompactTarget, Target, TxOut, -}; - -/// A stripped type of `SetCustomMiningJob` without the (`channel_id, `request_id` and `token`) -/// fields -#[derive(Debug)] -pub struct PartialSetCustomMiningJob { - pub version: u32, - pub prev_hash: binary_sv2::U256<'static>, - pub min_ntime: u32, - pub nbits: u32, - pub coinbase_tx_version: u32, - pub coinbase_prefix: binary_sv2::B0255<'static>, - pub coinbase_tx_input_n_sequence: u32, - pub coinbase_tx_value_remaining: u64, - pub coinbase_tx_outputs: binary_sv2::B064K<'static>, - pub coinbase_tx_locktime: u32, - pub merkle_path: binary_sv2::Seq0255<'static, binary_sv2::U256<'static>>, - pub future_job: bool, -} - -/// Represents the action that needs to be done when a new share is received. -#[derive(Debug, Clone)] -pub enum OnNewShare { - /// Used when the received is malformed, is for an inexistent channel or do not meet downstream - /// target. - SendErrorDownstream(SubmitSharesError<'static>), - /// Used when an extended channel in a proxy receive a share, and the share meet upstream - /// target, in this case a new share must be sent upstream. Also an optional template id is - /// returned, when a job declarator want to send a valid share upstream could use the - /// template for get the up job id. - SendSubmitShareUpstream((Share, Option)), - /// Used when a group channel in a proxy receive a share that is not malformed and is for a - /// valid channel in that case we relay the same exact share upstream with a new request id. - RelaySubmitShareUpstream, - /// Indicate that the share meet bitcoin target, when there is an upstream the we should send - /// the share upstream, whenever possible we should also notify the TP about it. - /// When a pool negotiate a job with downstream we do not have the template_id so we set it to - /// None - /// (share, template id, coinbase,complete extranonce) - ShareMeetBitcoinTarget((Share, Option, Vec, Vec)), - /// Indicate that the share meet downstream target, in the case we could send a success - /// response downstream. - ShareMeetDownstreamTarget, -} - -impl OnNewShare { - /// Converts standard share into extended share - pub fn into_extended(&mut self, extranonce: Vec, up_id: u32) { - match self { - OnNewShare::SendErrorDownstream(_) => (), - OnNewShare::SendSubmitShareUpstream((share, template_id)) => match share { - Share::Extended(_) => (), - Share::Standard((share, _)) => { - let share = SubmitSharesExtended { - channel_id: up_id, - sequence_number: share.sequence_number, - job_id: share.job_id, - nonce: share.nonce, - ntime: share.ntime, - version: share.version, - extranonce: extranonce.try_into().unwrap(), - }; - *self = Self::SendSubmitShareUpstream((Share::Extended(share), *template_id)); - } - }, - OnNewShare::RelaySubmitShareUpstream => (), - OnNewShare::ShareMeetBitcoinTarget((share, t_id, coinbase, ext)) => match share { - Share::Extended(_) => (), - Share::Standard((share, _)) => { - let share = SubmitSharesExtended { - channel_id: up_id, - sequence_number: share.sequence_number, - job_id: share.job_id, - nonce: share.nonce, - ntime: share.ntime, - version: share.version, - extranonce: extranonce.try_into().unwrap(), - }; - *self = Self::ShareMeetBitcoinTarget(( - Share::Extended(share), - *t_id, - coinbase.clone(), - ext.to_vec(), - )); - } - }, - OnNewShare::ShareMeetDownstreamTarget => todo!(), - } - } -} - -/// A share can be either extended or standard -#[derive(Clone, Debug)] -pub enum Share { - Extended(SubmitSharesExtended<'static>), - // share, group id - Standard((SubmitSharesStandard, u32)), -} - -/// Helper type used before a `SetNewPrevHash` has a channel_id -#[derive(Clone, Debug)] -pub struct StagedPhash { - job_id: u32, - prev_hash: binary_sv2::U256<'static>, - min_ntime: u32, - nbits: u32, -} - -impl StagedPhash { - /// Converts a Staged PrevHash into a SetNewPrevHash message - pub fn into_set_p_hash( - &self, - channel_id: u32, - new_job_id: Option, - ) -> SetNewPrevHash<'static> { - SetNewPrevHash { - channel_id, - job_id: new_job_id.unwrap_or(self.job_id), - prev_hash: self.prev_hash.clone(), - min_ntime: self.min_ntime, - nbits: self.nbits, - } - } -} - -impl Share { - /// Get share sequence number - pub fn get_sequence_number(&self) -> u32 { - match self { - Share::Extended(s) => s.sequence_number, - Share::Standard(s) => s.0.sequence_number, - } - } - - /// Get share channel id - pub fn get_channel_id(&self) -> u32 { - match self { - Share::Extended(s) => s.channel_id, - Share::Standard(s) => s.0.channel_id, - } - } - - /// Get share timestamp - pub fn get_n_time(&self) -> u32 { - match self { - Share::Extended(s) => s.ntime, - Share::Standard(s) => s.0.ntime, - } - } - - /// Get share nonce - pub fn get_nonce(&self) -> u32 { - match self { - Share::Extended(s) => s.nonce, - Share::Standard(s) => s.0.nonce, - } - } - - /// Get share job id - pub fn get_job_id(&self) -> u32 { - match self { - Share::Extended(s) => s.job_id, - Share::Standard(s) => s.0.job_id, - } - } - - /// Get share version - pub fn get_version(&self) -> u32 { - match self { - Share::Extended(s) => s.version, - Share::Standard(s) => s.0.version, - } - } -} - -#[derive(Debug)] -/// Basic logic shared between all the channel factories -struct ChannelFactory { - ids: Arc>, - extended_channels: - HashMap, BuildNoHashHasher>, - extranonces: ExtendedExtranonce, - share_per_min: f32, - // (NewExtendedMiningJob,group ids that already received the future job) - future_jobs: Vec<(NewExtendedMiningJob<'static>, Vec)>, - // (SetNewPrevHash,group ids that already received the set prev_hash) - last_prev_hash: Option<(StagedPhash, Vec)>, - last_prev_hash_: Option, - // (NewExtendedMiningJob,group ids that already received the job) - last_valid_job: Option<(NewExtendedMiningJob<'static>, Vec)>, - kind: ExtendedChannelKind, - job_ids: Id, - channel_to_group_id: HashMap>, - future_templates: HashMap, BuildNoHashHasher>, -} - -impl ChannelFactory { - /// Called when a `OpenExtendedMiningChannel` message is received. - /// Here we save the downstream's target (based on hashrate) and the - /// channel's extranonce details before returning the relevant SV2 mining messages - /// to be sent downstream. For the mining messages, we will first return an - /// `OpenExtendedMiningChannelSuccess` if the channel is successfully opened. Then we add - /// the `NewExtendedMiningJob` and `SetNewPrevHash` messages if the relevant data is - /// available. If the channel opening fails, we return `OpenExtendedMiningChannelError`. - pub fn new_extended_channel( - &mut self, - request_id: u32, - hash_rate: f32, - min_extranonce_size: u16, - ) -> Result>, Error> { - let extended_channels_group = 0; - let max_extranonce_size = self.extranonces.get_range2_len() as u16; - if min_extranonce_size <= max_extranonce_size { - // SECURITY is very unlikely to finish the ids btw this unwrap could be used by an - // attacker that want to disrupt the service maybe we should have a method - // to reuse ids that are no longer connected? - let channel_id = self - .ids - .safe_lock(|ids| ids.new_channel_id(extended_channels_group)) - .unwrap(); - self.channel_to_group_id.insert(channel_id, 0); - let target = match crate::utils::hash_rate_to_target( - hash_rate.into(), - self.share_per_min.into(), - ) { - Ok(target) => target, - Err(e) => { - error!( - "Impossible to get target: {:?}. Request id: {:?}", - e, request_id - ); - return Err(e); - } - }; - - let extranonce_prefix = self - .extranonces - .next_prefix_extended(max_extranonce_size as usize) - .unwrap() - .into_b032(); - let success = OpenExtendedMiningChannelSuccess { - request_id, - channel_id, - target: target.to_le_bytes().into(), - extranonce_size: max_extranonce_size, - extranonce_prefix, - }; - self.extended_channels.insert(channel_id, success.clone()); - let mut result = vec![Mining::OpenExtendedMiningChannelSuccess(success)]; - if let Some((job, _)) = &self.last_valid_job { - let mut job = job.clone(); - job.set_future(); - let j_id = job.job_id; - result.push(Mining::NewExtendedMiningJob(job)); - if let Some((new_prev_hash, _)) = &self.last_prev_hash { - let mut new_prev_hash = new_prev_hash.into_set_p_hash(channel_id, None); - new_prev_hash.job_id = j_id; - result.push(Mining::SetNewPrevHash(new_prev_hash.clone())) - }; - } else if let Some((new_prev_hash, _)) = &self.last_prev_hash { - let new_prev_hash = new_prev_hash.into_set_p_hash(channel_id, None); - result.push(Mining::SetNewPrevHash(new_prev_hash.clone())) - }; - for (job, _) in &self.future_jobs { - result.push(Mining::NewExtendedMiningJob(job.clone())) - } - Ok(result) - } else { - Ok(vec![Mining::OpenMiningChannelError( - OpenMiningChannelError::unsupported_extranonce_size(request_id), - )]) - } - } - - /// Called when we want to replicate a channel already opened by another actor. - /// It is used only in the jd client from the template provider module to mock a pool. - /// Anything else should open channel with the new_extended_channel function - pub fn replicate_upstream_extended_channel_only_jd( - &mut self, - target: binary_sv2::U256<'static>, - extranonce: mining_sv2::Extranonce, - channel_id: u32, - extranonce_size: u16, - ) -> Option<()> { - self.channel_to_group_id.insert(channel_id, 0); - let extranonce_prefix = extranonce.into(); - let success = OpenExtendedMiningChannelSuccess { - request_id: 0, - channel_id, - target, - extranonce_size, - extranonce_prefix, - }; - self.extended_channels.insert(channel_id, success.clone()); - Some(()) - } - - /// Called when a new prev hash is received. If the respective job is available in the future - /// job queue, we move the future job into the valid job slot and store the prev hash as the - /// current prev hash to be referenced. - fn on_new_prev_hash(&mut self, m: StagedPhash) -> Result<(), Error> { - while let Some(mut job) = self.future_jobs.pop() { - if job.0.job_id == m.job_id { - let now = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap() - .as_secs() as u32; - job.0.set_no_future(now); - self.last_valid_job = Some(job); - break; - } - self.last_valid_job = None; - } - self.future_jobs = vec![]; - self.last_prev_hash_ = Some(crate::utils::u256_to_block_hash(m.prev_hash.clone())); - self.last_prev_hash = Some((m, vec![])); - Ok(()) - } - - /// Called when a `NewExtendedMiningJob` arrives. If the job is future, we add it to the future - /// queue. If the job is not future, we pair it with a the most recent prev hash - fn on_new_extended_mining_job( - &mut self, - m: NewExtendedMiningJob<'static>, - ) -> Result, BuildNoHashHasher>, Error> { - match (m.is_future(), &self.last_prev_hash) { - (true, _) => { - let mut result = HashMap::with_hasher(BuildNoHashHasher::default()); - self.prepare_jobs_for_downstream_on_new_extended(&mut result, &m)?; - self.future_jobs.push((m, vec![])); - Ok(result) - } - (false, Some(_)) => { - let mut result = HashMap::with_hasher(BuildNoHashHasher::default()); - self.prepare_jobs_for_downstream_on_new_extended(&mut result, &m)?; - // If job is not future it must always be paired with the last received prev hash - self.last_valid_job = Some((m, vec![])); - if let Some((_p_hash, _)) = &self.last_prev_hash { - Ok(result) - } else { - Err(Error::JobIsNotFutureButPrevHashNotPresent) - } - } - // This should not happen when a non future job is received we always need to have a - // prev hash - (false, None) => Err(Error::JobIsNotFutureButPrevHashNotPresent), - } - } - - // When a new extended job is received we use this function to prepare the jobs to be sent - // downstream (standard for hom and this job for non hom) - fn prepare_jobs_for_downstream_on_new_extended( - &mut self, - result: &mut HashMap>, - m: &NewExtendedMiningJob<'static>, - ) -> Result<(), Error> { - for id in self.extended_channels.keys() { - let mut extended = m.clone(); - extended.channel_id = *id; - let extended_job = Mining::NewExtendedMiningJob(extended); - result.insert(*id, extended_job); - } - Ok(()) - } - - // If there is job creator, bitcoin_target is retrieved from there. If not, it is set to 0. - // If there is a job creator we pass the correct template id. If not, we pass `None` - // allow comparison chain because clippy wants to make job management assertion into a match - // clause - #[allow(clippy::comparison_chain)] - #[allow(clippy::too_many_arguments)] - fn check_target>( - &mut self, - mut m: Share, - bitcoin_target: Target, - template_id: Option, - up_id: u32, - merkle_path: Vec, - coinbase_tx_prefix: &[u8], - coinbase_tx_suffix: &[u8], - prev_blockhash: hash_types::BlockHash, - bits: u32, - ) -> Result { - debug!("Checking target for share {:?}", m); - let upstream_target = match &self.kind { - ExtendedChannelKind::Pool => Target::ZERO, - ExtendedChannelKind::Proxy { - upstream_target, .. - } - | ExtendedChannelKind::ProxyJd { - upstream_target, .. - } => upstream_target.clone(), - }; - - let (downstream_target, extranonce) = self - .get_channel_specific_mining_info(&m) - .ok_or(Error::ShareDoNotMatchAnyChannel)?; - let extranonce_1_len = self.extranonces.get_range0_len(); - let extranonce_2 = extranonce[extranonce_1_len..].to_vec(); - match &mut m { - Share::Extended(extended_share) => { - extended_share.extranonce = extranonce_2.try_into()?; - } - Share::Standard(_) => (), - }; - trace!( - "On checking target coinbase prefix is: {:?}", - coinbase_tx_prefix - ); - trace!( - "On checking target coinbase suffix is: {:?}", - coinbase_tx_suffix - ); - // Safe unwrap a sha256 can always be converted into [u8;32] - let merkle_root: [u8; 32] = crate::utils::merkle_root_from_path( - coinbase_tx_prefix, - coinbase_tx_suffix, - &extranonce[..], - &merkle_path[..], - ) - .ok_or(Error::InvalidCoinbase)? - .try_into() - .unwrap(); - let version = match &m { - Share::Extended(share) => share.version as i32, - Share::Standard(share) => share.0.version as i32, - }; - - let header = Header { - version: Version::from_consensus(version), - prev_blockhash, - merkle_root: (*Hash::from_bytes_ref(&merkle_root)).into(), - time: m.get_n_time(), - bits: CompactTarget::from_consensus(bits), - nonce: m.get_nonce(), - }; - - trace!("On checking target header is: {:?}", header); - let hash_ = header.block_hash(); - let hash: [u8; 32] = *hash_.to_raw_hash().as_ref(); - - if tracing::level_enabled!(tracing::Level::DEBUG) - || tracing::level_enabled!(tracing::Level::TRACE) - { - let bitcoin_target_log = bitcoin_target.to_be_bytes(); - debug!("Bitcoin target : {:?}", bitcoin_target_log.as_hex()); - let upstream_target = upstream_target.to_be_bytes(); - debug!("Upstream target: {:?}", upstream_target.to_vec().as_hex()); - let mut hash = hash; - hash.reverse(); - debug!("Hash : {:?}", hash.to_vec().as_hex()); - } - let hash = Target::from_be_bytes(hash); - - if hash <= bitcoin_target { - let mut print_hash: [u8; 32] = *hash_.to_raw_hash().as_ref(); - print_hash.reverse(); - - info!( - "Share hash meet bitcoin target: {:?}", - print_hash.to_vec().as_hex() - ); - - let coinbase = [coinbase_tx_prefix, &extranonce[..], coinbase_tx_suffix] - .concat() - .to_vec(); - match self.kind { - ExtendedChannelKind::Proxy { .. } | ExtendedChannelKind::ProxyJd { .. } => { - let upstream_extranonce_space = self.extranonces.get_range0_len(); - let extranonce_ = extranonce[upstream_extranonce_space..].to_vec(); - let mut res = OnNewShare::ShareMeetBitcoinTarget(( - m, - template_id, - coinbase, - extranonce.to_vec(), - )); - res.into_extended(extranonce_, up_id); - Ok(res) - } - ExtendedChannelKind::Pool => Ok(OnNewShare::ShareMeetBitcoinTarget(( - m, - template_id, - coinbase, - extranonce.to_vec(), - ))), - } - } else if hash <= upstream_target { - match self.kind { - ExtendedChannelKind::Proxy { .. } | ExtendedChannelKind::ProxyJd { .. } => { - let upstream_extranonce_space = self.extranonces.get_range0_len(); - let extranonce = extranonce[upstream_extranonce_space..].to_vec(); - let mut res = OnNewShare::SendSubmitShareUpstream((m, template_id)); - res.into_extended(extranonce, up_id); - Ok(res) - } - ExtendedChannelKind::Pool => { - Ok(OnNewShare::SendSubmitShareUpstream((m, template_id))) - } - } - } else if hash <= downstream_target { - Ok(OnNewShare::ShareMeetDownstreamTarget) - } else { - error!("Share does not meet any target: {:?}", m); - let error = SubmitSharesError { - channel_id: m.get_channel_id(), - sequence_number: m.get_sequence_number(), - // Infallible unwrap we already know the len of the error code (is a - // static string) - error_code: SubmitSharesError::difficulty_too_low_error_code() - .to_string() - .try_into() - .unwrap(), - }; - Ok(OnNewShare::SendErrorDownstream(error)) - } - } - - /// Returns the downstream target and extranonce for the channel - fn get_channel_specific_mining_info(&self, m: &Share) -> Option<(Target, Vec)> { - match m { - Share::Extended(share) => { - let channel = self.extended_channels.get(&m.get_channel_id())?; - let extranonce_prefix = channel.extranonce_prefix.to_vec(); - let dowstream_target = - Target::from_le_bytes(channel.target.inner_as_ref().try_into().unwrap()); - let extranonce = [&extranonce_prefix[..], &share.extranonce.to_vec()[..]] - .concat() - .to_vec(); - if extranonce.len() != self.extranonces.get_len() { - error!( - "Extranonce is not of the right len expected {} actual {}", - self.extranonces.get_len(), - extranonce.len() - ); - } - Some((dowstream_target, extranonce)) - } - Share::Standard((_share, _group_id)) => { - unimplemented!() - } - } - } - /// Updates the downstream target for the given channel_id - fn update_target_for_channel(&mut self, channel_id: u32, new_target: Target) -> Option { - let channel = self.extended_channels.get_mut(&channel_id)?; - channel.target = new_target.to_le_bytes().into(); - Some(true) - } -} - -/// Used by a pool to in order to manage all downstream channel. It adds job creation capabilities -/// to ChannelFactory. -#[derive(Debug)] -pub struct PoolChannelFactory { - inner: ChannelFactory, - job_creator: JobsCreators, - pool_coinbase_outputs: Vec, - // extended_channel_id -> SetCustomMiningJob - negotiated_jobs: HashMap, BuildNoHashHasher>, -} - -impl PoolChannelFactory { - /// constructor - pub fn new( - ids: Arc>, - extranonces: ExtendedExtranonce, - job_creator: JobsCreators, - share_per_min: f32, - kind: ExtendedChannelKind, - pool_coinbase_outputs: Vec, - ) -> Self { - let inner = ChannelFactory { - ids, - extended_channels: HashMap::with_hasher(BuildNoHashHasher::default()), - extranonces, - share_per_min, - future_jobs: Vec::new(), - last_prev_hash: None, - last_prev_hash_: None, - last_valid_job: None, - kind, - job_ids: Id::new(), - channel_to_group_id: HashMap::with_hasher(BuildNoHashHasher::default()), - future_templates: HashMap::with_hasher(BuildNoHashHasher::default()), - }; - - Self { - inner, - job_creator, - pool_coinbase_outputs, - negotiated_jobs: HashMap::with_hasher(BuildNoHashHasher::default()), - } - } - - /// Calls [`ChannelFactory::new_extended_channel`] - pub fn new_extended_channel( - &mut self, - request_id: u32, - hash_rate: f32, - min_extranonce_size: u16, - ) -> Result>, Error> { - self.inner - .new_extended_channel(request_id, hash_rate, min_extranonce_size) - } - - /// Called when we want to replicate a channel already opened by another actor. - /// is used only in the jd client from the template provider module to mock a pool. - /// Anything else should open channel with the new_extended_channel function - pub fn replicate_upstream_extended_channel_only_jd( - &mut self, - target: binary_sv2::U256<'static>, - extranonce: mining_sv2::Extranonce, - channel_id: u32, - extranonce_size: u16, - ) -> Option<()> { - self.inner.replicate_upstream_extended_channel_only_jd( - target, - extranonce, - channel_id, - extranonce_size, - ) - } - - /// Called only when a new prev hash is received by a Template Provider. It matches the - /// message with a `job_id` and calls [`ChannelFactory::on_new_prev_hash`] - /// it return the job_id - pub fn on_new_prev_hash_from_tp( - &mut self, - m: &SetNewPrevHashFromTp<'static>, - ) -> Result { - let job_id = self.job_creator.on_new_prev_hash(m).unwrap_or(0); - let new_prev_hash = StagedPhash { - job_id, - prev_hash: m.prev_hash.clone(), - min_ntime: m.header_timestamp, - nbits: m.n_bits, - }; - self.inner.on_new_prev_hash(new_prev_hash)?; - Ok(job_id) - } - - /// Called only when a new template is received by a Template Provider - pub fn on_new_template( - &mut self, - m: &mut NewTemplate<'static>, - ) -> Result, BuildNoHashHasher>, Error> { - let new_job = - self.job_creator - .on_new_template(m, true, self.pool_coinbase_outputs.clone())?; - self.inner.on_new_extended_mining_job(new_job) - } - - /// Called when a `SubmitSharesStandard` message is received from the downstream. We check the - /// shares against the channel's respective target and return `OnNewShare` to let us know if - /// and where the shares should be relayed - pub fn on_submit_shares_standard( - &mut self, - m: SubmitSharesStandard, - ) -> Result { - match self.inner.channel_to_group_id.get(&m.channel_id) { - Some(g_id) => { - let referenced_job = self - .inner - .last_valid_job - .clone() - .ok_or(Error::ShareDoNotMatchAnyJob)? - .0; - let merkle_path = referenced_job.merkle_path.to_vec(); - let template_id = self - .job_creator - .get_template_id_from_job(referenced_job.job_id) - .ok_or(Error::NoTemplateForId)?; - let target = self.job_creator.last_target(); - let prev_blockhash = self - .inner - .last_prev_hash_ - .ok_or(Error::ShareDoNotMatchAnyJob)?; - let bits = self - .inner - .last_prev_hash - .as_ref() - .ok_or(Error::ShareDoNotMatchAnyJob)? - .0 - .nbits; - self.inner.check_target( - Share::Standard((m, *g_id)), - target, - Some(template_id), - 0, - merkle_path, - referenced_job.coinbase_tx_prefix.as_ref(), - referenced_job.coinbase_tx_suffix.as_ref(), - prev_blockhash, - bits, - ) - } - None => { - let err = SubmitSharesError { - channel_id: m.channel_id, - sequence_number: m.sequence_number, - error_code: SubmitSharesError::invalid_channel_error_code() - .to_string() - .try_into() - .unwrap(), - }; - Ok(OnNewShare::SendErrorDownstream(err)) - } - } - } - - /// Called when a `SubmitSharesExtended` message is received from the downstream. We check the - /// shares against the channel's respective target and return `OnNewShare` to let us know if - /// and where the shares should be relayed - pub fn on_submit_shares_extended( - &mut self, - m: SubmitSharesExtended, - ) -> Result { - let target = self.job_creator.last_target(); - // When downstream set a custom mining job we add the job to the negotiated job - // hashmap, with the extended channel id as a key. Whenever the pool receive a share must - // first check if the channel have a negotiated job if so we can not retrieve the template - // via the job creator but we create a new one from the set custom job. - if self.negotiated_jobs.contains_key(&m.channel_id) { - let referenced_job = self.negotiated_jobs.get(&m.channel_id).unwrap(); - let merkle_path = referenced_job.merkle_path.to_vec(); - let extended_job = job_creator::extended_job_from_custom_job( - referenced_job, - self.inner.extranonces.get_len() as u8, - ) - .unwrap(); - let prev_blockhash = crate::utils::u256_to_block_hash(referenced_job.prev_hash.clone()); - let bits = referenced_job.nbits; - self.inner.check_target( - Share::Extended(m.into_static()), - target, - None, - 0, - merkle_path, - extended_job.coinbase_tx_prefix.as_ref(), - extended_job.coinbase_tx_suffix.as_ref(), - prev_blockhash, - bits, - ) - } else { - let referenced_job = self - .inner - .last_valid_job - .clone() - .ok_or(Error::ShareDoNotMatchAnyJob)? - .0; - let merkle_path = referenced_job.merkle_path.to_vec(); - let template_id = self - .job_creator - .get_template_id_from_job(referenced_job.job_id) - .ok_or(Error::NoTemplateForId)?; - let prev_blockhash = self - .inner - .last_prev_hash_ - .ok_or(Error::ShareDoNotMatchAnyJob)?; - let bits = self - .inner - .last_prev_hash - .as_ref() - .ok_or(Error::ShareDoNotMatchAnyJob)? - .0 - .nbits; - self.inner.check_target( - Share::Extended(m.into_static()), - target, - Some(template_id), - 0, - merkle_path, - referenced_job.coinbase_tx_prefix.as_ref(), - referenced_job.coinbase_tx_suffix.as_ref(), - prev_blockhash, - bits, - ) - } - } - - /// Utility function to return a new group id - pub fn new_group_id(&mut self) -> u32 { - let new_id = self.inner.ids.safe_lock(|ids| ids.new_group_id()).unwrap(); - new_id - } - - /// Utility function to return a new standard channel id - pub fn new_standard_id_for_hom(&mut self) -> u32 { - let hom_group_id = 0; - let new_id = self - .inner - .ids - .safe_lock(|ids| ids.new_channel_id(hom_group_id)) - .unwrap(); - new_id - } - - /// Returns the full extranonce, extranonce1 (static for channel) + extranonce2 (miner nonce - /// space) - pub fn extranonce_from_downstream_extranonce( - &self, - ext: mining_sv2::Extranonce, - ) -> Option { - self.inner - .extranonces - .extranonce_from_downstream_extranonce(ext) - .ok() - } - - /// Called when a new custom mining job arrives - pub fn on_new_set_custom_mining_job( - &mut self, - set_custom_mining_job: SetCustomMiningJob<'static>, - ) -> SetCustomMiningJobSuccess { - if self.check_set_custom_mining_job(&set_custom_mining_job) { - self.negotiated_jobs.insert( - set_custom_mining_job.channel_id, - set_custom_mining_job.clone(), - ); - SetCustomMiningJobSuccess { - channel_id: set_custom_mining_job.channel_id, - request_id: set_custom_mining_job.request_id, - job_id: self.inner.job_ids.next(), - } - } else { - todo!() - } - } - - fn check_set_custom_mining_job( - &self, - _set_custom_mining_job: &SetCustomMiningJob<'static>, - ) -> bool { - true - } - - /// Get extended channel ids - pub fn get_extended_channels_ids(&self) -> Vec { - self.inner.extended_channels.keys().copied().collect() - } - - pub fn get_shares_per_minute(&self) -> f32 { - self.inner.share_per_min - } - - /// Update coinbase outputs - pub fn update_pool_outputs(&mut self, outs: Vec) { - self.pool_coinbase_outputs = outs; - } - - /// Calls [`ChannelFactory::update_target_for_channel`] - /// Set a particular downstream channel target. - pub fn update_target_for_channel( - &mut self, - channel_id: u32, - new_target: Target, - ) -> Option { - self.inner.update_target_for_channel(channel_id, new_target) - } - - /// Set the target for this channel. This is the upstream target. - pub fn set_target(&mut self, new_target: &mut Target) { - self.inner.kind.set_target(new_target); - } -} - -/// Used by proxies that want to open extended channels with upstream. If the proxy has job -/// declaration capabilities, we set the job creator and the coinbase outs. -#[derive(Debug)] -pub struct ProxyExtendedChannelFactory { - inner: ChannelFactory, - job_creator: Option, - pool_coinbase_outputs: Option>, - // Id assigned to the extended channel by upstream - extended_channel_id: u32, -} - -impl ProxyExtendedChannelFactory { - /// Constructor - #[allow(clippy::too_many_arguments)] - pub fn new( - ids: Arc>, - extranonces: ExtendedExtranonce, - job_creator: Option, - share_per_min: f32, - kind: ExtendedChannelKind, - pool_coinbase_outputs: Option>, - extended_channel_id: u32, - ) -> Self { - match &kind { - ExtendedChannelKind::Proxy { .. } => { - if job_creator.is_some() { - panic!("Channel factory of kind Proxy can not be initialized with a JobCreators"); - }; - }, - ExtendedChannelKind::ProxyJd { .. } => { - if job_creator.is_none() { - panic!("Channel factory of kind ProxyJd must be initialized with a JobCreators"); - }; - } - ExtendedChannelKind::Pool => panic!("Try to construct an ProxyExtendedChannelFactory with pool kind, kind must be Proxy or ProxyJd"), - }; - let inner = ChannelFactory { - ids, - extended_channels: HashMap::with_hasher(BuildNoHashHasher::default()), - extranonces, - share_per_min, - future_jobs: Vec::new(), - last_prev_hash: None, - last_prev_hash_: None, - last_valid_job: None, - kind, - job_ids: Id::new(), - channel_to_group_id: HashMap::with_hasher(BuildNoHashHasher::default()), - future_templates: HashMap::with_hasher(BuildNoHashHasher::default()), - }; - ProxyExtendedChannelFactory { - inner, - job_creator, - pool_coinbase_outputs, - extended_channel_id, - } - } - - /// Calls [`ChannelFactory::new_extended_channel`] - pub fn new_extended_channel( - &mut self, - request_id: u32, - hash_rate: f32, - min_extranonce_size: u16, - ) -> Result>, Error> { - self.inner - .new_extended_channel(request_id, hash_rate, min_extranonce_size) - } - - /// Called only when a new prev hash is received by a Template Provider when job declaration is - /// used. It matches the message with a `job_id`, creates a new custom job, and calls - /// [`ChannelFactory::on_new_prev_hash`] - pub fn on_new_prev_hash_from_tp( - &mut self, - m: &SetNewPrevHashFromTp<'static>, - ) -> Result, Error> { - if let Some(job_creator) = self.job_creator.as_mut() { - let job_id = job_creator.on_new_prev_hash(m).unwrap_or(0); - let new_prev_hash = StagedPhash { - job_id, - prev_hash: m.prev_hash.clone(), - min_ntime: m.header_timestamp, - nbits: m.n_bits, - }; - let mut custom_job = None; - if let Some(template) = self.inner.future_templates.get(&job_id) { - custom_job = Some(( - PartialSetCustomMiningJob { - version: template.version, - prev_hash: new_prev_hash.prev_hash.clone(), - min_ntime: new_prev_hash.min_ntime, - nbits: new_prev_hash.nbits, - coinbase_tx_version: template.coinbase_tx_version, - coinbase_prefix: template.coinbase_prefix.clone(), - coinbase_tx_input_n_sequence: template.coinbase_tx_input_sequence, - coinbase_tx_value_remaining: template.coinbase_tx_value_remaining, - coinbase_tx_outputs: template.coinbase_tx_outputs.clone(), - coinbase_tx_locktime: template.coinbase_tx_locktime, - merkle_path: template.merkle_path.clone(), - future_job: template.future_template, - }, - job_id, - )); - } - self.inner.future_templates = HashMap::with_hasher(BuildNoHashHasher::default()); - self.inner.on_new_prev_hash(new_prev_hash)?; - Ok(custom_job) - } else { - panic!("A channel factory without job creator do not have declaration capabilities") - } - } - - /// Called only when a new template is received by a Template Provider when job declaration is - /// used. It creates a new custom job and calls - /// [`ChannelFactory::on_new_extended_mining_job`] - #[allow(clippy::type_complexity)] - pub fn on_new_template( - &mut self, - m: &mut NewTemplate<'static>, - ) -> Result< - ( - // downstream job_id -> downstream message (newextjob or newjob) - HashMap, BuildNoHashHasher>, - // PartialSetCustomMiningJob to send to the pool - Option, - // job_id registered in the channel, the one that SetNewPrevHash refer to (upstsream - // job id) - u32, - ), - Error, - > { - if let (Some(job_creator), Some(pool_coinbase_outputs)) = ( - self.job_creator.as_mut(), - self.pool_coinbase_outputs.as_mut(), - ) { - let new_job = job_creator.on_new_template(m, true, pool_coinbase_outputs.clone())?; - let id = new_job.job_id; - if !new_job.is_future() && self.inner.last_prev_hash.is_some() { - let prev_hash = self.last_prev_hash().unwrap(); - let min_ntime = self.last_min_ntime().unwrap(); - let nbits = self.last_nbits().unwrap(); - let custom_mining_job = PartialSetCustomMiningJob { - version: m.version, - prev_hash, - min_ntime, - nbits, - coinbase_tx_version: m.coinbase_tx_version, - coinbase_prefix: m.coinbase_prefix.clone(), - coinbase_tx_input_n_sequence: m.coinbase_tx_input_sequence, - coinbase_tx_value_remaining: m.coinbase_tx_value_remaining, - coinbase_tx_outputs: m.coinbase_tx_outputs.clone(), - coinbase_tx_locktime: m.coinbase_tx_locktime, - merkle_path: m.merkle_path.clone(), - future_job: m.future_template, - }; - return Ok(( - self.inner.on_new_extended_mining_job(new_job)?, - Some(custom_mining_job), - id, - )); - } else if new_job.is_future() { - self.inner - .future_templates - .insert(new_job.job_id, m.clone()); - } - Ok((self.inner.on_new_extended_mining_job(new_job)?, None, id)) - } else { - panic!("Either channel factory has no job creator or pool_coinbase_outputs are not yet set") - } - } - - /// Called when a `SubmitSharesStandard` message is received from the downstream. We check the - /// shares against the channel's respective target and return `OnNewShare` to let us know if - /// and where the shares should be relayed - pub fn on_submit_shares_extended( - &mut self, - m: SubmitSharesExtended<'static>, - ) -> Result { - let merkle_path = self - .inner - .last_valid_job - .as_ref() - .ok_or(Error::ShareDoNotMatchAnyJob)? - .0 - .merkle_path - .to_vec(); - - let referenced_job = self - .inner - .last_valid_job - .clone() - .ok_or(Error::ShareDoNotMatchAnyJob)? - .0; - - if referenced_job.job_id != m.job_id { - let error = SubmitSharesError { - channel_id: m.channel_id, - sequence_number: m.sequence_number, - // Infallible unwrap we already know the len of the error code (is a - // static string) - error_code: SubmitSharesError::invalid_job_id_error_code() - .to_string() - .try_into() - .unwrap(), - }; - return Ok(OnNewShare::SendErrorDownstream(error)); - } - - if let Some(job_creator) = self.job_creator.as_mut() { - let template_id = job_creator - .get_template_id_from_job(referenced_job.job_id) - .ok_or(Error::NoTemplateForId)?; - let bitcoin_target = job_creator.last_target(); - let prev_blockhash = self - .inner - .last_prev_hash_ - .ok_or(Error::ShareDoNotMatchAnyJob)?; - let bits = self - .inner - .last_prev_hash - .as_ref() - .ok_or(Error::ShareDoNotMatchAnyJob)? - .0 - .nbits; - self.inner.check_target( - Share::Extended(m), - bitcoin_target, - Some(template_id), - self.extended_channel_id, - merkle_path, - referenced_job.coinbase_tx_prefix.as_ref(), - referenced_job.coinbase_tx_suffix.as_ref(), - prev_blockhash, - bits, - ) - } else { - let bitcoin_target = Target::ZERO; - // if there is not job_creator is not proxy duty to check if target is below or above - // bitcoin target so we set bitcoin_target = 0. - let prev_blockhash = self - .inner - .last_prev_hash_ - .ok_or(Error::ShareDoNotMatchAnyJob)?; - let bits = self - .inner - .last_prev_hash - .as_ref() - .ok_or(Error::ShareDoNotMatchAnyJob)? - .0 - .nbits; - self.inner.check_target( - Share::Extended(m), - bitcoin_target, - None, - self.extended_channel_id, - merkle_path, - referenced_job.coinbase_tx_prefix.as_ref(), - referenced_job.coinbase_tx_suffix.as_ref(), - prev_blockhash, - bits, - ) - } - } - - /// Called when a `SubmitSharesStandard` message is received from the Downstream. We check the - /// shares against the channel's respective target and return `OnNewShare` to let us know if - /// and where the shares should be relayed - pub fn on_submit_shares_standard( - &mut self, - m: SubmitSharesStandard, - ) -> Result { - let merkle_path = self - .inner - .last_valid_job - .as_ref() - .ok_or(Error::ShareDoNotMatchAnyJob)? - .0 - .merkle_path - .to_vec(); - let referenced_job = self - .inner - .last_valid_job - .clone() - .ok_or(Error::ShareDoNotMatchAnyJob)? - .0; - match self.inner.channel_to_group_id.get(&m.channel_id) { - Some(g_id) => { - if let Some(job_creator) = self.job_creator.as_mut() { - let template_id = job_creator - .get_template_id_from_job( - self.inner.last_valid_job.as_ref().unwrap().0.job_id, - ) - .ok_or(Error::NoTemplateForId)?; - let bitcoin_target = job_creator.last_target(); - let prev_blockhash = self - .inner - .last_prev_hash_ - .ok_or(Error::ShareDoNotMatchAnyJob)?; - let bits = self - .inner - .last_prev_hash - .as_ref() - .ok_or(Error::ShareDoNotMatchAnyJob)? - .0 - .nbits; - self.inner.check_target( - Share::Standard((m, *g_id)), - bitcoin_target, - Some(template_id), - self.extended_channel_id, - merkle_path, - referenced_job.coinbase_tx_prefix.as_ref(), - referenced_job.coinbase_tx_suffix.as_ref(), - prev_blockhash, - bits, - ) - } else { - let bitcoin_target = Target::ZERO; - let prev_blockhash = self - .inner - .last_prev_hash_ - .ok_or(Error::ShareDoNotMatchAnyJob)?; - let bits = self - .inner - .last_prev_hash - .as_ref() - .ok_or(Error::ShareDoNotMatchAnyJob)? - .0 - .nbits; - // if there is not job_creator is not proxy duty to check if target is below or - // above bitcoin target so we set bitcoin_target = 0. - self.inner.check_target( - Share::Standard((m, *g_id)), - bitcoin_target, - None, - self.extended_channel_id, - merkle_path, - referenced_job.coinbase_tx_prefix.as_ref(), - referenced_job.coinbase_tx_suffix.as_ref(), - prev_blockhash, - bits, - ) - } - } - None => { - let err = SubmitSharesError { - channel_id: m.channel_id, - sequence_number: m.sequence_number, - error_code: SubmitSharesError::invalid_channel_error_code() - .to_string() - .try_into() - .unwrap(), - }; - Ok(OnNewShare::SendErrorDownstream(err)) - } - } - } - - /// Calls [`ChannelFactory::on_new_prev_hash`] - pub fn on_new_prev_hash(&mut self, m: SetNewPrevHash<'static>) -> Result<(), Error> { - self.inner.on_new_prev_hash(StagedPhash { - job_id: m.job_id, - prev_hash: m.prev_hash.clone().into_static(), - min_ntime: m.min_ntime, - nbits: m.nbits, - }) - } - - /// Calls [`ChannelFactory::on_new_extended_mining_job`] - pub fn on_new_extended_mining_job( - &mut self, - m: NewExtendedMiningJob<'static>, - ) -> Result, BuildNoHashHasher>, Error> { - self.inner.on_new_extended_mining_job(m) - } - - /// Set new target - pub fn set_target(&mut self, new_target: &mut Target) { - self.inner.kind.set_target(new_target); - } - - /// Get last valid job version - pub fn last_valid_job_version(&self) -> Option { - self.inner.last_valid_job.as_ref().map(|j| j.0.version) - } - - /// Returns the full extranonce, extranonce1 (static for channel) + extranonce2 (miner nonce - /// space) - pub fn extranonce_from_downstream_extranonce( - &self, - ext: mining_sv2::Extranonce, - ) -> Option { - self.inner - .extranonces - .extranonce_from_downstream_extranonce(ext) - .ok() - } - - /// Returns the most recent prev hash - pub fn last_prev_hash(&self) -> Option> { - self.inner - .last_prev_hash - .as_ref() - .map(|f| f.0.prev_hash.clone()) - } - - /// Get last min ntime - pub fn last_min_ntime(&self) -> Option { - self.inner.last_prev_hash.as_ref().map(|f| f.0.min_ntime) - } - - /// Get last nbits - pub fn last_nbits(&self) -> Option { - self.inner.last_prev_hash.as_ref().map(|f| f.0.nbits) - } - - /// Get extranonce_size - pub fn extranonce_size(&self) -> usize { - self.inner.extranonces.get_len() - } - - /// Get extranonce_2 size - pub fn channel_extranonce2_size(&self) -> usize { - self.inner.extranonces.get_len() - self.inner.extranonces.get_range0_len() - } - - // Only used when the proxy is using Job Declaration - /// Updates pool outputs - pub fn update_pool_outputs(&mut self, outs: Vec) { - self.pool_coinbase_outputs = Some(outs); - } - - /// Get this channel id - pub fn get_this_channel_id(&self) -> u32 { - self.extended_channel_id - } - - /// Returns the extranonce1 len of the upstream. For a proxy, this would - /// be the extranonce_prefix len - pub fn get_upstream_extranonce1_len(&self) -> usize { - self.inner.extranonces.get_range0_len() - } - - /// Calls [`ChannelFactory::update_target_for_channel`] - pub fn update_target_for_channel( - &mut self, - channel_id: u32, - new_target: Target, - ) -> Option { - self.inner.update_target_for_channel(channel_id, new_target) - } -} - -/// Used by proxies for tracking upstream targets. -#[derive(Debug, Clone)] -pub enum ExtendedChannelKind { - Proxy { upstream_target: Target }, - ProxyJd { upstream_target: Target }, - Pool, -} -impl ExtendedChannelKind { - /// Set target - pub fn set_target(&mut self, new_target: &mut Target) { - match self { - ExtendedChannelKind::Proxy { upstream_target } - | ExtendedChannelKind::ProxyJd { upstream_target } => { - std::mem::swap(upstream_target, new_target) - } - ExtendedChannelKind::Pool => warn!("Try to set upstream target for a pool"), - } - } -} diff --git a/roles/stratum-apps/src/job_creator.rs b/roles/stratum-apps/src/job_creator.rs deleted file mode 100644 index 830a4d1136..0000000000 --- a/roles/stratum-apps/src/job_creator.rs +++ /dev/null @@ -1,716 +0,0 @@ -//! # Job Creator -//! -//! This module provides logic to create extended mining jobs given a template from -//! a template provider as well as logic to clean up old templates when new blocks are mined. -use crate::{errors, utils::Id, Error}; -use bitcoin::{ - absolute::LockTime, - blockdata::{ - transaction::{OutPoint, Transaction, TxIn, TxOut, Version}, - witness::Witness, - }, - consensus, - consensus::Decodable, - Amount, Target, -}; -use codec_sv2::binary_sv2::{self, B064K}; -use mining_sv2::NewExtendedMiningJob; -use nohash_hasher::BuildNoHashHasher; -use std::{collections::HashMap, convert::TryInto}; -use template_distribution_sv2::{NewTemplate, SetNewPrevHash}; -use tracing::debug; - -#[derive(Debug)] -pub struct JobsCreators { - lasts_new_template: Vec>, - job_to_template_id: HashMap>, - templte_to_job_id: HashMap>, - ids: Id, - last_target: Target, - last_ntime: Option, - extranonce_len: u8, -} - -/// Transforms the byte array `coinbase_outputs` in a vector of TxOut -/// It assumes the data to be valid data and does not do any kind of check -pub fn tx_outputs_to_costum_scripts(tx_outputs: &[u8]) -> Vec { - let mut txs = vec![]; - let mut cursor = 0; - let mut txouts = &tx_outputs[cursor..]; - while let Ok(out) = TxOut::consensus_decode(&mut txouts) { - let len = match out.script_pubkey.len() { - a @ 0..=252 => 8 + 1 + a, - a @ 253..=10000 => 8 + 3 + a, - _ => break, - }; - cursor += len; - txs.push(out) - } - txs -} - -impl JobsCreators { - /// Constructor - pub fn new(extranonce_len: u8) -> Self { - Self { - lasts_new_template: Vec::new(), - job_to_template_id: HashMap::with_hasher(BuildNoHashHasher::default()), - templte_to_job_id: HashMap::with_hasher(BuildNoHashHasher::default()), - ids: Id::new(), - last_target: Target::ZERO, - last_ntime: None, - extranonce_len, - } - } - - /// Get template id from job - pub fn get_template_id_from_job(&self, job_id: u32) -> Option { - self.job_to_template_id.get(&job_id).map(|x| x - 1) - } - - /// Used to create new jobs when a new template arrives - pub fn on_new_template( - &mut self, - template: &mut NewTemplate, - version_rolling_allowed: bool, - mut pool_coinbase_outputs: Vec, - ) -> Result, Error> { - let server_tx_outputs = template.coinbase_tx_outputs.to_vec(); - let mut outputs = tx_outputs_to_costum_scripts(&server_tx_outputs); - pool_coinbase_outputs.append(&mut outputs); - - // This is to make sure that 0 is never used, so we can use 0 for - // set_new_prev_hashes that do not refer to any future job/template if needed - // Then we will do the inverse (-1) where needed - let template_id = template.template_id + 1; - self.lasts_new_template.push(template.as_static()); - let next_job_id = self.ids.next(); - self.job_to_template_id.insert(next_job_id, template_id); - self.templte_to_job_id.insert(template_id, next_job_id); - new_extended_job( - template, - &mut pool_coinbase_outputs, - next_job_id, - version_rolling_allowed, - self.extranonce_len, - self.last_ntime, - ) - } - - pub(crate) fn reset_new_templates(&mut self, template: Option>) { - match template { - Some(t) => self.lasts_new_template = vec![t], - None => self.lasts_new_template = vec![], - } - } - - /// When we get a new `SetNewPrevHash` we need to clear all the other templates and only - /// keep the one that matches the template_id of the new prev hash. If none match then - /// we clear all the saved templates. - pub fn on_new_prev_hash(&mut self, prev_hash: &SetNewPrevHash<'static>) -> Option { - self.last_target = - Target::from_be_bytes(prev_hash.target.inner_as_ref().try_into().unwrap()); - self.last_ntime = prev_hash.header_timestamp.into(); // set correct ntime - let template: Vec> = self - .lasts_new_template - .clone() - .into_iter() - .filter(|a| a.template_id == prev_hash.template_id) - .collect(); - match template.len() { - 0 => { - self.reset_new_templates(None); - None - } - 1 => { - self.reset_new_templates(Some(template[0].clone())); - - self.templte_to_job_id - .get(&(prev_hash.template_id + 1)) - .copied() - } - // TODO how many templates can we have at max - _ => todo!("{:#?}", template.len()), - } - } - - /// Returns the latest mining target - pub fn last_target(&self) -> Target { - self.last_target.clone() - } -} - -/// Converts custom job into extended job -pub fn extended_job_from_custom_job( - referenced_job: &mining_sv2::SetCustomMiningJob, - extranonce_len: u8, -) -> Result, Error> { - let mut outputs = - tx_outputs_to_costum_scripts(referenced_job.coinbase_tx_outputs.clone().as_ref()); - - let mut template_value = 0; - for output in &outputs { - template_value += output.value.to_sat(); - } - - let mut template = NewTemplate { - template_id: 0, - future_template: false, - version: referenced_job.version, - coinbase_tx_version: referenced_job.coinbase_tx_version, - coinbase_prefix: referenced_job.coinbase_prefix.clone(), - coinbase_tx_input_sequence: referenced_job.coinbase_tx_input_n_sequence, - coinbase_tx_value_remaining: template_value, - coinbase_tx_outputs_count: outputs.len() as u32, - coinbase_tx_outputs: referenced_job.coinbase_tx_outputs.clone(), - coinbase_tx_locktime: referenced_job.coinbase_tx_locktime, - merkle_path: referenced_job.merkle_path.clone(), - }; - new_extended_job( - &mut template, - &mut outputs, - 0, - true, - extranonce_len, - Some(referenced_job.min_ntime), - ) -} - -// Returns an extended job given the provided template from the Template Provider and other -// Pool role related fields. -// -// Pool related arguments: -// -// * `coinbase_outputs`: coinbase output transactions specified by the pool. -// * `job_id`: incremented job identifier specified by the pool. -// * `version_rolling_allowed`: boolean specified by the channel. -// * `extranonce_len`: extranonce length specified by the channel. -fn new_extended_job( - new_template: &mut NewTemplate, - coinbase_outputs: &mut [TxOut], - job_id: u32, - version_rolling_allowed: bool, - extranonce_len: u8, - ntime: Option, -) -> Result, Error> { - coinbase_outputs[0].value = match new_template.coinbase_tx_value_remaining.checked_mul(1) { - //check that value_remaining is updated by TP - Some(result) => Amount::from_sat(result), - None => return Err(Error::ValueRemainingNotUpdated), - }; - let tx_version = new_template - .coinbase_tx_version - .try_into() - .map_err(|_| Error::TxVersionTooBig)?; - - let script_sig_prefix = new_template.coinbase_prefix.to_vec(); - let script_sig_prefix_len = script_sig_prefix.len(); - - let coinbase = coinbase( - script_sig_prefix, - tx_version, - new_template.coinbase_tx_locktime, - new_template.coinbase_tx_input_sequence, - coinbase_outputs, - extranonce_len, - )?; - - let min_ntime = binary_sv2::Sv2Option::new(if new_template.future_template { - None - } else { - ntime - }); - - let new_extended_mining_job: NewExtendedMiningJob<'static> = NewExtendedMiningJob { - channel_id: 0, - job_id, - min_ntime, - version: new_template.version, - version_rolling_allowed, - merkle_path: new_template.merkle_path.clone().into_static(), - coinbase_tx_prefix: coinbase_tx_prefix(&coinbase, script_sig_prefix_len)?, - coinbase_tx_suffix: coinbase_tx_suffix(&coinbase, extranonce_len, script_sig_prefix_len)?, - }; - - debug!( - "New extended mining job created: {:?}", - new_extended_mining_job - ); - Ok(new_extended_mining_job) -} - -// Used to extract the coinbase transaction prefix for extended jobs -// so the extranonce search space can be introduced -fn coinbase_tx_prefix( - coinbase: &Transaction, - script_sig_prefix_len: usize, -) -> Result, Error> { - let encoded = consensus::serialize(coinbase); - // If script_prefix_len is not 0 we are not in a test environment and the coinbase will have the - // 0 witness - let segwit_bytes = match script_sig_prefix_len { - 0 => 0, - _ => 2, - }; - let index = 4 // tx version - + segwit_bytes - + 1 // number of inputs TODO can be also 3 - + 32 // prev OutPoint - + 4 // index - + 1 // bytes in script TODO can be also 3 - + script_sig_prefix_len; // script_sig_prefix - let r = encoded[0..index].to_vec(); - r.try_into().map_err(Error::BinarySv2Error) -} - -// Used to extract the coinbase transaction suffix for extended jobs -// so the extranonce search space can be introduced -fn coinbase_tx_suffix( - coinbase: &Transaction, - extranonce_len: u8, - script_sig_prefix_len: usize, -) -> Result, Error> { - let encoded = consensus::serialize(coinbase); - // If script_sig_prefix_len is not 0 we are not in a test environment and the coinbase have the - // 0 witness - let segwit_bytes = match script_sig_prefix_len { - 0 => 0, - _ => 2, - }; - let r = encoded[4 // tx version - + segwit_bytes - + 1 // number of inputs TODO can be also 3 - + 32 // prev OutPoint - + 4 // index - + 1 // bytes in script TODO can be also 3 - + script_sig_prefix_len // script_sig_prefix - + (extranonce_len as usize)..] - .to_vec(); - r.try_into().map_err(Error::BinarySv2Error) -} - -// try to build a Transaction coinbase -fn coinbase( - script_sig_prefix: Vec, - version: i32, - lock_time: u32, - sequence: u32, - coinbase_outputs: &[TxOut], - extranonce_len: u8, -) -> Result { - // If script_sig_prefix_len is not 0 we are not in a test environment and the coinbase have the - // 0 witness - let witness = match script_sig_prefix.len() { - 0 => Witness::from(vec![] as Vec>), - _ => Witness::from(vec![vec![0; 32]]), - }; - let mut script_sig = script_sig_prefix; - script_sig.extend_from_slice(&vec![0; extranonce_len as usize]); - let tx_in = TxIn { - previous_output: OutPoint::null(), - script_sig: script_sig.into(), - sequence: bitcoin::Sequence(sequence), - witness, - }; - Ok(Transaction { - version: Version::non_standard(version), - lock_time: LockTime::from_consensus(lock_time), - input: vec![tx_in], - output: coinbase_outputs.to_vec(), - }) -} - -/// Helper type to strip a segwit data from the coinbase_tx_prefix and coinbase_tx_suffix -/// to ensure miners are hashing with the correct coinbase -pub fn extended_job_to_non_segwit( - job: NewExtendedMiningJob<'static>, - full_extranonce_len: usize, -) -> Result, Error> { - let mut encoded = job.coinbase_tx_prefix.to_vec(); - // just add empty extranonce space so it can be deserialized. The real extranonce - // should be inserted based on the miner's shares - let extranonce = vec![0_u8; full_extranonce_len]; - encoded.extend_from_slice(&extranonce[..]); - encoded.extend_from_slice(job.coinbase_tx_suffix.inner_as_ref()); - let coinbase = consensus::deserialize(&encoded).map_err(|_| Error::InvalidCoinbase)?; - let stripped_tx = StrippedCoinbaseTx::from_coinbase(coinbase, full_extranonce_len)?; - - Ok(NewExtendedMiningJob { - channel_id: job.channel_id, - job_id: job.job_id, - min_ntime: job.min_ntime, - version: job.version, - version_rolling_allowed: job.version_rolling_allowed, - merkle_path: job.merkle_path, - coinbase_tx_prefix: stripped_tx.into_coinbase_tx_prefix()?, - coinbase_tx_suffix: stripped_tx.into_coinbase_tx_suffix()?, - }) -} -// Helper type to strip a segwit data from the coinbase_tx_prefix and coinbase_tx_suffix -// to ensure miners are hashing with the correct coinbase -struct StrippedCoinbaseTx { - version: u32, - inputs: Vec>, - outputs: Vec>, - lock_time: u32, - // helper field - bip141_bytes_len: usize, -} - -impl StrippedCoinbaseTx { - // create - fn from_coinbase(tx: Transaction, full_extranonce_len: usize) -> Result { - let bip141_bytes_len = tx - .input - .last() - .ok_or(Error::BadPayloadSize)? - .script_sig - .len() - - full_extranonce_len; - Ok(Self { - version: tx.version.0 as u32, - inputs: tx - .input - .iter() - .map(|txin| { - let mut ser: Vec = vec![]; - ser.extend_from_slice(txin.previous_output.txid.as_ref()); - ser.extend_from_slice(&txin.previous_output.vout.to_le_bytes()); - ser.push(txin.script_sig.len() as u8); - ser.extend_from_slice(txin.script_sig.as_bytes()); - ser.extend_from_slice(&txin.sequence.0.to_le_bytes()); - ser - }) - .collect(), - outputs: tx.output.iter().map(consensus::serialize).collect(), - lock_time: tx.lock_time.to_consensus_u32(), - bip141_bytes_len, - }) - } - - // The coinbase tx prefix is the LE bytes concatenation of the tx version and all - // of the tx inputs minus the 32 bytes after the script_sig_prefix bytes - // and the last input's sequence (used as the first entry in the coinbase tx suffix). - // The last 32 bytes after the bip34 bytes in the script will be used to allow extranonce - // space for the miner. We remove the bip141 marker and flag since it is only used for - // computing the `wtxid` and the legacy `txid` is what is used for computing the merkle root - // clippy allow because we don't want to consume self - #[allow(clippy::wrong_self_convention)] - fn into_coinbase_tx_prefix(&self) -> Result, errors::Error> { - let mut inputs = self.inputs.clone(); - let last_input = inputs.last_mut().ok_or(Error::BadPayloadSize)?; - let new_last_input_len = - 32 // outpoint - + 4 // vout - + 1 // script length byte -> TODO can be also 3 (based on TODO in `coinbase_tx_prefix()`) - + self.bip141_bytes_len // space for bip34 bytes - ; - last_input.truncate(new_last_input_len); - let mut prefix: Vec = vec![]; - prefix.extend_from_slice(&self.version.to_le_bytes()); - prefix.push(self.inputs.len() as u8); - prefix.extend_from_slice(&inputs.concat()); - prefix.try_into().map_err(Error::BinarySv2Error) - } - - // This coinbase tx suffix is the sequence of the last tx input plus - // the serialized tx outputs and the lock time. Note we do not use the witnesses - // (placed between txouts and lock time) since it is only used for - // computing the `wtxid` and the legacy `txid` is what is used for computing the merkle root - // clippy allow because we don't want to consume self - #[allow(clippy::wrong_self_convention)] - fn into_coinbase_tx_suffix(&self) -> Result, errors::Error> { - let mut suffix: Vec = vec![]; - let last_input = self.inputs.last().ok_or(Error::BadPayloadSize)?; - // only take the last intput's sequence u32 (bytes after the extranonce space) - let last_input_sequence = &last_input[last_input.len() - 4..]; - suffix.extend_from_slice(last_input_sequence); - suffix.push(self.outputs.len() as u8); - suffix.extend_from_slice(&self.outputs.concat()); - suffix.extend_from_slice(&self.lock_time.to_le_bytes()); - suffix.try_into().map_err(Error::BinarySv2Error) - } -} - -// Test -#[cfg(test)] - -pub mod tests { - use super::*; - use crate::utils::merkle_root_from_path; - #[cfg(feature = "prop_test")] - use codec_sv2::binary_sv2::u256_from_int; - use quickcheck::{Arbitrary, Gen}; - use std::{cmp, vec}; - - #[cfg(feature = "prop_test")] - use std::borrow::BorrowMut; - - use bitcoin::{consensus::Encodable, secp256k1::Secp256k1, Network, PrivateKey, PublicKey}; - - pub fn template_from_gen(g: &mut Gen) -> NewTemplate<'static> { - let mut coinbase_prefix_gen = Gen::new(255); - let mut coinbase_prefix: vec::Vec = vec::Vec::new(); - - let max_num_for_script_sig_prefix = 253; - let prefix_len = cmp::min(u8::arbitrary(&mut coinbase_prefix_gen), 6); - coinbase_prefix.push(prefix_len); - coinbase_prefix.resize_with(prefix_len as usize + 2, || { - cmp::min( - u8::arbitrary(&mut coinbase_prefix_gen), - max_num_for_script_sig_prefix, - ) - }); - let coinbase_prefix: binary_sv2::B0255 = coinbase_prefix.try_into().unwrap(); - - let mut coinbase_tx_outputs_gen = Gen::new(32); - let mut coinbase_tx_outputs_inner: vec::Vec = vec::Vec::new(); - coinbase_tx_outputs_inner.resize_with(32, || u8::arbitrary(&mut coinbase_tx_outputs_gen)); - let coinbase_tx_outputs: binary_sv2::B064K = coinbase_tx_outputs_inner.try_into().unwrap(); - - let mut merkle_path_inner_gen = Gen::new(32); - let mut merkle_path_inner: vec::Vec = vec::Vec::new(); - merkle_path_inner.resize_with(32, || u8::arbitrary(&mut merkle_path_inner_gen)); - let merkle_path_inner: binary_sv2::U256 = merkle_path_inner.try_into().unwrap(); - let merkle_path: binary_sv2::Seq0255 = vec![merkle_path_inner].into(); - - NewTemplate { - template_id: u64::arbitrary(g), - future_template: bool::arbitrary(g), - version: u32::arbitrary(g), - coinbase_tx_version: 2, - coinbase_prefix, - coinbase_tx_input_sequence: u32::arbitrary(g), - coinbase_tx_value_remaining: u64::arbitrary(g), - coinbase_tx_outputs_count: 0, - coinbase_tx_outputs, - coinbase_tx_locktime: u32::arbitrary(g), - merkle_path, - } - } - - const PRIVATE_KEY_BTC: [u8; 32] = [34; 32]; - const NETWORK: Network = Network::Testnet; - - #[cfg(feature = "prop_test")] - const BLOCK_REWARD: u64 = 625_000_000_000; - - pub fn new_pub_key() -> PublicKey { - let priv_k = PrivateKey::from_slice(&PRIVATE_KEY_BTC, NETWORK).unwrap(); - let secp = Secp256k1::default(); - - PublicKey::from_private_key(&secp, &priv_k) - } - - #[cfg(feature = "prop_test")] - use bitcoin::ScriptBuf; - - // Test job_id_from_template - #[cfg(feature = "prop_test")] - #[quickcheck_macros::quickcheck] - fn test_job_id_from_template(mut template: NewTemplate<'static>) { - let mut prefix = template.coinbase_prefix.to_vec(); - if prefix.len() > 0 { - let len = u8::min(prefix[0], 6); - prefix[0] = len; - prefix.resize(len as usize + 2, 0); - template.coinbase_prefix = prefix.try_into().unwrap(); - }; - let out = TxOut { - value: Amount::from_sat(BLOCK_REWARD), - script_pubkey: ScriptBuf::new_p2pk(&new_pub_key()), - }; - let mut jobs_creators = JobsCreators::new(32); - - let job = jobs_creators - .on_new_template(template.borrow_mut(), false, vec![out]) - .unwrap(); - - assert_eq!( - jobs_creators.get_template_id_from_job(job.job_id), - Some(template.template_id) - ); - - // Assert returns non if no match - assert_eq!(jobs_creators.get_template_id_from_job(70), None); - } - - // Test reset new template - #[cfg(feature = "prop_test")] - #[quickcheck_macros::quickcheck] - fn test_reset_new_template(mut template: NewTemplate<'static>) { - let out = TxOut { - value: Amount::from_sat(BLOCK_REWARD), - script_pubkey: ScriptBuf::new_p2pk(&new_pub_key()), - }; - let mut jobs_creators = JobsCreators::new(32); - - assert_eq!(jobs_creators.lasts_new_template.len(), 0); - - let _ = jobs_creators.on_new_template(template.borrow_mut(), false, vec![out]); - - assert_eq!(jobs_creators.lasts_new_template.len(), 1); - assert_eq!(jobs_creators.lasts_new_template[0], template); - - //Create a 2nd template - let mut template2 = template_from_gen(&mut Gen::new(255)); - template2.template_id = template.template_id.checked_sub(1).unwrap_or(0); - - // Reset new template - jobs_creators.reset_new_templates(Some(template2.clone())); - - // Should be pointing at new template - assert_eq!(jobs_creators.lasts_new_template.len(), 1); - assert_eq!(jobs_creators.lasts_new_template[0], template2); - - // Reset new template - jobs_creators.reset_new_templates(None); - - // Should be pointing at new template - assert_eq!(jobs_creators.lasts_new_template.len(), 0); - } - - // Test on_new_prev_hash - #[cfg(feature = "prop_test")] - #[quickcheck_macros::quickcheck] - fn test_on_new_prev_hash(mut template: NewTemplate<'static>) { - let out = TxOut { - value: Amount::from_sat(BLOCK_REWARD), - script_pubkey: ScriptBuf::new_p2pk(&new_pub_key()), - }; - let mut jobs_creators = JobsCreators::new(32); - - //Create a template - let _ = jobs_creators.on_new_template(template.borrow_mut(), false, vec![out]); - let test_id = template.template_id; - - // Create a SetNewPrevHash with matching template_id - let prev_hash = SetNewPrevHash { - template_id: test_id, - prev_hash: u256_from_int(45_u32), - header_timestamp: 0, - n_bits: 0, - target: ([0_u8; 32]).try_into().unwrap(), - }; - - jobs_creators.on_new_prev_hash(&prev_hash); - - //Validate that we still have the same template loaded as there were matching templateIds - assert_eq!(jobs_creators.lasts_new_template.len(), 1); - assert_eq!(jobs_creators.lasts_new_template[0], template); - - // Create a SetNewPrevHash with matching template_id - let test_id_2 = test_id.wrapping_add(1); - let prev_hash2 = SetNewPrevHash { - template_id: test_id_2, - prev_hash: u256_from_int(45_u32), - header_timestamp: 0, - n_bits: 0, - target: ([0_u8; 32]).try_into().unwrap(), - }; - - jobs_creators.on_new_prev_hash(&prev_hash2); - - //Validate that templates were cleared as we got a new templateId in setNewPrevHash - assert_eq!(jobs_creators.lasts_new_template.len(), 0); - } - - #[quickcheck_macros::quickcheck] - fn it_parse_valid_tx_outs( - mut hash1: Vec, - mut hash2: Vec, - value1: u64, - value2: u64, - size1: u8, - size2: u8, - ) { - hash1.resize(size1 as usize + 2, 0); - hash2.resize(size2 as usize + 2, 0); - let tx1 = TxOut { - value: Amount::from_sat(value1), - script_pubkey: hash1.into(), - }; - let tx2 = TxOut { - value: Amount::from_sat(value2), - script_pubkey: hash2.into(), - }; - let mut encoded1 = vec![]; - let mut encoded2 = vec![]; - tx1.consensus_encode(&mut encoded1).unwrap(); - tx2.consensus_encode(&mut encoded2).unwrap(); - let mut encoded = vec![]; - encoded.append(&mut encoded1.clone()); - encoded.append(&mut encoded2.clone()); - let outs = tx_outputs_to_costum_scripts(&encoded[..]); - assert!(outs[0] == tx1); - assert!(outs[1] == tx2); - } - - // test that witness stripped tx id matches that of the txid of the coinbase - #[test] - fn stripped_tx_id() { - let encoded: &[u8] = &[ - 2, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 36, 2, 107, 22, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, - 255, 255, 2, 0, 0, 0, 0, 0, 0, 0, 0, 67, 65, 4, 70, 109, 127, 202, 229, 99, 229, 203, - 9, 160, 209, 135, 11, 181, 128, 52, 72, 4, 97, 120, 121, 161, 73, 73, 207, 34, 40, 95, - 27, 174, 63, 39, 103, 40, 23, 108, 60, 100, 49, 248, 238, 218, 69, 56, 220, 55, 200, - 101, 226, 120, 79, 58, 158, 119, 208, 68, 243, 62, 64, 119, 151, 225, 39, 138, 172, 0, - 0, 0, 0, 0, 0, 0, 0, 38, 106, 36, 170, 33, 169, 237, 226, 246, 28, 63, 113, 209, 222, - 253, 63, 169, 153, 223, 163, 105, 83, 117, 92, 105, 6, 137, 121, 153, 98, 180, 139, - 235, 216, 54, 151, 78, 140, 249, 1, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - ]; - let coinbase: Transaction = consensus::deserialize(encoded).unwrap(); - let stripped = StrippedCoinbaseTx::from_coinbase(coinbase.clone(), 32).unwrap(); - let prefix = stripped.into_coinbase_tx_prefix().unwrap().to_vec(); - let suffix = stripped.into_coinbase_tx_suffix().unwrap().to_vec(); - let extranonce = &[0_u8; 32]; - let path: &[binary_sv2::U256] = &[]; - let stripped_merkle_root = - merkle_root_from_path(&prefix[..], &suffix[..], extranonce, path).unwrap(); - let txid = coinbase.compute_txid(); - let txid_bytes: &[u8; 32] = txid.as_ref(); - let og_merkle_root = txid_bytes.to_vec(); - assert!( - stripped_merkle_root == og_merkle_root, - "stripped tx hash is not the same as bitcoin crate" - ); - } - #[test] - fn stripped_tx_id_braiins_example() { - let mut encoded = vec![]; - let coinbase_prefix = &[ - 1_u8, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 75, 3, 176, 235, 11, 250, 190, 109, 109, - 50, 247, 22, 140, 225, 176, 1, 231, 78, 225, 50, 226, 181, 165, 55, 145, 137, 154, 46, - 9, 44, 65, 72, 231, 173, 111, 131, 26, 81, 223, 179, 225, 1, 0, 0, 0, 0, 0, 0, 0, - ]; - let coinbase_suffix = &[ - 245_u8, 192, 42, 69, 19, 47, 115, 108, 117, 115, 104, 47, 0, 0, 0, 0, 3, 78, 213, 148, - 39, 0, 0, 0, 0, 25, 118, 169, 20, 124, 21, 78, 209, 220, 89, 96, 158, 61, 38, 171, 178, - 223, 46, 163, 213, 135, 205, 140, 65, 136, 172, 0, 0, 0, 0, 0, 0, 0, 0, 44, 106, 76, - 41, 82, 83, 75, 66, 76, 79, 67, 75, 58, 214, 9, 239, 96, 221, 25, 108, 87, 155, 50, 55, - 47, 91, 115, 172, 168, 0, 12, 86, 195, 26, 241, 10, 22, 190, 151, 254, 24, 0, 78, 106, - 26, 0, 0, 0, 0, 0, 0, 0, 0, 38, 106, 36, 170, 33, 169, 237, 103, 66, 68, 105, 2, 55, - 65, 241, 216, 46, 82, 223, 150, 0, 97, 103, 2, 82, 186, 233, 145, 90, 210, 231, 35, - 100, 107, 52, 171, 233, 50, 200, 0, 0, 0, 0, - ]; - let extranonce = [0_u8; 15]; // braiins pool requires 15 bytes for extranonce - encoded.extend_from_slice(coinbase_prefix); - let mut encoded_clone = encoded.clone(); - encoded_clone.extend_from_slice(&extranonce); - encoded_clone.extend_from_slice(coinbase_suffix); - // let mut i = 1; - // while let Err(_) = Transaction::deserialize(&encoded_clone) { - // encoded_clone = encoded.clone(); - // extranonce.push(0); - // encoded_clone.extend_from_slice(&extranonce[..]); - // encoded_clone.extend_from_slice(coinbase_suffix); - // i+=1; - // } - // println!("SIZE: {:?}", i); - let _tx: Transaction = consensus::deserialize(&encoded_clone).unwrap(); - } -}