diff --git a/sv2/channels-sv2/src/server/extended.rs b/sv2/channels-sv2/src/server/extended.rs index 11e2495cac..d0624595b7 100644 --- a/sv2/channels-sv2/src/server/extended.rs +++ b/sv2/channels-sv2/src/server/extended.rs @@ -44,7 +44,9 @@ use crate::{ merkle_root::merkle_root_from_path, server::{ error::ExtendedChannelError, - jobs::{extended::ExtendedJob, factory::JobFactory, job_store::JobStore, JobOrigin}, + jobs::{ + extended::ExtendedJob, factory::JobFactoryExtended, job_store::JobStore, JobOrigin, + }, share_accounting::{ShareAccounting, ShareValidationError, ShareValidationResult}, }, target::{bytes_to_hex, hash_rate_to_target, u256_to_block_hash}, @@ -83,9 +85,11 @@ use tracing::debug; /// - the channel's job factory /// - the channel's chain tip #[derive(Debug)] -pub struct ExtendedChannel<'a, J> +pub struct ExtendedChannel<'a, J, E, F> where - J: JobStore>, + E: ExtendedJob<'a>, + F: JobFactoryExtended<'a, E>, + J: JobStore, { channel_id: u32, user_identity: String, @@ -95,16 +99,18 @@ where target: Target, // todo: try to use Target from rust-bitcoin nominal_hashrate: f32, job_store: J, - job_factory: JobFactory, + job_factory: F, share_accounting: ShareAccounting, expected_share_per_minute: f32, chain_tip: Option, - phantom: PhantomData<&'a ()>, + phantom: PhantomData<(E, &'a ())>, } -impl<'a, J> ExtendedChannel<'a, J> +impl<'a, J, E, F> ExtendedChannel<'a, J, E, F> where - J: JobStore>, + E: ExtendedJob<'a>, + F: JobFactoryExtended<'a, E>, + J: JobStore, { /// Constructor of `ExtendedChannel` for a Sv2 Pool Server. /// Not meant for usage on a Sv2 Job Declaration Client. @@ -243,7 +249,7 @@ where target, nominal_hashrate, job_store, - job_factory: JobFactory::new(version_rolling_allowed, pool_tag, miner_tag), + job_factory: F::new_factory_extended(version_rolling_allowed, pool_tag, miner_tag), share_accounting: ShareAccounting::new(share_batch_size), expected_share_per_minute, chain_tip: None, @@ -408,19 +414,19 @@ where } /// Returns an owned copy of the currently active job, if any. - pub fn get_active_job(&self) -> Option> { + pub fn get_active_job(&self) -> Option { // cloning happens inside the job store self.job_store.get_active_job() } /// Returns an owned copy of a future job from its job ID, if any. - pub fn get_future_job(&self, job_id: u32) -> Option> { + pub fn get_future_job(&self, job_id: u32) -> Option { // cloning happens inside the job store self.job_store.get_future_job(job_id) } /// Returns an owned copy of a past job from its job ID, if any. - pub fn get_past_job(&self, job_id: u32) -> Option> { + pub fn get_past_job(&self, job_id: u32) -> Option { // cloning happens inside the job store self.job_store.get_past_job(job_id) } @@ -738,7 +744,12 @@ mod tests { server::{ error::ExtendedChannelError, extended::ExtendedChannel, - jobs::job_store::{DefaultJobStore, JobStore}, + jobs::{ + extended::{DefaultExtendedJob, ExtendedJob}, + factory::DefaultJobFactory, + job_store::{DefaultJobStore, JobStore}, + Job, + }, share_accounting::{ShareValidationError, ShareValidationResult}, }, }; @@ -768,9 +779,14 @@ mod tests { let version_rolling_allowed = true; let rollable_extranonce_size = 4u16; let share_batch_size = 100; - let job_store = DefaultJobStore::new(); - - let mut channel = ExtendedChannel::new( + let job_store: DefaultJobStore = DefaultJobStore::new(); + + let mut channel: ExtendedChannel< + '_, + DefaultJobStore, + DefaultExtendedJob, + DefaultJobFactory, + > = ExtendedChannel::new( channel_id, user_identity, extranonce_prefix, @@ -914,9 +930,14 @@ mod tests { let version_rolling_allowed = true; let rollable_extranonce_size = 4u16; let share_batch_size = 100; - let job_store = DefaultJobStore::new(); - - let mut channel = ExtendedChannel::new( + let job_store: DefaultJobStore = DefaultJobStore::new(); + + let mut channel: ExtendedChannel< + '_, + DefaultJobStore, + DefaultExtendedJob, + DefaultJobFactory, + > = ExtendedChannel::new( channel_id, user_identity, extranonce_prefix, @@ -1034,9 +1055,14 @@ mod tests { let version_rolling_allowed = true; let rollable_extranonce_size = 4u16; let share_batch_size = 100; - let job_store = DefaultJobStore::new(); - - let mut channel = ExtendedChannel::new( + let job_store: DefaultJobStore = DefaultJobStore::new(); + + let mut channel: ExtendedChannel< + '_, + DefaultJobStore, + DefaultExtendedJob, + DefaultJobFactory, + > = ExtendedChannel::new( channel_id, user_identity, extranonce_prefix, @@ -1112,9 +1138,14 @@ mod tests { let version_rolling_allowed = true; let rollable_extranonce_size = 8u16; let share_batch_size = 100; - let job_store = DefaultJobStore::new(); - - let mut channel = ExtendedChannel::new( + let job_store: DefaultJobStore = DefaultJobStore::new(); + + let mut channel: ExtendedChannel< + '_, + DefaultJobStore, + DefaultExtendedJob, + DefaultJobFactory, + > = ExtendedChannel::new( channel_id, user_identity, extranonce_prefix, @@ -1224,9 +1255,14 @@ mod tests { let version_rolling_allowed = true; let rollable_extranonce_size = 8u16; let share_batch_size = 100; - let job_store = DefaultJobStore::new(); - - let mut channel = ExtendedChannel::new( + let job_store: DefaultJobStore = DefaultJobStore::new(); + + let mut channel: ExtendedChannel< + '_, + DefaultJobStore, + DefaultExtendedJob, + DefaultJobFactory, + > = ExtendedChannel::new( channel_id, user_identity, extranonce_prefix, @@ -1336,9 +1372,14 @@ mod tests { let version_rolling_allowed = true; let rollable_extranonce_size = 8u16; let share_batch_size = 100; - let job_store = DefaultJobStore::new(); - - let mut channel = ExtendedChannel::new( + let job_store: DefaultJobStore = DefaultJobStore::new(); + + let mut channel: ExtendedChannel< + '_, + DefaultJobStore, + DefaultExtendedJob, + DefaultJobFactory, + > = ExtendedChannel::new( channel_id, user_identity, extranonce_prefix, @@ -1459,13 +1500,18 @@ mod tests { let version_rolling_allowed = true; let rollable_extranonce_size = 4u16; let share_batch_size = 100; - let job_store = DefaultJobStore::new(); + let job_store: DefaultJobStore = DefaultJobStore::new(); // this is the most permissive possible max_target let max_target = Target::from_le_bytes([0xff; 32]); // Create a channel with initial hashrate - let mut channel = ExtendedChannel::new( + let mut channel: ExtendedChannel< + '_, + DefaultJobStore, + DefaultExtendedJob, + DefaultJobFactory, + > = ExtendedChannel::new( channel_id, user_identity, extranonce_prefix, @@ -1550,9 +1596,14 @@ mod tests { let version_rolling_allowed = true; let rollable_extranonce_size = 4u16; let share_batch_size = 100; - let job_store = DefaultJobStore::new(); - - let mut channel = ExtendedChannel::new( + let job_store: DefaultJobStore = DefaultJobStore::new(); + + let mut channel: ExtendedChannel< + '_, + DefaultJobStore, + DefaultExtendedJob, + DefaultJobFactory, + > = ExtendedChannel::new( channel_id, user_identity, extranonce_prefix.clone(), diff --git a/sv2/channels-sv2/src/server/group.rs b/sv2/channels-sv2/src/server/group.rs index d324033585..48117c0009 100644 --- a/sv2/channels-sv2/src/server/group.rs +++ b/sv2/channels-sv2/src/server/group.rs @@ -35,7 +35,7 @@ use crate::{ chain_tip::ChainTip, server::{ error::GroupChannelError, - jobs::{extended::ExtendedJob, factory::JobFactory, job_store::JobStore}, + jobs::{extended::ExtendedJob, factory::JobFactoryExtended, job_store::JobStore}, }, }; use bitcoin::transaction::TxOut; @@ -58,22 +58,26 @@ use template_distribution_sv2::{NewTemplate, SetNewPrevHash as SetNewPrevHashTdp /// - the group channel's stale jobs /// - the group channel's share validation state #[derive(Debug)] -pub struct GroupChannel<'a, J> +pub struct GroupChannel<'a, J, E, F> where - J: JobStore>, + J: JobStore, + E: ExtendedJob<'a>, + F: JobFactoryExtended<'a, E>, { group_channel_id: u32, standard_channel_ids: HashSet, - job_factory: JobFactory, + job_factory: F, job_store: J, chain_tip: Option, full_extranonce_size: usize, - phantom: PhantomData<&'a ()>, + phantom: PhantomData<(E, &'a ())>, } -impl<'a, J> GroupChannel<'a, J> +impl<'a, J, E, F> GroupChannel<'a, J, E, F> where - J: JobStore>, + J: JobStore, + E: ExtendedJob<'a>, + F: JobFactoryExtended<'a, E>, { /// Constructor of `GroupChannel` for a Sv2 Pool Server. /// Not meant for usage on a Sv2 Job Declaration Client. @@ -149,7 +153,7 @@ where Ok(Self { group_channel_id, standard_channel_ids: HashSet::new(), - job_factory: JobFactory::new(true, pool_tag, miner_tag), + job_factory: F::new_factory_extended(true, pool_tag, miner_tag), job_store, chain_tip: None, full_extranonce_size, @@ -193,7 +197,7 @@ where } /// Returns an owned copy of the currently active job, if any. - pub fn get_active_job(&self) -> Option> { + pub fn get_active_job(&self) -> Option { // cloning happens inside the job store self.job_store.get_active_job() } @@ -205,7 +209,7 @@ where } /// Returns an owned copy of a future job from its job ID, if any. - pub fn get_future_job(&self, job_id: u32) -> Option> { + pub fn get_future_job(&self, job_id: u32) -> Option { // cloning happens inside the job store self.job_store.get_future_job(job_id) } @@ -300,7 +304,12 @@ mod tests { chain_tip::ChainTip, server::{ group::GroupChannel, - jobs::job_store::{DefaultJobStore, JobStore}, + jobs::{ + extended::{DefaultExtendedJob, ExtendedJob}, + factory::DefaultJobFactory, + job_store::{DefaultJobStore, JobStore}, + Job, + }, }, }; use binary_sv2::Sv2Option; @@ -317,9 +326,13 @@ mod tests { // the messages on this test were collected from a sane message flow // we use them as test vectors to assert correct behavior of job creation let group_channel_id = 1; - let job_store = DefaultJobStore::new(); + let job_store: DefaultJobStore> = DefaultJobStore::new(); let full_extranonce_size = 32; - let mut group_channel = GroupChannel::new( + let mut group_channel = GroupChannel::< + DefaultJobStore>, + DefaultExtendedJob<'_>, + DefaultJobFactory, + >::new( group_channel_id, job_store, full_extranonce_size, @@ -449,7 +462,11 @@ mod tests { let job_store = DefaultJobStore::new(); let full_extranonce_size = 32; - let mut group_channel = GroupChannel::new( + let mut group_channel = GroupChannel::< + DefaultJobStore>, + DefaultExtendedJob<'_>, + DefaultJobFactory, + >::new( group_channel_id, job_store, full_extranonce_size, @@ -507,7 +524,7 @@ mod tests { .on_new_template(template.clone(), coinbase_reward_outputs) .unwrap(); - let active_job = group_channel.get_active_job().unwrap(); + let active_job: DefaultExtendedJob<'_> = group_channel.get_active_job().unwrap(); // we know that the provided template + coinbase_reward_outputs should generate this // non-future job @@ -545,9 +562,13 @@ mod tests { // we use them as test vectors to assert correct behavior of job creation let group_channel_id = 1; - let job_store = DefaultJobStore::new(); + let job_store: DefaultJobStore> = DefaultJobStore::new(); let full_extranonce_size = 32; - let mut group_channel = GroupChannel::new( + let mut group_channel = GroupChannel::< + DefaultJobStore>, + DefaultExtendedJob<'_>, + DefaultJobFactory, + >::new( group_channel_id, job_store, full_extranonce_size, diff --git a/sv2/channels-sv2/src/server/jobs/extended.rs b/sv2/channels-sv2/src/server/jobs/extended.rs index dc26d7d07d..064251bc40 100644 --- a/sv2/channels-sv2/src/server/jobs/extended.rs +++ b/sv2/channels-sv2/src/server/jobs/extended.rs @@ -2,7 +2,11 @@ use super::Job; use crate::{ merkle_root::merkle_root_from_path, outputs::deserialize_template_outputs, - server::jobs::{error::ExtendedJobError, standard::StandardJob, JobOrigin}, + server::jobs::{ + error::ExtendedJobError, + standard::{DefaultStandardJob, StandardJob}, + JobOrigin, + }, }; use binary_sv2::{Seq0255, Sv2Option, U256}; use bitcoin::transaction::TxOut; @@ -10,6 +14,54 @@ use mining_sv2::{NewExtendedMiningJob, NewMiningJob, SetCustomMiningJob}; use std::convert::TryInto; use template_distribution_sv2::NewTemplate; +pub trait ExtendedJob<'a>: Job { + // the standard job type that this extended job could be potentially converted to + type StandardJob: StandardJob<'a>; + + fn from_template( + template: NewTemplate<'a>, + extranonce_prefix: Vec, + additional_coinbase_outputs: Vec, + coinbase_tx_prefix: Vec, + coinbase_tx_suffix: Vec, + job_message: NewExtendedMiningJob<'a>, + ) -> Result + where + Self: Sized; + + fn from_custom_job( + custom_job: SetCustomMiningJob<'a>, + extranonce_prefix: Vec, + coinbase_outputs: Vec, + coinbase_tx_prefix: Vec, + coinbase_tx_suffix: Vec, + job_message: NewExtendedMiningJob<'a>, + ) -> Self + where + Self: Sized; + + fn into_standard_job( + self, + channel_id: u32, + extranonce_prefix: Vec, + ) -> Result + where + Self: Sized; + + fn get_origin(&self) -> &JobOrigin<'a>; + fn get_coinbase_outputs(&self) -> &Vec; + fn get_job_message(&self) -> &NewExtendedMiningJob<'a>; + fn get_merkle_path(&self) -> &Seq0255<'a, U256<'a>>; + fn get_min_ntime(&self) -> Sv2Option<'a, u32>; + fn get_version(&self) -> u32; + fn version_rolling_allowed(&self) -> bool; + fn get_coinbase_tx_prefix_with_bip141(&self) -> Vec; + fn get_coinbase_tx_suffix_with_bip141(&self) -> Vec; + fn get_coinbase_tx_prefix_without_bip141(&self) -> Vec; + fn get_coinbase_tx_suffix_without_bip141(&self) -> Vec; + fn get_extranonce_prefix(&self) -> &Vec; +} + /// Abstraction of an extended mining job with: /// - the `NewTemplate` OR `SetCustomMiningJob` message that originated it /// - the extranonce prefix associated with the channel at the time of job creation @@ -25,7 +77,7 @@ use template_distribution_sv2::NewTemplate; /// That makes it easy to calculate the coinbase `txid` (instead of `wtxid`) for merkle root /// calculation. #[derive(Debug, Clone)] -pub struct ExtendedJob<'a> { +pub struct DefaultExtendedJob<'a> { origin: JobOrigin<'a>, extranonce_prefix: Vec, coinbase_outputs: Vec, @@ -34,21 +86,23 @@ pub struct ExtendedJob<'a> { job_message: NewExtendedMiningJob<'a>, } -impl Job for ExtendedJob<'_> { +impl Job for DefaultExtendedJob<'_> { fn get_job_id(&self) -> u32 { self.job_message.job_id } fn activate(&mut self, min_ntime: u32) { - self.activate(min_ntime); + self.job_message.min_ntime = Sv2Option::new(Some(min_ntime)); } } -impl<'a> ExtendedJob<'a> { +impl<'a> ExtendedJob<'a> for DefaultExtendedJob<'a> { + type StandardJob = DefaultStandardJob<'a>; + /// Creates a new job from a template. /// /// `additional_coinbase_outputs` are added to the coinbase outputs coming from the template. - pub fn from_template( + fn from_template( template: NewTemplate<'a>, extranonce_prefix: Vec, additional_coinbase_outputs: Vec, @@ -75,10 +129,11 @@ impl<'a> ExtendedJob<'a> { job_message, }) } + /// Creates a new extended job from a custom mining job message. /// /// Used for jobs originating from [`SetCustomMiningJob`] messages. - pub fn from_custom_job( + fn from_custom_job( custom_job: SetCustomMiningJob<'a>, extranonce_prefix: Vec, coinbase_outputs: Vec, @@ -100,11 +155,11 @@ impl<'a> ExtendedJob<'a> { /// /// Only possible if the job was created from a `NewTemplate`. /// Jobs created from `SetCustomMiningJob` cannot be converted - pub fn into_standard_job( + fn into_standard_job( self, channel_id: u32, extranonce_prefix: Vec, - ) -> Result, ExtendedJobError> { + ) -> Result { // here we can only convert extended jobs that were created from a template let template = match self.get_origin() { JobOrigin::NewTemplate(template) => template, @@ -131,7 +186,7 @@ impl<'a> ExtendedJob<'a> { min_ntime: self.get_min_ntime(), }; - let standard_job = StandardJob::from_template( + let standard_job = DefaultStandardJob::from_template( template.clone(), extranonce_prefix, self.get_coinbase_outputs().clone(), @@ -142,67 +197,61 @@ impl<'a> ExtendedJob<'a> { Ok(standard_job) } - /// Returns the job ID for this job. - pub fn get_job_id(&self) -> u32 { - self.job_message.job_id - } - /// Returns the origin message for this job (template or custom job). - pub fn get_origin(&self) -> &JobOrigin<'a> { + fn get_origin(&self) -> &JobOrigin<'a> { &self.origin } - /// Returns the coinbase transaction without for this job without BIP141 data. - pub fn get_coinbase_tx_prefix_without_bip141(&self) -> Vec { - self.job_message.coinbase_tx_prefix.inner_as_ref().to_vec() - } - - /// Returns the coinbase transaction suffix for this job without BIP141 data. - pub fn get_coinbase_tx_suffix_without_bip141(&self) -> Vec { - self.job_message.coinbase_tx_suffix.inner_as_ref().to_vec() - } - - /// Returns the extranonce prefix used for this job. - pub fn get_extranonce_prefix(&self) -> &Vec { - &self.extranonce_prefix - } /// Returns all coinbase outputs for this job. - pub fn get_coinbase_outputs(&self) -> &Vec { + fn get_coinbase_outputs(&self) -> &Vec { &self.coinbase_outputs } + /// Returns the [`NewExtendedMiningJob`] message for this job. - pub fn get_job_message(&self) -> &NewExtendedMiningJob<'a> { + fn get_job_message(&self) -> &NewExtendedMiningJob<'a> { &self.job_message } + /// Returns the merkle path for this job. - pub fn get_merkle_path(&self) -> &Seq0255<'a, U256<'a>> { + fn get_merkle_path(&self) -> &Seq0255<'a, U256<'a>> { &self.job_message.merkle_path } + /// Returns the minimum ntime for this job (if set). - pub fn get_min_ntime(&self) -> Sv2Option<'a, u32> { + fn get_min_ntime(&self) -> Sv2Option<'a, u32> { self.job_message.min_ntime.clone() } + /// Returns the block version for this job. - pub fn get_version(&self) -> u32 { + fn get_version(&self) -> u32 { self.job_message.version } + /// Returns true if version rolling is allowed for this job. - pub fn version_rolling_allowed(&self) -> bool { + fn version_rolling_allowed(&self) -> bool { self.job_message.version_rolling_allowed } - pub fn get_coinbase_tx_prefix_with_bip141(&self) -> Vec { + fn get_coinbase_tx_prefix_with_bip141(&self) -> Vec { self.coinbase_tx_prefix_with_bip141.clone() } - pub fn get_coinbase_tx_suffix_with_bip141(&self) -> Vec { + fn get_coinbase_tx_suffix_with_bip141(&self) -> Vec { self.coinbase_tx_suffix_with_bip141.clone() } - /// Activates the job, setting the `min_ntime` field of the `NewExtendedMiningJob` message. - /// - /// To be used while activating future jobs upon updating channel `ChainTip` state. - pub fn activate(&mut self, min_ntime: u32) { - self.job_message.min_ntime = Sv2Option::new(Some(min_ntime)); + /// Returns the coinbase transaction prefix for this job without BIP141 data. + fn get_coinbase_tx_prefix_without_bip141(&self) -> Vec { + self.job_message.coinbase_tx_prefix.inner_as_ref().to_vec() + } + + /// Returns the coinbase transaction suffix for this job without BIP141 data. + fn get_coinbase_tx_suffix_without_bip141(&self) -> Vec { + self.job_message.coinbase_tx_suffix.inner_as_ref().to_vec() + } + + /// Returns the extranonce prefix used for this job. + fn get_extranonce_prefix(&self) -> &Vec { + &self.extranonce_prefix } } diff --git a/sv2/channels-sv2/src/server/jobs/factory.rs b/sv2/channels-sv2/src/server/jobs/factory.rs index 266555c3b2..0b5af136d3 100644 --- a/sv2/channels-sv2/src/server/jobs/factory.rs +++ b/sv2/channels-sv2/src/server/jobs/factory.rs @@ -25,7 +25,11 @@ use crate::{ chain_tip::ChainTip, merkle_root::merkle_root_from_path, outputs::deserialize_template_outputs, - server::jobs::{error::*, extended::ExtendedJob, standard::StandardJob}, + server::jobs::{ + error::*, + extended::{DefaultExtendedJob, ExtendedJob}, + standard::{DefaultStandardJob, StandardJob}, + }, }; use binary_sv2::{Sv2Option, B0255}; use bitcoin::{ @@ -39,6 +43,55 @@ use mining_sv2::{NewExtendedMiningJob, NewMiningJob, SetCustomMiningJob}; use std::convert::TryInto; use template_distribution_sv2::NewTemplate; +pub trait JobFactoryExtended<'a, E> +where + E: ExtendedJob<'a>, +{ + fn new_factory_extended( + version_rolling_allowed: bool, + pool_tag_string: Option, + miner_tag_string: Option, + ) -> Self; + + fn new_extended_job( + &mut self, + channel_id: u32, + chain_tip: Option, + extranonce_prefix: Vec, + template: NewTemplate<'a>, + additional_coinbase_outputs: Vec, + full_extranonce_size: usize, + ) -> Result; + + fn new_extended_job_from_custom_job( + &mut self, + set_custom_mining_job: SetCustomMiningJob<'a>, + extranonce_prefix: Vec, + full_extranonce_size: usize, + ) -> Result; +} + +pub trait JobFactoryStandard<'a, S> +where + S: StandardJob<'a>, +{ + fn new_factory_standard( + pool_tag_string: Option, + miner_tag_string: Option, + ) -> Self; + + fn new_standard_job( + &mut self, + channel_id: u32, + chain_tip: Option, + extranonce_prefix: Vec, + template: NewTemplate<'a>, + additional_coinbase_outputs: Vec, + ) -> Result; + + fn op_pushbytes_pool_miner_tag(&self) -> Result, JobFactoryError>; +} + #[derive(Debug, PartialEq, Eq, Clone)] struct JobIdFactory { state: u32, @@ -65,22 +118,19 @@ impl JobIdFactory { /// /// Enables creation of new Standard Jobs from NewTemplate messages. #[derive(Debug, Clone)] -pub struct JobFactory { +pub struct DefaultJobFactory { job_id_factory: JobIdFactory, version_rolling_allowed: bool, pool_tag_string: Option, miner_tag_string: Option, } -impl JobFactory { +impl<'a> JobFactoryExtended<'a, DefaultExtendedJob<'a>> for DefaultJobFactory { /// Creates a new [`JobFactory`] instance. /// /// The `pool_tag_string` and `miner_tag_string` are optional and will be added to the coinbase /// scriptSig. - /// - /// Version rolling is always allowed for standard jobs, so the `version_rolling_allowed` - /// parameter is only relevant for creating extended jobs. - pub fn new( + fn new_factory_extended( version_rolling_allowed: bool, pool_tag_string: Option, miner_tag_string: Option, @@ -93,40 +143,6 @@ impl JobFactory { } } - /// Returns a byte vector with the OP_PUSHBYTES opcode and the pool+miner tag. - /// - /// The character `/` is used as a delimiter. - /// - /// If no pool or miner tag is provided, the delimiters are still added. - pub fn op_pushbytes_pool_miner_tag(&self) -> Result, JobFactoryError> { - let mut pool_miner_tag = vec![]; - pool_miner_tag.extend_from_slice(b"/"); - if let Some(pool_tag_string) = &self.pool_tag_string { - pool_miner_tag.extend_from_slice(pool_tag_string.as_bytes()); - } - pool_miner_tag.extend_from_slice(b"/"); - if let Some(miner_tag_string) = &self.miner_tag_string { - pool_miner_tag.extend_from_slice(miner_tag_string.as_bytes()); - } - pool_miner_tag.extend_from_slice(b"/"); - - // Create the proper OP_PUSHBYTES opcode based on data length - let op_pushbytes = match pool_miner_tag.len() { - // 100 bytes are available for scriptSig - // subtract 5 for BIP34 - // subtract 1 for OP_PUSHBYTES before pool/miner tag - // subtract 1+32 for extranonce (OP_PUSHBYTES + 32 bytes max) - len @ 1..=61 => len as u8, - _ => return Err(JobFactoryError::CoinbaseTxPrefixError), - }; - - let mut op_pushbytes_pool_miner_tag = vec![]; - op_pushbytes_pool_miner_tag.push(op_pushbytes); - op_pushbytes_pool_miner_tag.extend_from_slice(&pool_miner_tag); - - Ok(op_pushbytes_pool_miner_tag) - } - /// Creates a new job from a template. /// /// This job (and related shares) is fully committed to: @@ -136,19 +152,17 @@ impl JobFactory { /// /// The optional `ChainTip` defines whether the job will be future or not. /// - /// Version rolling is always allowed for standard jobs, so the `version_rolling_allowed` - /// parameter is ignored. - /// /// It's up to the caller to ensure that the sum of `additional_coinbase_outputs` is equal to /// available template revenue. Returns an error otherwise. - pub fn new_standard_job<'a>( + fn new_extended_job( &mut self, channel_id: u32, chain_tip: Option, extranonce_prefix: Vec, template: NewTemplate<'a>, additional_coinbase_outputs: Vec, - ) -> Result, JobFactoryError> { + full_extranonce_size: usize, + ) -> Result, JobFactoryError> { let coinbase_outputs_sum = additional_coinbase_outputs .iter() .map(|o| o.value.to_sat()) @@ -164,52 +178,65 @@ impl JobFactory { let coinbase_tx_prefix = self.coinbase_tx_prefix( template.clone(), additional_coinbase_outputs.clone(), - extranonce_prefix.len(), + full_extranonce_size, )?; let coinbase_tx_suffix = self.coinbase_tx_suffix( template.clone(), additional_coinbase_outputs.clone(), - extranonce_prefix.len(), + full_extranonce_size, )?; + + // strip bip141 bytes from coinbase_tx_prefix and coinbase_tx_suffix + let (coinbase_tx_prefix_stripped_bip141, coinbase_tx_suffix_stripped_bip141) = + try_strip_bip141(&coinbase_tx_prefix, &coinbase_tx_suffix) + .map_err(|_| JobFactoryError::FailedToStripBip141)? + .ok_or(JobFactoryError::FailedToStripBip141)?; + let merkle_path = template.merkle_path.clone(); - let merkle_root = merkle_root_from_path( - &coinbase_tx_prefix, - &coinbase_tx_suffix, - &extranonce_prefix, - &merkle_path.inner_as_ref(), - ) - .expect("merkle root must be valid") - .try_into() - .expect("merkle root must be 32 bytes"); let job_message = match template.future_template { - true => NewMiningJob { + true => NewExtendedMiningJob { channel_id, job_id, min_ntime: Sv2Option::new(None), version, - merkle_root, + version_rolling_allowed: self.version_rolling_allowed, + merkle_path, + coinbase_tx_prefix: coinbase_tx_prefix_stripped_bip141 + .try_into() + .map_err(|_| JobFactoryError::CoinbaseTxPrefixError)?, + coinbase_tx_suffix: coinbase_tx_suffix_stripped_bip141 + .try_into() + .map_err(|_| JobFactoryError::CoinbaseTxSuffixError)?, }, false => { let min_ntime = match chain_tip { Some(chain_tip) => Some(chain_tip.min_ntime()), None => return Err(JobFactoryError::ChainTipRequired), }; - - NewMiningJob { + NewExtendedMiningJob { channel_id, job_id, min_ntime: Sv2Option::new(min_ntime), version, - merkle_root, + version_rolling_allowed: self.version_rolling_allowed, + merkle_path, + coinbase_tx_prefix: coinbase_tx_prefix_stripped_bip141 + .try_into() + .map_err(|_| JobFactoryError::CoinbaseTxPrefixError)?, + coinbase_tx_suffix: coinbase_tx_suffix_stripped_bip141 + .try_into() + .map_err(|_| JobFactoryError::CoinbaseTxSuffixError)?, } } }; - let job = StandardJob::from_template( + let job = DefaultExtendedJob::from_template( template, extranonce_prefix, additional_coinbase_outputs, + coinbase_tx_prefix, + coinbase_tx_suffix, job_message, ) .map_err(|_| JobFactoryError::DeserializeCoinbaseOutputsError)?; @@ -217,6 +244,85 @@ impl JobFactory { Ok(job) } + /// Creates a new Extended Job from a SetCustomMiningJob message. + /// + /// Assumes that the SetCustomMiningJob message has already been validated. + /// + /// To be used by Extended Channels on a Sv2 Pool Server. + fn new_extended_job_from_custom_job( + &mut self, + set_custom_mining_job: SetCustomMiningJob<'a>, + extranonce_prefix: Vec, + full_extranonce_size: usize, + ) -> Result, JobFactoryError> { + let serialized_outputs = set_custom_mining_job + .coinbase_tx_outputs + .inner_as_ref() + .to_vec(); + + let coinbase_outputs = Vec::::consensus_decode(&mut serialized_outputs.as_slice()) + .map_err(|_| JobFactoryError::DeserializeCoinbaseOutputsError)?; + + let job_id = self.job_id_factory.next(); + + let version = set_custom_mining_job.version; + + let coinbase_tx_prefix = + self.custom_coinbase_tx_prefix(set_custom_mining_job.clone(), full_extranonce_size)?; + let coinbase_tx_suffix = + self.custom_coinbase_tx_suffix(set_custom_mining_job.clone(), full_extranonce_size)?; + + // strip bip141 bytes from coinbase_tx_prefix and coinbase_tx_suffix + let (coinbase_tx_prefix_stripped_bip141, coinbase_tx_suffix_stripped_bip141) = + try_strip_bip141(&coinbase_tx_prefix, &coinbase_tx_suffix) + .map_err(|_| JobFactoryError::FailedToStripBip141)? + .ok_or(JobFactoryError::FailedToStripBip141)?; + + let merkle_path = set_custom_mining_job.merkle_path.clone().into_static(); + + let job_message = NewExtendedMiningJob { + channel_id: set_custom_mining_job.channel_id, + job_id, + min_ntime: Sv2Option::new(Some(set_custom_mining_job.min_ntime)), + version, + version_rolling_allowed: self.version_rolling_allowed, + coinbase_tx_prefix: coinbase_tx_prefix_stripped_bip141 + .clone() + .try_into() + .map_err(|_| JobFactoryError::CoinbaseTxPrefixError)?, + coinbase_tx_suffix: coinbase_tx_suffix_stripped_bip141 + .clone() + .try_into() + .map_err(|_| JobFactoryError::CoinbaseTxSuffixError)?, + merkle_path, + }; + + let job = DefaultExtendedJob::from_custom_job( + set_custom_mining_job, + extranonce_prefix, + coinbase_outputs, + coinbase_tx_prefix, + coinbase_tx_suffix, + job_message, + ); + + Ok(job) + } +} + +impl<'a> JobFactoryStandard<'a, DefaultStandardJob<'a>> for DefaultJobFactory { + fn new_factory_standard( + pool_tag_string: Option, + miner_tag_string: Option, + ) -> Self { + Self { + job_id_factory: JobIdFactory::new(), + pool_tag_string, + miner_tag_string, + version_rolling_allowed: true, + } + } + /// Creates a new job from a template. /// /// This job (and related shares) is fully committed to: @@ -226,17 +332,19 @@ impl JobFactory { /// /// The optional `ChainTip` defines whether the job will be future or not. /// + /// Version rolling is always allowed for standard jobs, so the `version_rolling_allowed` + /// parameter is ignored. + /// /// It's up to the caller to ensure that the sum of `additional_coinbase_outputs` is equal to /// available template revenue. Returns an error otherwise. - pub fn new_extended_job<'a>( + fn new_standard_job( &mut self, channel_id: u32, chain_tip: Option, extranonce_prefix: Vec, template: NewTemplate<'a>, additional_coinbase_outputs: Vec, - full_extranonce_size: usize, - ) -> Result, JobFactoryError> { + ) -> Result, JobFactoryError> { let coinbase_outputs_sum = additional_coinbase_outputs .iter() .map(|o| o.value.to_sat()) @@ -252,65 +360,52 @@ impl JobFactory { let coinbase_tx_prefix = self.coinbase_tx_prefix( template.clone(), additional_coinbase_outputs.clone(), - full_extranonce_size, + extranonce_prefix.len(), )?; let coinbase_tx_suffix = self.coinbase_tx_suffix( template.clone(), additional_coinbase_outputs.clone(), - full_extranonce_size, + extranonce_prefix.len(), )?; - - // strip bip141 bytes from coinbase_tx_prefix and coinbase_tx_suffix - let (coinbase_tx_prefix_stripped_bip141, coinbase_tx_suffix_stripped_bip141) = - try_strip_bip141(&coinbase_tx_prefix, &coinbase_tx_suffix) - .map_err(|_| JobFactoryError::FailedToStripBip141)? - .ok_or(JobFactoryError::FailedToStripBip141)?; - let merkle_path = template.merkle_path.clone(); + let merkle_root = merkle_root_from_path( + &coinbase_tx_prefix, + &coinbase_tx_suffix, + &extranonce_prefix, + &merkle_path.inner_as_ref(), + ) + .expect("merkle root must be valid") + .try_into() + .expect("merkle root must be 32 bytes"); let job_message = match template.future_template { - true => NewExtendedMiningJob { + true => NewMiningJob { channel_id, job_id, min_ntime: Sv2Option::new(None), version, - version_rolling_allowed: self.version_rolling_allowed, - merkle_path, - coinbase_tx_prefix: coinbase_tx_prefix_stripped_bip141 - .try_into() - .map_err(|_| JobFactoryError::CoinbaseTxPrefixError)?, - coinbase_tx_suffix: coinbase_tx_suffix_stripped_bip141 - .try_into() - .map_err(|_| JobFactoryError::CoinbaseTxSuffixError)?, + merkle_root, }, false => { let min_ntime = match chain_tip { Some(chain_tip) => Some(chain_tip.min_ntime()), None => return Err(JobFactoryError::ChainTipRequired), }; - NewExtendedMiningJob { + + NewMiningJob { channel_id, job_id, min_ntime: Sv2Option::new(min_ntime), version, - version_rolling_allowed: self.version_rolling_allowed, - merkle_path, - coinbase_tx_prefix: coinbase_tx_prefix_stripped_bip141 - .try_into() - .map_err(|_| JobFactoryError::CoinbaseTxPrefixError)?, - coinbase_tx_suffix: coinbase_tx_suffix_stripped_bip141 - .try_into() - .map_err(|_| JobFactoryError::CoinbaseTxSuffixError)?, + merkle_root, } } }; - let job = ExtendedJob::from_template( + let job = DefaultStandardJob::from_template( template, extranonce_prefix, additional_coinbase_outputs, - coinbase_tx_prefix, - coinbase_tx_suffix, job_message, ) .map_err(|_| JobFactoryError::DeserializeCoinbaseOutputsError)?; @@ -318,6 +413,41 @@ impl JobFactory { Ok(job) } + /// Returns a byte vector with the OP_PUSHBYTES opcode and the pool+miner tag. + /// + /// The character `/` is used as a delimiter. + /// + /// If no pool or miner tag is provided, the delimiters are still added. + fn op_pushbytes_pool_miner_tag(&self) -> Result, JobFactoryError> { + let mut pool_miner_tag = vec![]; + pool_miner_tag.extend_from_slice(b"/"); + if let Some(pool_tag_string) = &self.pool_tag_string { + pool_miner_tag.extend_from_slice(pool_tag_string.as_bytes()); + } + pool_miner_tag.extend_from_slice(b"/"); + if let Some(miner_tag_string) = &self.miner_tag_string { + pool_miner_tag.extend_from_slice(miner_tag_string.as_bytes()); + } + pool_miner_tag.extend_from_slice(b"/"); + + // Create the proper OP_PUSHBYTES opcode based on data length + let op_pushbytes = match pool_miner_tag.len() { + // 100 bytes are available for scriptSig + // subtract 5 for BIP34 + // subtract 1 for OP_PUSHBYTES before pool/miner tag + // subtract 1+32 for extranonce (OP_PUSHBYTES + 32 bytes max) + len @ 1..=61 => len as u8, + _ => return Err(JobFactoryError::CoinbaseTxPrefixError), + }; + + let mut op_pushbytes_pool_miner_tag = vec![]; + op_pushbytes_pool_miner_tag.push(op_pushbytes); + op_pushbytes_pool_miner_tag.extend_from_slice(&pool_miner_tag); + + Ok(op_pushbytes_pool_miner_tag) + } +} +impl DefaultJobFactory { /// Creates a new coinbase_tx_prefix and coinbase_tx_suffix from a template. /// /// To be used by a Sv2 Job Declarator Client to create a `DeclareMiningJob` message. @@ -412,75 +542,10 @@ impl JobFactory { Ok(set_custom_mining_job) } - - /// Creates a new Extended Job from a SetCustomMiningJob message. - /// - /// Assumes that the SetCustomMiningJob message has already been validated. - /// - /// To be used by Extended Channels on a Sv2 Pool Server. - pub fn new_extended_job_from_custom_job<'a>( - &mut self, - set_custom_mining_job: SetCustomMiningJob<'a>, - extranonce_prefix: Vec, - full_extranonce_size: usize, - ) -> Result, JobFactoryError> { - let serialized_outputs = set_custom_mining_job - .coinbase_tx_outputs - .inner_as_ref() - .to_vec(); - - let coinbase_outputs = Vec::::consensus_decode(&mut serialized_outputs.as_slice()) - .map_err(|_| JobFactoryError::DeserializeCoinbaseOutputsError)?; - - let job_id = self.job_id_factory.next(); - - let version = set_custom_mining_job.version; - - let coinbase_tx_prefix = - self.custom_coinbase_tx_prefix(set_custom_mining_job.clone(), full_extranonce_size)?; - let coinbase_tx_suffix = - self.custom_coinbase_tx_suffix(set_custom_mining_job.clone(), full_extranonce_size)?; - - // strip bip141 bytes from coinbase_tx_prefix and coinbase_tx_suffix - let (coinbase_tx_prefix_stripped_bip141, coinbase_tx_suffix_stripped_bip141) = - try_strip_bip141(&coinbase_tx_prefix, &coinbase_tx_suffix) - .map_err(|_| JobFactoryError::FailedToStripBip141)? - .ok_or(JobFactoryError::FailedToStripBip141)?; - - let merkle_path = set_custom_mining_job.merkle_path.clone().into_static(); - - let job_message = NewExtendedMiningJob { - channel_id: set_custom_mining_job.channel_id, - job_id, - min_ntime: Sv2Option::new(Some(set_custom_mining_job.min_ntime)), - version, - version_rolling_allowed: self.version_rolling_allowed, - coinbase_tx_prefix: coinbase_tx_prefix_stripped_bip141 - .clone() - .try_into() - .map_err(|_| JobFactoryError::CoinbaseTxPrefixError)?, - coinbase_tx_suffix: coinbase_tx_suffix_stripped_bip141 - .clone() - .try_into() - .map_err(|_| JobFactoryError::CoinbaseTxSuffixError)?, - merkle_path, - }; - - let job = ExtendedJob::from_custom_job( - set_custom_mining_job, - extranonce_prefix, - coinbase_outputs, - coinbase_tx_prefix, - coinbase_tx_suffix, - job_message, - ); - - Ok(job) - } } // impl block with private methods -impl JobFactory { +impl DefaultJobFactory { // build a coinbase transaction from a SetCustomMiningJob // this is only used to extract coinbase_tx_prefix and coinbase_tx_suffix from the custom // coinbase @@ -695,7 +760,11 @@ mod tests { #[test] fn test_new_pool_job() { - let mut job_factory = JobFactory::new(true, Some("Stratum V2 SRI Pool".to_string()), None); + let mut job_factory = DefaultJobFactory::new_factory_extended( + true, + Some("Stratum V2 SRI Pool".to_string()), + None, + ); // note: // the messages on this test were collected from a sane message flow @@ -786,7 +855,7 @@ mod tests { #[test] fn test_new_extended_job_from_custom_job() { - let jdc_job_factory = JobFactory::new( + let jdc_job_factory = DefaultJobFactory::new_factory_extended( true, Some("Stratum V2 SRI Pool".to_string()), Some("Stratum V2 SRI Miner".to_string()), @@ -855,8 +924,11 @@ mod tests { ) .unwrap(); - let mut pool_job_factory = - JobFactory::new(true, Some("Stratum V2 SRI Pool".to_string()), None); + let mut pool_job_factory = DefaultJobFactory::new_factory_extended( + true, + Some("Stratum V2 SRI Pool".to_string()), + None, + ); let custom_job = pool_job_factory .new_extended_job_from_custom_job(set_custom_mining_job, extranonce_prefix, 32) diff --git a/sv2/channels-sv2/src/server/jobs/standard.rs b/sv2/channels-sv2/src/server/jobs/standard.rs index 7efc109ff7..4a728b4ad4 100644 --- a/sv2/channels-sv2/src/server/jobs/standard.rs +++ b/sv2/channels-sv2/src/server/jobs/standard.rs @@ -28,20 +28,38 @@ use bitcoin::transaction::TxOut; use mining_sv2::NewMiningJob; use template_distribution_sv2::NewTemplate; +pub trait StandardJob<'a>: Job { + fn from_template( + template: NewTemplate<'a>, + extranonce_prefix: Vec, + additional_coinbase_outputs: Vec, + job_message: NewMiningJob<'a>, + ) -> Result + where + Self: Sized; + + fn get_coinbase_outputs(&self) -> &Vec; + fn get_extranonce_prefix(&self) -> &Vec; + fn get_job_message(&self) -> &NewMiningJob<'a>; + fn get_template(&self) -> &NewTemplate<'a>; + fn get_merkle_root(&self) -> &U256<'a>; + fn is_future(&self) -> bool; +} + /// Abstraction of a standard mining job with: /// - the `NewTemplate` message that originated it /// - the extranonce prefix associated with the channel at the time of job creation /// - all coinbase outputs (spendable + unspendable) associated with the job /// - the `NewMiningJob` message to be sent across the wire #[derive(Debug, Clone)] -pub struct StandardJob<'a> { +pub struct DefaultStandardJob<'a> { template: NewTemplate<'a>, extranonce_prefix: Vec, coinbase_outputs: Vec, job_message: NewMiningJob<'a>, } -impl Job for StandardJob<'_> { +impl Job for DefaultStandardJob<'_> { /// Returns the job ID for this job. fn get_job_id(&self) -> u32 { self.job_message.job_id @@ -49,16 +67,16 @@ impl Job for StandardJob<'_> { /// Activates the job by setting the minimum ntime field. fn activate(&mut self, min_ntime: u32) { - self.activate(min_ntime); + self.job_message.min_ntime = Sv2Option::new(Some(min_ntime)); } } -impl<'a> StandardJob<'a> { +impl<'a> StandardJob<'a> for DefaultStandardJob<'a> { /// Creates a new standard job from a template. /// /// Combines coinbase outputs from the template and any additional outputs. /// Returns an error if coinbase outputs cannot be deserialized. - pub fn from_template( + fn from_template( template: NewTemplate<'a>, extranonce_prefix: Vec, additional_coinbase_outputs: Vec, @@ -81,38 +99,34 @@ impl<'a> StandardJob<'a> { job_message, }) } - /// Returns the job ID for this job. - pub fn get_job_id(&self) -> u32 { - self.job_message.job_id - } + /// Returns all coinbase outputs (spendable and unspendable) for this job. - pub fn get_coinbase_outputs(&self) -> &Vec { + fn get_coinbase_outputs(&self) -> &Vec { &self.coinbase_outputs } + /// Returns the extranonce prefix used for this job. - pub fn get_extranonce_prefix(&self) -> &Vec { + fn get_extranonce_prefix(&self) -> &Vec { &self.extranonce_prefix } + /// Returns the `NewMiningJob` message for this job. - pub fn get_job_message(&self) -> &NewMiningJob<'a> { + fn get_job_message(&self) -> &NewMiningJob<'a> { &self.job_message } + /// Returns the originating `NewTemplate` message for this job. - pub fn get_template(&self) -> &NewTemplate<'a> { + fn get_template(&self) -> &NewTemplate<'a> { &self.template } + /// Returns the merkle root for this job. - pub fn get_merkle_root(&self) -> &U256<'a> { + fn get_merkle_root(&self) -> &U256<'a> { &self.job_message.merkle_root } + /// Returns true if the job is a future job (not yet activated). - pub fn is_future(&self) -> bool { + fn is_future(&self) -> bool { self.job_message.min_ntime.clone().into_inner().is_none() } - /// Activates the job by setting the minimum ntime field. - /// - /// Should be called when activating future jobs. - pub fn activate(&mut self, min_ntime: u32) { - self.job_message.min_ntime = Sv2Option::new(Some(min_ntime)); - } } diff --git a/sv2/channels-sv2/src/server/standard.rs b/sv2/channels-sv2/src/server/standard.rs index 20d9cb0aba..b9533682fb 100644 --- a/sv2/channels-sv2/src/server/standard.rs +++ b/sv2/channels-sv2/src/server/standard.rs @@ -38,7 +38,8 @@ use crate::{ server::{ error::StandardChannelError, jobs::{ - extended::ExtendedJob, factory::JobFactory, job_store::JobStore, standard::StandardJob, + extended::ExtendedJob, factory::JobFactoryStandard, job_store::JobStore, + standard::StandardJob, }, share_accounting::{ShareAccounting, ShareValidationError, ShareValidationResult}, }, @@ -80,9 +81,12 @@ use tracing::debug; /// - the channel's job factory /// - the channel's chain tip #[derive(Debug)] -pub struct StandardChannel<'a, J> +pub struct StandardChannel<'a, J, S, E, F> where - J: JobStore>, + S: StandardJob<'a>, + E: ExtendedJob<'a, StandardJob = S>, + F: JobFactoryStandard<'a, S>, + J: JobStore, { pub channel_id: u32, user_identity: String, @@ -93,14 +97,17 @@ where share_accounting: ShareAccounting, expected_share_per_minute: f32, job_store: J, - job_factory: JobFactory, + job_factory: F, chain_tip: Option, - phantom: PhantomData<&'a ()>, + phantom: PhantomData<(S, E, &'a ())>, } -impl<'a, J> StandardChannel<'a, J> +impl<'a, J, S, E, F> StandardChannel<'a, J, S, E, F> where - J: JobStore>, + S: StandardJob<'a>, + E: ExtendedJob<'a, StandardJob = S>, + F: JobFactoryStandard<'a, S>, + J: JobStore, { /// Constructor of `StandardChannel` for a Sv2 Pool Server. /// Not meant for usage on a Sv2 Job Declaration Client. @@ -228,7 +235,7 @@ where nominal_hashrate, share_accounting: ShareAccounting::new(share_batch_size), expected_share_per_minute, - job_factory: JobFactory::new(true, pool_tag_string, miner_tag_string), + job_factory: F::new_factory_standard(pool_tag_string, miner_tag_string), chain_tip: None, job_store, phantom: PhantomData, @@ -349,7 +356,7 @@ where } /// Returns the currently active job, if any. - pub fn get_active_job(&self) -> Option> { + pub fn get_active_job(&self) -> Option { // cloning happens inside the job store self.job_store.get_active_job() } @@ -360,19 +367,19 @@ where } /// Returns an owned copy of a future job from its job ID, if any. - pub fn get_future_job(&self, job_id: u32) -> Option> { + pub fn get_future_job(&self, job_id: u32) -> Option { // cloning happens inside the job store self.job_store.get_future_job(job_id) } /// Returns an owned copy of a past job from its job ID, if any. - pub fn get_past_job(&self, job_id: u32) -> Option> { + pub fn get_past_job(&self, job_id: u32) -> Option { // cloning happens inside the job store self.job_store.get_past_job(job_id) } /// Returns an owned copy of a stale job from its job ID, if any. - pub fn get_stale_job(&self, job_id: u32) -> Option> { + pub fn get_stale_job(&self, job_id: u32) -> Option { // cloning happens inside the job store self.job_store.get_stale_job(job_id) } @@ -457,10 +464,7 @@ where /// /// We use this method to update the channel state, so it can validate share from the job that /// was broadcasted to the group channel. - pub fn on_group_channel_job( - &mut self, - extended_job: ExtendedJob<'a>, - ) -> Result<(), StandardChannelError> { + pub fn on_group_channel_job(&mut self, extended_job: E) -> Result<(), StandardChannelError> { let standard_job = extended_job .into_standard_job(self.channel_id, self.extranonce_prefix.clone()) .map_err(|_| StandardChannelError::FailedToConvertToStandardJob)?; @@ -671,8 +675,11 @@ mod tests { server::{ error::StandardChannelError, jobs::{ + extended::DefaultExtendedJob, + factory::DefaultJobFactory, job_store::{DefaultJobStore, JobStore}, - standard::StandardJob, + standard::{DefaultStandardJob, StandardJob}, + Job, }, share_accounting::{ShareValidationError, ShareValidationResult}, standard::StandardChannel, @@ -704,9 +711,14 @@ mod tests { let nominal_hashrate = 10.0; let share_batch_size = 100; let expected_share_per_minute = 1.0; - let job_store = DefaultJobStore::::new(); - - let mut standard_channel = StandardChannel::new( + let job_store = DefaultJobStore::::new(); + + let mut standard_channel = StandardChannel::< + DefaultJobStore, + DefaultStandardJob, + DefaultExtendedJob, + DefaultJobFactory, + >::new( standard_channel_id, user_identity, extranonce_prefix.clone(), @@ -831,9 +843,14 @@ mod tests { let share_batch_size = 100; let expected_share_per_minute = 1.0; - let job_store = DefaultJobStore::::new(); + let job_store = DefaultJobStore::::new(); - let mut standard_channel = StandardChannel::new( + let mut standard_channel = StandardChannel::< + DefaultJobStore, + DefaultStandardJob, + DefaultExtendedJob, + DefaultJobFactory, + >::new( standard_channel_id, user_identity, extranonce_prefix.clone(), @@ -935,9 +952,14 @@ mod tests { let share_batch_size = 100; let expected_share_per_minute = 1.0; - let job_store = DefaultJobStore::::new(); + let job_store = DefaultJobStore::::new(); - let mut standard_channel = StandardChannel::new( + let mut standard_channel = StandardChannel::< + DefaultJobStore, + DefaultStandardJob, + DefaultExtendedJob, + DefaultJobFactory, + >::new( standard_channel_id, user_identity, extranonce_prefix.clone(), @@ -1044,9 +1066,14 @@ mod tests { let share_batch_size = 100; let expected_share_per_minute = 1.0; - let job_store = DefaultJobStore::::new(); + let job_store = DefaultJobStore::::new(); - let mut standard_channel = StandardChannel::new( + let mut standard_channel = StandardChannel::< + DefaultJobStore, + DefaultStandardJob, + DefaultExtendedJob, + DefaultJobFactory, + >::new( standard_channel_id, user_identity, extranonce_prefix.clone(), @@ -1153,9 +1180,14 @@ mod tests { let share_batch_size = 100; let expected_share_per_minute = 1.0; - let job_store = DefaultJobStore::::new(); + let job_store = DefaultJobStore::::new(); - let mut standard_channel = StandardChannel::new( + let mut standard_channel = StandardChannel::< + DefaultJobStore, + DefaultStandardJob, + DefaultExtendedJob, + DefaultJobFactory, + >::new( standard_channel_id, user_identity, extranonce_prefix.clone(), @@ -1253,12 +1285,17 @@ mod tests { let expected_share_per_minute = 1.0; let initial_hashrate = 10.0; let share_batch_size = 100; - let job_store = DefaultJobStore::::new(); + let job_store = DefaultJobStore::::new(); // this is the most permissive possible max_target let max_target = Target::from_le_bytes([0xff; 32]); // Create a channel with initial hashrate - let mut channel = StandardChannel::new( + let mut channel = StandardChannel::< + DefaultJobStore, + DefaultStandardJob, + DefaultExtendedJob, + DefaultJobFactory, + >::new( channel_id, user_identity, extranonce_prefix, @@ -1343,9 +1380,14 @@ mod tests { let expected_share_per_minute = 1.0; let nominal_hashrate = 1_000.0; let share_batch_size = 100; - let job_store = DefaultJobStore::::new(); - - let mut channel = StandardChannel::new( + let job_store = DefaultJobStore::::new(); + + let mut channel = StandardChannel::< + DefaultJobStore, + DefaultStandardJob, + DefaultExtendedJob, + DefaultJobFactory, + >::new( channel_id, user_identity, extranonce_prefix.clone(),