diff --git a/bitcoin-core-sv2/Cargo.toml b/bitcoin-core-sv2/Cargo.toml index d5d4a8bf3..930b3c8eb 100644 --- a/bitcoin-core-sv2/Cargo.toml +++ b/bitcoin-core-sv2/Cargo.toml @@ -21,6 +21,7 @@ async-channel = "1.5.1" # fetching from github enables synchronizing development workflows across sv2-apps and stratum repos # it MUST be changed before bitcoin-core-sv2 is published to crates.io # with the proper version of stratum-core being fetched from crates.io as well -stratum-core = { git = "https://github.com/stratum-mining/stratum", branch = "main" } +# TODO: points this to https://github.com/stratum-mining/stratum once https://github.com/stratum-mining/stratum/pull/2098 is merged +stratum-core = { git = "https://github.com/GitGab19/stratum", branch = "extranonce-allocation" } bitcoin-capnp-types = "0.1.0" diff --git a/integration-tests/Cargo.lock b/integration-tests/Cargo.lock index 166fd5f1c..7d17a935c 100644 --- a/integration-tests/Cargo.lock +++ b/integration-tests/Cargo.lock @@ -292,7 +292,7 @@ dependencies = [ [[package]] name = "binary_sv2" version = "5.0.1" -source = "git+https://github.com/stratum-mining/stratum?branch=main#ece2b035697ec35a8d1506ee1a321bccc9707b09" +source = "git+https://github.com/GitGab19/stratum?branch=extranonce-allocation#834b6cedd33239eead15db198b529e9450e96ccd" dependencies = [ "buffer_sv2 3.0.1", "derive_codec_sv2 1.1.2", @@ -462,7 +462,7 @@ dependencies = [ [[package]] name = "buffer_sv2" version = "3.0.1" -source = "git+https://github.com/stratum-mining/stratum?branch=main#ece2b035697ec35a8d1506ee1a321bccc9707b09" +source = "git+https://github.com/GitGab19/stratum?branch=extranonce-allocation#834b6cedd33239eead15db198b529e9450e96ccd" dependencies = [ "aes-gcm", ] @@ -589,11 +589,11 @@ dependencies = [ [[package]] name = "channels_sv2" version = "4.0.0" -source = "git+https://github.com/stratum-mining/stratum?branch=main#ece2b035697ec35a8d1506ee1a321bccc9707b09" +source = "git+https://github.com/GitGab19/stratum?branch=extranonce-allocation#834b6cedd33239eead15db198b529e9450e96ccd" dependencies = [ "binary_sv2 5.0.1", "bitcoin", - "mining_sv2 7.0.0", + "mining_sv2 8.0.0", "primitive-types", "template_distribution_sv2 5.0.0", "tracing", @@ -666,7 +666,7 @@ dependencies = [ [[package]] name = "codec_sv2" version = "5.0.0" -source = "git+https://github.com/stratum-mining/stratum?branch=main#ece2b035697ec35a8d1506ee1a321bccc9707b09" +source = "git+https://github.com/GitGab19/stratum?branch=extranonce-allocation#834b6cedd33239eead15db198b529e9450e96ccd" dependencies = [ "binary_sv2 5.0.1", "buffer_sv2 3.0.1", @@ -693,7 +693,7 @@ dependencies = [ [[package]] name = "common_messages_sv2" version = "7.0.0" -source = "git+https://github.com/stratum-mining/stratum?branch=main#ece2b035697ec35a8d1506ee1a321bccc9707b09" +source = "git+https://github.com/GitGab19/stratum?branch=extranonce-allocation#834b6cedd33239eead15db198b529e9450e96ccd" dependencies = [ "binary_sv2 5.0.1", ] @@ -927,7 +927,7 @@ dependencies = [ [[package]] name = "derive_codec_sv2" version = "1.1.2" -source = "git+https://github.com/stratum-mining/stratum?branch=main#ece2b035697ec35a8d1506ee1a321bccc9707b09" +source = "git+https://github.com/GitGab19/stratum?branch=extranonce-allocation#834b6cedd33239eead15db198b529e9450e96ccd" [[package]] name = "digest" @@ -1034,7 +1034,7 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "extensions_sv2" version = "0.1.0" -source = "git+https://github.com/stratum-mining/stratum?branch=main#ece2b035697ec35a8d1506ee1a321bccc9707b09" +source = "git+https://github.com/GitGab19/stratum?branch=extranonce-allocation#834b6cedd33239eead15db198b529e9450e96ccd" dependencies = [ "binary_sv2 5.0.1", ] @@ -1101,7 +1101,7 @@ dependencies = [ [[package]] name = "framing_sv2" version = "6.0.1" -source = "git+https://github.com/stratum-mining/stratum?branch=main#ece2b035697ec35a8d1506ee1a321bccc9707b09" +source = "git+https://github.com/GitGab19/stratum?branch=extranonce-allocation#834b6cedd33239eead15db198b529e9450e96ccd" dependencies = [ "binary_sv2 5.0.1", "buffer_sv2 3.0.1", @@ -1269,14 +1269,14 @@ dependencies = [ [[package]] name = "handlers_sv2" version = "0.2.2" -source = "git+https://github.com/stratum-mining/stratum?branch=main#ece2b035697ec35a8d1506ee1a321bccc9707b09" +source = "git+https://github.com/GitGab19/stratum?branch=extranonce-allocation#834b6cedd33239eead15db198b529e9450e96ccd" dependencies = [ "binary_sv2 5.0.1", "common_messages_sv2 7.0.0", "extensions_sv2", "framing_sv2 6.0.1", - "job_declaration_sv2 6.0.0", - "mining_sv2 7.0.0", + "job_declaration_sv2 7.0.0", + "mining_sv2 8.0.0", "parsers_sv2 0.2.2", "template_distribution_sv2 5.0.0", "trait-variant", @@ -1703,8 +1703,8 @@ dependencies = [ [[package]] name = "job_declaration_sv2" -version = "6.0.0" -source = "git+https://github.com/stratum-mining/stratum?branch=main#ece2b035697ec35a8d1506ee1a321bccc9707b09" +version = "7.0.0" +source = "git+https://github.com/GitGab19/stratum?branch=extranonce-allocation#834b6cedd33239eead15db198b529e9450e96ccd" dependencies = [ "binary_sv2 5.0.1", ] @@ -1863,8 +1863,8 @@ dependencies = [ [[package]] name = "mining_sv2" -version = "7.0.0" -source = "git+https://github.com/stratum-mining/stratum?branch=main#ece2b035697ec35a8d1506ee1a321bccc9707b09" +version = "8.0.0" +source = "git+https://github.com/GitGab19/stratum?branch=extranonce-allocation#834b6cedd33239eead15db198b529e9450e96ccd" dependencies = [ "binary_sv2 5.0.1", ] @@ -1957,7 +1957,7 @@ dependencies = [ [[package]] name = "noise_sv2" version = "1.4.2" -source = "git+https://github.com/stratum-mining/stratum?branch=main#ece2b035697ec35a8d1506ee1a321bccc9707b09" +source = "git+https://github.com/GitGab19/stratum?branch=extranonce-allocation#834b6cedd33239eead15db198b529e9450e96ccd" dependencies = [ "aes-gcm", "chacha20poly1305", @@ -2095,14 +2095,14 @@ dependencies = [ [[package]] name = "parsers_sv2" version = "0.2.2" -source = "git+https://github.com/stratum-mining/stratum?branch=main#ece2b035697ec35a8d1506ee1a321bccc9707b09" +source = "git+https://github.com/GitGab19/stratum?branch=extranonce-allocation#834b6cedd33239eead15db198b529e9450e96ccd" dependencies = [ "binary_sv2 5.0.1", "common_messages_sv2 7.0.0", "extensions_sv2", "framing_sv2 6.0.1", - "job_declaration_sv2 6.0.0", - "mining_sv2 7.0.0", + "job_declaration_sv2 7.0.0", + "mining_sv2 8.0.0", "template_distribution_sv2 5.0.0", ] @@ -2820,7 +2820,7 @@ dependencies = [ [[package]] name = "stratum-core" version = "0.2.1" -source = "git+https://github.com/stratum-mining/stratum?branch=main#ece2b035697ec35a8d1506ee1a321bccc9707b09" +source = "git+https://github.com/GitGab19/stratum?branch=extranonce-allocation#834b6cedd33239eead15db198b529e9450e96ccd" dependencies = [ "binary_sv2 5.0.1", "bitcoin", @@ -2831,8 +2831,8 @@ dependencies = [ "extensions_sv2", "framing_sv2 6.0.1", "handlers_sv2 0.2.2", - "job_declaration_sv2 6.0.0", - "mining_sv2 7.0.0", + "job_declaration_sv2 7.0.0", + "mining_sv2 8.0.0", "noise_sv2 1.4.2", "parsers_sv2 0.2.2", "stratum_translation", @@ -2843,12 +2843,12 @@ dependencies = [ [[package]] name = "stratum_translation" version = "0.1.3" -source = "git+https://github.com/stratum-mining/stratum?branch=main#ece2b035697ec35a8d1506ee1a321bccc9707b09" +source = "git+https://github.com/GitGab19/stratum?branch=extranonce-allocation#834b6cedd33239eead15db198b529e9450e96ccd" dependencies = [ "binary_sv2 5.0.1", "bitcoin", "channels_sv2 4.0.0", - "mining_sv2 7.0.0", + "mining_sv2 8.0.0", "sv1_api", "tracing", ] @@ -2868,7 +2868,7 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "sv1_api" version = "2.1.3" -source = "git+https://github.com/stratum-mining/stratum?branch=main#ece2b035697ec35a8d1506ee1a321bccc9707b09" +source = "git+https://github.com/GitGab19/stratum?branch=extranonce-allocation#834b6cedd33239eead15db198b529e9450e96ccd" dependencies = [ "binary_sv2 5.0.1", "bitcoin_hashes 0.3.2", @@ -2956,7 +2956,7 @@ dependencies = [ [[package]] name = "template_distribution_sv2" version = "5.0.0" -source = "git+https://github.com/stratum-mining/stratum?branch=main#ece2b035697ec35a8d1506ee1a321bccc9707b09" +source = "git+https://github.com/GitGab19/stratum?branch=extranonce-allocation#834b6cedd33239eead15db198b529e9450e96ccd" dependencies = [ "binary_sv2 5.0.1", ] diff --git a/miner-apps/Cargo.lock b/miner-apps/Cargo.lock index 281f3fbc6..153295c29 100644 --- a/miner-apps/Cargo.lock +++ b/miner-apps/Cargo.lock @@ -273,7 +273,7 @@ checksum = "32637268377fc7b10a8c6d51de3e7fba1ce5dd371a96e342b34e6078db558e7f" [[package]] name = "binary_sv2" version = "5.0.1" -source = "git+https://github.com/stratum-mining/stratum?branch=main#ece2b035697ec35a8d1506ee1a321bccc9707b09" +source = "git+https://github.com/GitGab19/stratum?branch=extranonce-allocation#834b6cedd33239eead15db198b529e9450e96ccd" dependencies = [ "buffer_sv2", "derive_codec_sv2", @@ -428,7 +428,7 @@ dependencies = [ [[package]] name = "buffer_sv2" version = "3.0.1" -source = "git+https://github.com/stratum-mining/stratum?branch=main#ece2b035697ec35a8d1506ee1a321bccc9707b09" +source = "git+https://github.com/GitGab19/stratum?branch=extranonce-allocation#834b6cedd33239eead15db198b529e9450e96ccd" dependencies = [ "aes-gcm", ] @@ -540,7 +540,7 @@ dependencies = [ [[package]] name = "channels_sv2" version = "4.0.0" -source = "git+https://github.com/stratum-mining/stratum?branch=main#ece2b035697ec35a8d1506ee1a321bccc9707b09" +source = "git+https://github.com/GitGab19/stratum?branch=extranonce-allocation#834b6cedd33239eead15db198b529e9450e96ccd" dependencies = [ "binary_sv2", "bitcoin", @@ -610,7 +610,7 @@ checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831" [[package]] name = "codec_sv2" version = "5.0.0" -source = "git+https://github.com/stratum-mining/stratum?branch=main#ece2b035697ec35a8d1506ee1a321bccc9707b09" +source = "git+https://github.com/GitGab19/stratum?branch=extranonce-allocation#834b6cedd33239eead15db198b529e9450e96ccd" dependencies = [ "binary_sv2", "buffer_sv2", @@ -638,7 +638,7 @@ dependencies = [ [[package]] name = "common_messages_sv2" version = "7.0.0" -source = "git+https://github.com/stratum-mining/stratum?branch=main#ece2b035697ec35a8d1506ee1a321bccc9707b09" +source = "git+https://github.com/GitGab19/stratum?branch=extranonce-allocation#834b6cedd33239eead15db198b529e9450e96ccd" dependencies = [ "binary_sv2", ] @@ -806,7 +806,7 @@ dependencies = [ [[package]] name = "derive_codec_sv2" version = "1.1.2" -source = "git+https://github.com/stratum-mining/stratum?branch=main#ece2b035697ec35a8d1506ee1a321bccc9707b09" +source = "git+https://github.com/GitGab19/stratum?branch=extranonce-allocation#834b6cedd33239eead15db198b529e9450e96ccd" [[package]] name = "digest" @@ -935,7 +935,7 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "extensions_sv2" version = "0.1.0" -source = "git+https://github.com/stratum-mining/stratum?branch=main#ece2b035697ec35a8d1506ee1a321bccc9707b09" +source = "git+https://github.com/GitGab19/stratum?branch=extranonce-allocation#834b6cedd33239eead15db198b529e9450e96ccd" dependencies = [ "binary_sv2", ] @@ -996,7 +996,7 @@ dependencies = [ [[package]] name = "framing_sv2" version = "6.0.1" -source = "git+https://github.com/stratum-mining/stratum?branch=main#ece2b035697ec35a8d1506ee1a321bccc9707b09" +source = "git+https://github.com/GitGab19/stratum?branch=extranonce-allocation#834b6cedd33239eead15db198b529e9450e96ccd" dependencies = [ "binary_sv2", "buffer_sv2", @@ -1131,7 +1131,7 @@ dependencies = [ [[package]] name = "handlers_sv2" version = "0.2.2" -source = "git+https://github.com/stratum-mining/stratum?branch=main#ece2b035697ec35a8d1506ee1a321bccc9707b09" +source = "git+https://github.com/GitGab19/stratum?branch=extranonce-allocation#834b6cedd33239eead15db198b529e9450e96ccd" dependencies = [ "binary_sv2", "common_messages_sv2", @@ -1534,8 +1534,8 @@ dependencies = [ [[package]] name = "job_declaration_sv2" -version = "6.0.0" -source = "git+https://github.com/stratum-mining/stratum?branch=main#ece2b035697ec35a8d1506ee1a321bccc9707b09" +version = "7.0.0" +source = "git+https://github.com/GitGab19/stratum?branch=extranonce-allocation#834b6cedd33239eead15db198b529e9450e96ccd" dependencies = [ "binary_sv2", ] @@ -1655,8 +1655,8 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "mining_sv2" -version = "7.0.0" -source = "git+https://github.com/stratum-mining/stratum?branch=main#ece2b035697ec35a8d1506ee1a321bccc9707b09" +version = "8.0.0" +source = "git+https://github.com/GitGab19/stratum?branch=extranonce-allocation#834b6cedd33239eead15db198b529e9450e96ccd" dependencies = [ "binary_sv2", ] @@ -1696,7 +1696,7 @@ dependencies = [ [[package]] name = "noise_sv2" version = "1.4.2" -source = "git+https://github.com/stratum-mining/stratum?branch=main#ece2b035697ec35a8d1506ee1a321bccc9707b09" +source = "git+https://github.com/GitGab19/stratum?branch=extranonce-allocation#834b6cedd33239eead15db198b529e9450e96ccd" dependencies = [ "aes-gcm", "chacha20poly1305", @@ -1820,7 +1820,7 @@ dependencies = [ [[package]] name = "parsers_sv2" version = "0.2.2" -source = "git+https://github.com/stratum-mining/stratum?branch=main#ece2b035697ec35a8d1506ee1a321bccc9707b09" +source = "git+https://github.com/GitGab19/stratum?branch=extranonce-allocation#834b6cedd33239eead15db198b529e9450e96ccd" dependencies = [ "binary_sv2", "common_messages_sv2", @@ -2462,7 +2462,7 @@ dependencies = [ [[package]] name = "stratum-core" version = "0.2.1" -source = "git+https://github.com/stratum-mining/stratum?branch=main#ece2b035697ec35a8d1506ee1a321bccc9707b09" +source = "git+https://github.com/GitGab19/stratum?branch=extranonce-allocation#834b6cedd33239eead15db198b529e9450e96ccd" dependencies = [ "binary_sv2", "bitcoin", @@ -2485,7 +2485,7 @@ dependencies = [ [[package]] name = "stratum_translation" version = "0.1.3" -source = "git+https://github.com/stratum-mining/stratum?branch=main#ece2b035697ec35a8d1506ee1a321bccc9707b09" +source = "git+https://github.com/GitGab19/stratum?branch=extranonce-allocation#834b6cedd33239eead15db198b529e9450e96ccd" dependencies = [ "binary_sv2", "bitcoin", @@ -2510,7 +2510,7 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "sv1_api" version = "2.1.3" -source = "git+https://github.com/stratum-mining/stratum?branch=main#ece2b035697ec35a8d1506ee1a321bccc9707b09" +source = "git+https://github.com/GitGab19/stratum?branch=extranonce-allocation#834b6cedd33239eead15db198b529e9450e96ccd" dependencies = [ "binary_sv2", "bitcoin_hashes 0.3.2", @@ -2557,7 +2557,7 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "template_distribution_sv2" version = "5.0.0" -source = "git+https://github.com/stratum-mining/stratum?branch=main#ece2b035697ec35a8d1506ee1a321bccc9707b09" +source = "git+https://github.com/GitGab19/stratum?branch=extranonce-allocation#834b6cedd33239eead15db198b529e9450e96ccd" dependencies = [ "binary_sv2", ] diff --git a/miner-apps/jd-client/src/lib/channel_manager/downstream_message_handler.rs b/miner-apps/jd-client/src/lib/channel_manager/downstream_message_handler.rs index 1619eb44d..4225eb127 100644 --- a/miner-apps/jd-client/src/lib/channel_manager/downstream_message_handler.rs +++ b/miner-apps/jd-client/src/lib/channel_manager/downstream_message_handler.rs @@ -205,6 +205,15 @@ impl HandleMiningMessagesFromClientAsync for ChannelManager { channel_manager_data .vardiff .remove(&(downstream_id, msg.channel_id).into()); + + if let Some(prefix_id) = channel_manager_data + .channel_to_local_prefix_id + .remove(&(downstream_id, msg.channel_id)) + { + if let Some(ref mut allocator) = channel_manager_data.extranonce_allocator { + allocator.free(prefix_id); + } + } Ok(()) }) } @@ -301,23 +310,31 @@ impl HandleMiningMessagesFromClientAsync for ChannelManager { let standard_channel_id = data.channel_id_factory.fetch_add(1, Ordering::Relaxed); - let extranonce_prefix = match channel_manager_data - .extranonce_prefix_factory_standard - .next_prefix_standard() - { + let allocator = channel_manager_data + .extranonce_allocator + .as_mut() + .ok_or_else(|| { + JDCError::shutdown(JDCErrorKind::ExtranonceAllocatorNotInitialized) + })?; + let prefix = match allocator.allocate_standard() { Ok(p) => p, Err(e) => { error!(?e, "Failed to get extranonce prefix"); return Err(JDCError::shutdown(e)); } }; + let extranonce_prefix = prefix.as_bytes().to_vec(); + channel_manager_data.channel_to_local_prefix_id.insert( + (downstream_id, standard_channel_id), + prefix.local_prefix_id(), + ); let job_store = DefaultJobStore::new(); let mut standard_channel = match StandardChannel::new_for_job_declaration_client( standard_channel_id, user_identity.to_string(), - extranonce_prefix.to_vec(), + extranonce_prefix, requested_max_target, nominal_hash_rate, self.share_batch_size, @@ -534,9 +551,14 @@ impl HandleMiningMessagesFromClientAsync for ChannelManager { let extended_channel_id = data.channel_id_factory.fetch_add(1, Ordering::Relaxed); - let extranonce_prefix = match channel_manager_data - .extranonce_prefix_factory_extended - .next_prefix_extended(requested_min_rollable_extranonce_size.into()) + let allocator = channel_manager_data + .extranonce_allocator + .as_mut() + .ok_or_else(|| { + JDCError::shutdown(JDCErrorKind::ExtranonceAllocatorNotInitialized) + })?; + let prefix = match allocator + .allocate_extended(requested_min_rollable_extranonce_size.into()) { Ok(p) => p, Err(e) => { @@ -548,6 +570,11 @@ impl HandleMiningMessagesFromClientAsync for ChannelManager { .into()]); } }; + let extranonce_prefix = prefix.as_bytes().to_vec(); + channel_manager_data.channel_to_local_prefix_id.insert( + (downstream_id, extended_channel_id), + prefix.local_prefix_id(), + ); let job_store = DefaultJobStore::new(); @@ -559,13 +586,13 @@ impl HandleMiningMessagesFromClientAsync for ChannelManager { // upstream channel is not present (solo mining mode) let rollable_extranonce_size = - full_extranonce_size - extranonce_prefix.clone().to_vec().len(); + full_extranonce_size - extranonce_prefix.len(); let mut extended_channel = match ExtendedChannel::new_for_job_declaration_client( extended_channel_id, user_identity.to_string(), - extranonce_prefix.into(), + extranonce_prefix, requested_max_target, nominal_hash_rate, true, diff --git a/miner-apps/jd-client/src/lib/channel_manager/mod.rs b/miner-apps/jd-client/src/lib/channel_manager/mod.rs index d3c25c565..01cf08f88 100644 --- a/miner-apps/jd-client/src/lib/channel_manager/mod.rs +++ b/miner-apps/jd-client/src/lib/channel_manager/mod.rs @@ -19,6 +19,7 @@ use stratum_apps::{ bitcoin::{consensus, Amount, Target, TxOut}, channels_sv2::{ client::extended::ExtendedChannel, + extranonce_manager::ExtranonceAllocator, outputs::deserialize_outputs, server::{ group::GroupChannel, @@ -39,10 +40,7 @@ use stratum_apps::{ job_declaration_sv2::{ AllocateMiningJobToken, AllocateMiningJobTokenSuccess, DeclareMiningJob, }, - mining_sv2::{ - ExtendedExtranonce, OpenExtendedMiningChannel, SetCustomMiningJob, SetTarget, - UpdateChannel, - }, + mining_sv2::{OpenExtendedMiningChannel, SetCustomMiningJob, SetTarget, UpdateChannel}, parsers_sv2::{AnyMessage, JobDeclaration, Mining, TemplateDistribution, Tlv}, template_distribution_sv2::{NewTemplate, SetNewPrevHash as SetNewPrevHashTdp}, }, @@ -72,17 +70,23 @@ mod jd_message_handler; mod template_message_handler; mod upstream_message_handler; -pub const JDC_SEARCH_SPACE_BYTES: usize = 4; -// These are only used for solo-mining, very similar to pool -const CLIENT_SEARCH_SPACE_BYTES: usize = 16; -pub const FULL_EXTRANONCE_SIZE: usize = JDC_SEARCH_SPACE_BYTES + CLIENT_SEARCH_SPACE_BYTES; -// some extra bytes guaranteed for standard channels -// allows for 65,536 standard channels on worst case -// where worst-case means: OpenExtendedMiningChannel.Success.extranonce_size == -// OpenExtendedMiningChannel.min_extranonce_size -const STANDARD_CHANNEL_ALLOCATION_BYTES: usize = 2; -// for OpenExtendedMiningChannel.min_extranonce_size -const MIN_EXTRANONCE_SIZE: usize = JDC_SEARCH_SPACE_BYTES + STANDARD_CHANNEL_ALLOCATION_BYTES; +/// Number of bytes JDC uses for its local extranonce prefix allocation. +/// 2 bytes = 65,536 maximum downstream channels. +pub const JDC_ALLOCATION_BYTES: usize = 2; + +/// Maximum number of downstream channels JDC can allocate prefixes for. +/// Must be <= 2^(JDC_ALLOCATION_BYTES * 8) +pub const JDC_MAX_CHANNELS: usize = 65_536; + +/// Number of bytes JDC leaves for clients to use (their allocation + extra rollable space). +const CLIENT_SEARCH_SPACE_BYTES: usize = 12; + +/// Extranonce size JDC requests to upstream +pub const JDC_UPSTREAM_EXTRANONCE_SIZE: usize = JDC_ALLOCATION_BYTES + CLIENT_SEARCH_SPACE_BYTES; + +/// Full extranonce size when no upstream is present (solo-mining mode). +/// Same as JDC_UPSTREAM_EXTRANONCE_SIZE since we use the same client space in both modes. +pub const FULL_EXTRANONCE_SIZE: usize = JDC_UPSTREAM_EXTRANONCE_SIZE; /// A `DeclaredJob` encapsulates all the relevant data associated with a single /// job declaration, including its template, optional messages, coinbase output, @@ -114,12 +118,12 @@ pub struct ChannelManagerData { // Mapping of `downstream_id` → `Downstream` object, // used by the channel manager to locate and interact with downstream clients. pub downstream: HashMap, - // Extranonce prefix factory for **extended downstream channels**. - // Each new extended downstream receives a unique extranonce prefix. - extranonce_prefix_factory_extended: ExtendedExtranonce, - // Extranonce prefix factory for **standard downstream channels**. - // Each new standard downstream receives a unique extranonce prefix. - extranonce_prefix_factory_standard: ExtendedExtranonce, + // Extranonce allocator for both standard and extended downstream channels. + // Each new downstream channel receives a unique extranonce prefix. + // `None` until upstream channel is established (or initialized for solo mining). + pub(crate) extranonce_allocator: Option, + // Mapping of `(downstream_id, channel_id)` → `local_prefix_id` for freeing extranonces. + pub(crate) channel_to_local_prefix_id: HashMap<(DownstreamId, ChannelId), usize>, // Factory that generates **monotonically increasing request IDs** // for messages sent from the JDC. request_id_factory: AtomicU32, @@ -179,23 +183,13 @@ impl ChannelManagerData { self.template_id_to_upstream_job_id.clear(); self.downstream_channel_id_and_job_id_to_template_id.clear(); self.pending_downstream_requests.clear(); + self.channel_to_local_prefix_id.clear(); self.downstream_id_factory = AtomicUsize::new(0); self.request_id_factory = AtomicU32::new(0); - let (range_0, range_1, range_2) = { - let range_1 = 0..JDC_SEARCH_SPACE_BYTES; - ( - 0..range_1.start, - range_1.clone(), - range_1.end..FULL_EXTRANONCE_SIZE, - ) - }; - self.extranonce_prefix_factory_extended = - ExtendedExtranonce::new(range_0.clone(), range_1.clone(), range_2.clone(), None) - .expect("valid ranges"); - self.extranonce_prefix_factory_standard = - ExtendedExtranonce::new(range_0, range_1, range_2, None).expect("valid ranges"); + // Reset allocator - will be re-created from upstream or for solo mining + self.extranonce_allocator = None; self.allocate_tokens = None; self.upstream_channel = None; @@ -280,27 +274,12 @@ impl ChannelManager { supported_extensions: Vec, required_extensions: Vec, ) -> JDCResult { - let (range_0, range_1, range_2) = { - let range_1 = 0..JDC_SEARCH_SPACE_BYTES; - ( - 0..range_1.start, - range_1.clone(), - range_1.end..FULL_EXTRANONCE_SIZE, - ) - }; - - let make_extranonce_factory = || { - ExtendedExtranonce::new(range_0.clone(), range_1.clone(), range_2.clone(), None) - .expect("Failed to create ExtendedExtranonce with valid ranges") - }; - - let extranonce_prefix_factory_extended = make_extranonce_factory(); - let extranonce_prefix_factory_standard = make_extranonce_factory(); - + // Allocator is None initially - will be created from upstream response + // or initialized for solo mining mode when needed. let channel_manager_data = Arc::new(Mutex::new(ChannelManagerData { downstream: HashMap::new(), - extranonce_prefix_factory_extended, - extranonce_prefix_factory_standard, + extranonce_allocator: None, + channel_to_local_prefix_id: HashMap::new(), downstream_id_factory: AtomicUsize::new(0), request_id_factory: AtomicU32::new(0), sequence_number_factory: AtomicU32::new(1), @@ -678,6 +657,21 @@ impl ChannelManager { cm_data .vardiff .retain(|key, _| key.downstream_id != downstream_id); + // Free extranonce prefixes for all channels belonging to this downstream + if let Some(ref mut allocator) = cm_data.extranonce_allocator { + let prefix_ids_to_free: Vec = cm_data + .channel_to_local_prefix_id + .iter() + .filter(|((ds_id, _), _)| *ds_id == downstream_id) + .map(|(_, prefix_id)| *prefix_id) + .collect(); + for prefix_id in prefix_ids_to_free { + allocator.free(prefix_id); + } + } + cm_data + .channel_to_local_prefix_id + .retain(|(ds_id, _), _| *ds_id != downstream_id); }); Ok(()) } @@ -809,7 +803,10 @@ impl ChannelManager { .try_into() .map_err(JDCError::shutdown)?; upstream_message.request_id = 1; - upstream_message.min_extranonce_size += MIN_EXTRANONCE_SIZE as u16; + // Always request our fixed extranonce size, ignoring downstream's + // request + upstream_message.min_extranonce_size = + JDC_UPSTREAM_EXTRANONCE_SIZE as u16; let upstream_message = Mining::OpenExtendedMiningChannel(upstream_message) .into_static(); @@ -869,7 +866,7 @@ impl ChannelManager { request_id: 1, nominal_hash_rate: downstream_channel_request.nominal_hash_rate, max_target: downstream_channel_request.max_target, - min_extranonce_size: MIN_EXTRANONCE_SIZE as u16, + min_extranonce_size: JDC_UPSTREAM_EXTRANONCE_SIZE as u16, }; let message = diff --git a/miner-apps/jd-client/src/lib/channel_manager/upstream_message_handler.rs b/miner-apps/jd-client/src/lib/channel_manager/upstream_message_handler.rs index e48467341..048528f47 100644 --- a/miner-apps/jd-client/src/lib/channel_manager/upstream_message_handler.rs +++ b/miner-apps/jd-client/src/lib/channel_manager/upstream_message_handler.rs @@ -4,8 +4,8 @@ use stratum_apps::{ stratum_core::{ bitcoin::Target, channels_sv2::{ - client::extended::ExtendedChannel, outputs::deserialize_outputs, - server::jobs::factory::JobFactory, + client::extended::ExtendedChannel, extranonce_manager::ExtranonceAllocator, + outputs::deserialize_outputs, server::jobs::factory::JobFactory, MAX_EXTRANONCE_LEN, }, handlers_sv2::{HandleMiningMessagesFromServerAsync, SupportedChannelTypes}, mining_sv2::*, @@ -19,7 +19,7 @@ use tracing::{debug, error, info, warn}; use crate::{ channel_manager::{ downstream_message_handler::RouteMessageTo, ChannelManager, DeclaredJob, - JDC_SEARCH_SPACE_BYTES, MIN_EXTRANONCE_SIZE, STANDARD_CHANNEL_ALLOCATION_BYTES, + JDC_ALLOCATION_BYTES, JDC_MAX_CHANNELS, JDC_UPSTREAM_EXTRANONCE_SIZE, }, error::{self, JDCError, JDCErrorKind}, jd_mode::{get_jd_mode, JdMode}, @@ -95,10 +95,12 @@ impl HandleMiningMessagesFromServerAsync for ChannelManager { let outputs = deserialize_outputs(coinbase_outputs) .map_err(|_| JDCError::shutdown(JDCErrorKind::DeclaredJobHasBadCoinbaseOutputs))?; - if (msg.extranonce_size as usize) < MIN_EXTRANONCE_SIZE { + // JDC always requests JDC_UPSTREAM_EXTRANONCE_SIZE from upstream. + // If pool grants less, we can't support the expected downstream allocation. + if (msg.extranonce_size as usize) < JDC_UPSTREAM_EXTRANONCE_SIZE { warn!( - "Pool granted extranonce_size={} but JDC requires at least {} (JDC_SEARCH_SPACE_BYTES={} + STANDARD_CHANNEL_ALLOCATION_BYTES={}), preparing fallback.", - msg.extranonce_size, MIN_EXTRANONCE_SIZE, JDC_SEARCH_SPACE_BYTES, STANDARD_CHANNEL_ALLOCATION_BYTES + "Pool granted extranonce_size={} but JDC requires {} bytes, preparing fallback.", + msg.extranonce_size, JDC_UPSTREAM_EXTRANONCE_SIZE ); return Err(JDCError::fallback(JDCErrorKind::ExtranonceSizeTooSmall)); } @@ -115,11 +117,7 @@ impl HandleMiningMessagesFromServerAsync for ChannelManager { let hashrate = pending_request.hashrate(); let prefix_len = msg.extranonce_prefix.len(); - let total_len = prefix_len + msg.extranonce_size as usize; - let range_0 = 0..prefix_len; - let range_1 = prefix_len..prefix_len + JDC_SEARCH_SPACE_BYTES; - let range_2 = prefix_len + JDC_SEARCH_SPACE_BYTES..total_len; debug!( prefix_len, @@ -128,15 +126,15 @@ impl HandleMiningMessagesFromServerAsync for ChannelManager { "Calculated extranonce ranges" ); - let extranonces = match ExtendedExtranonce::from_upstream_extranonce( - msg.extranonce_prefix.clone().into(), - range_0, - range_1, - range_2, + let allocator = match ExtranonceAllocator::from_upstream( + msg.extranonce_prefix.to_vec(), + JDC_ALLOCATION_BYTES, + total_len, + JDC_MAX_CHANNELS, ) { - Ok(e) => e, + Ok(a) => a, Err(e) => { - warn!("Failed to build extranonce factory: {e:?}"); + warn!("Failed to build extranonce allocator: {e:?}"); self.upstream_state.set(UpstreamState::NoChannel); let close_channel = create_close_channel_msg(msg.channel_id, "downstream not available"); @@ -208,8 +206,7 @@ impl HandleMiningMessagesFromServerAsync for ChannelManager { let full_extranonce_size = extended_channel.get_full_extranonce_size(); - data.extranonce_prefix_factory_extended = extranonces.clone(); - data.extranonce_prefix_factory_standard = extranonces; + data.extranonce_allocator = Some(allocator); data.upstream_channel = Some(extended_channel); data.job_factory = Some(job_factory); self.upstream_state.set(UpstreamState::Connected); @@ -374,15 +371,13 @@ impl HandleMiningMessagesFromServerAsync for ChannelManager { return Err(JDCError::fallback(JDCErrorKind::ExtranonceSizeTooLarge)); } - let range_0 = 0..new_prefix_len; - let range_1 = new_prefix_len..new_prefix_len + JDC_SEARCH_SPACE_BYTES; - let range_2 = new_prefix_len + JDC_SEARCH_SPACE_BYTES..full_extranonce_size; - - if range_2.is_empty() { + // JDC needs JDC_UPSTREAM_EXTRANONCE_SIZE to support the typical downstream + // stack + if (rollable_extranonce_size as usize) < JDC_UPSTREAM_EXTRANONCE_SIZE { warn!( - "SetExtranoncePrefix leaves no space for standard channel allocation \ - (new_prefix_len={}, rollable_extranonce_size={}, JDC_SEARCH_SPACE_BYTES={})", - new_prefix_len, rollable_extranonce_size, JDC_SEARCH_SPACE_BYTES + "SetExtranoncePrefix leaves insufficient space for JDC allocation \ + (rollable_extranonce_size={}, JDC_UPSTREAM_EXTRANONCE_SIZE={})", + rollable_extranonce_size, JDC_UPSTREAM_EXTRANONCE_SIZE ); return Err(JDCError::fallback(JDCErrorKind::ExtranonceSizeTooSmall)); } @@ -393,43 +388,50 @@ impl HandleMiningMessagesFromServerAsync for ChannelManager { full_extranonce_size, "Calculated extranonce ranges" ); - let extranonces = match ExtendedExtranonce::from_upstream_extranonce( - msg.extranonce_prefix.clone().into(), - range_0, - range_1, - range_2, + + let allocator = match ExtranonceAllocator::from_upstream( + msg.extranonce_prefix.to_vec(), + JDC_ALLOCATION_BYTES, + full_extranonce_size, + JDC_MAX_CHANNELS, ) { - Ok(e) => e, + Ok(a) => a, Err(e) => { - warn!("Failed to build extranonce factory: {e:?}"); + warn!("Failed to build extranonce allocator: {e:?}"); return Err(JDCError::fallback(e)); } }; - channel_manager_data.extranonce_prefix_factory_extended = - extranonces.clone(); - channel_manager_data.extranonce_prefix_factory_standard = extranonces; + // Clear old prefix id mappings since we're reassigning + channel_manager_data.channel_to_local_prefix_id.clear(); + channel_manager_data.extranonce_allocator = Some(allocator); for (downstream_id, downstream) in channel_manager_data.downstream.iter_mut() { downstream.downstream_data.super_safe_lock(|data| { + let allocator = + channel_manager_data.extranonce_allocator.as_mut().unwrap(); for (channel_id, standard_channel) in data.standard_channels.iter_mut() { - match channel_manager_data - .extranonce_prefix_factory_standard - .next_prefix_standard() - { + match allocator.allocate_standard() { Ok(prefix) => { + let prefix_bytes = prefix.as_bytes().to_vec(); + channel_manager_data.channel_to_local_prefix_id.insert( + (*downstream_id, *channel_id), + prefix.local_prefix_id(), + ); standard_channel - .set_extranonce_prefix(prefix.clone().to_vec()) + .set_extranonce_prefix(prefix_bytes.clone()) .expect("Prefix will always be less than 32"); messages_results.push(Ok(( *downstream_id, Mining::SetExtranoncePrefix(SetExtranoncePrefix { channel_id: *channel_id, - extranonce_prefix: prefix.into(), + extranonce_prefix: prefix_bytes + .try_into() + .expect("valid prefix"), }), ) .into())); @@ -443,22 +445,26 @@ impl HandleMiningMessagesFromServerAsync for ChannelManager { for (channel_id, extended_channel) in data.extended_channels.iter_mut() { - match channel_manager_data - .extranonce_prefix_factory_extended - .next_prefix_extended( - extended_channel.get_rollable_extranonce_size() - as usize, - ) { + match allocator.allocate_extended( + extended_channel.get_rollable_extranonce_size() as usize, + ) { Ok(prefix) => { + let prefix_bytes = prefix.as_bytes().to_vec(); + channel_manager_data.channel_to_local_prefix_id.insert( + (*downstream_id, *channel_id), + prefix.local_prefix_id(), + ); extended_channel - .set_extranonce_prefix(prefix.clone().to_vec()) + .set_extranonce_prefix(prefix_bytes.clone()) .expect("Prefix will always be less than 32"); messages_results.push(Ok(( *downstream_id, Mining::SetExtranoncePrefix(SetExtranoncePrefix { channel_id: *channel_id, - extranonce_prefix: prefix.into(), + extranonce_prefix: prefix_bytes + .try_into() + .expect("valid prefix"), }), ) .into())); diff --git a/miner-apps/jd-client/src/lib/error.rs b/miner-apps/jd-client/src/lib/error.rs index f05b6fc2c..ecd4cfcef 100644 --- a/miner-apps/jd-client/src/lib/error.rs +++ b/miner-apps/jd-client/src/lib/error.rs @@ -22,6 +22,7 @@ use stratum_apps::{ binary_sv2, bitcoin, channels_sv2::{ client::error::ExtendedChannelError as ExtendedChannelClientError, + extranonce_manager::ExtranonceAllocatorError, server::error::{ ExtendedChannelError as ExtendedChannelServerError, GroupChannelError, StandardChannelError, @@ -29,7 +30,6 @@ use stratum_apps::{ }, framing_sv2, handlers_sv2::HandlerErrorType, - mining_sv2::ExtendedExtranonceError, noise_sv2, parsers_sv2::ParserError, }, @@ -138,7 +138,7 @@ where pub enum ChannelSv2Error { ExtendedChannelClientSide(ExtendedChannelClientError), ExtendedChannelServerSide(ExtendedChannelServerError), - ExtranonceError(ExtendedExtranonceError), + ExtranonceError(ExtranonceAllocatorError), StandardChannelServerSide(StandardChannelError), GroupChannelServerSide(GroupChannelError), } @@ -214,8 +214,10 @@ pub enum JDCErrorKind { FailedToCreateGroupChannel(GroupChannelError), ///Channel Errors ChannelSv2(ChannelSv2Error), - /// Extranonce prefix error - ExtranoncePrefixFactoryError(ExtendedExtranonceError), + /// Extranonce allocator error + ExtranonceAllocatorError(ExtranonceAllocatorError), + /// Extranonce allocator not initialized + ExtranonceAllocatorNotInitialized, /// Invalid unsupported extensions sequence (exceeds maximum length) InvalidUnsupportedExtensionsSequence, /// Invalid required extensions sequence (exceeds maximum length) @@ -338,8 +340,8 @@ impl fmt::Display for JDCErrorKind { FailedToCreateGroupChannel(ref e) => { write!(f, "Failed to create group channel: {e:?}") } - ExtranoncePrefixFactoryError(e) => { - write!(f, "Failed to create ExtranoncePrefixFactory: {e:?}") + ExtranonceAllocatorError(e) => { + write!(f, "Extranonce allocator error: {e:?}") } ChannelSv2(channel_error) => { write!(f, "Channel error: {channel_error:?}") @@ -396,6 +398,9 @@ impl fmt::Display for JDCErrorKind { CustomJobError => write!(f, "Custom job not acknowledged"), CouldNotInitiateSystem => write!(f, "Could not initiate subsystem"), InvalidKey => write!(f, "Invalid key used during noise handshake"), + ExtranonceAllocatorNotInitialized => { + write!(f, "Extranonce allocator not initialized") + } } } } @@ -494,8 +499,8 @@ impl From for JDCErrorKind { } } -impl From for JDCErrorKind { - fn from(value: ExtendedExtranonceError) -> Self { +impl From for JDCErrorKind { + fn from(value: ExtranonceAllocatorError) -> Self { JDCErrorKind::ChannelSv2(ChannelSv2Error::ExtranonceError(value)) } } diff --git a/miner-apps/translator/src/lib/sv1/sv1_server/sv1_server.rs b/miner-apps/translator/src/lib/sv1/sv1_server/sv1_server.rs index f1af8cd2e..3c910d6cf 100644 --- a/miner-apps/translator/src/lib/sv1/sv1_server/sv1_server.rs +++ b/miner-apps/translator/src/lib/sv1/sv1_server/sv1_server.rs @@ -799,22 +799,22 @@ impl Sv1Server { let channel_id = downstream.downstream_data.super_safe_lock(|d| d.channel_id); if let Some(channel_id) = channel_id { - if !self.config.aggregate_channels { - info!("Sending CloseChannel message: {channel_id} for downstream: {downstream_id}"); - let reason_code = - Str0255::try_from("downstream disconnected".to_string()).unwrap(); - _ = self - .sv1_server_channel_state - .channel_manager_sender - .send(( - Mining::CloseChannel(CloseChannel { - channel_id, - reason_code, - }), - None, - )) - .await; - } + // Always send CloseChannel to ChannelManager so it can free the extranonce + // prefix. In aggregated mode, ChannelManager will free the prefix but won't + // forward CloseChannel to upstream. + info!("Sending CloseChannel message: {channel_id} for downstream: {downstream_id}"); + let reason_code = Str0255::try_from("downstream disconnected".to_string()).unwrap(); + _ = self + .sv1_server_channel_state + .channel_manager_sender + .send(( + Mining::CloseChannel(CloseChannel { + channel_id, + reason_code, + }), + None, + )) + .await; } } } diff --git a/miner-apps/translator/src/lib/sv2/channel_manager/channel_manager.rs b/miner-apps/translator/src/lib/sv2/channel_manager/channel_manager.rs index 50b149812..ca59220e3 100644 --- a/miner-apps/translator/src/lib/sv2/channel_manager/channel_manager.rs +++ b/miner-apps/translator/src/lib/sv2/channel_manager/channel_manager.rs @@ -12,12 +12,15 @@ use stratum_apps::{ custom_mutex::Mutex, fallback_coordinator::FallbackCoordinator, stratum_core::{ - channels_sv2::client::{extended::ExtendedChannel, group::GroupChannel}, + channels_sv2::{ + client::{extended::ExtendedChannel, group::GroupChannel}, + extranonce_manager::ExtranonceAllocator, + }, codec_sv2::StandardSv2Frame, extensions_sv2::{EXTENSION_TYPE_WORKER_HASHRATE_TRACKING, TLV_FIELD_TYPE_USER_IDENTITY}, framing_sv2, handlers_sv2::{HandleExtensionsFromServerAsync, HandleMiningMessagesFromServerAsync}, - mining_sv2::{ExtendedExtranonce, OpenExtendedMiningChannelSuccess}, + mining_sv2::OpenExtendedMiningChannelSuccess, parsers_sv2::{AnyMessage, Mining, Tlv, TlvList}, }, task_manager::TaskManager, @@ -30,10 +33,12 @@ use stratum_apps::{ use tokio_util::sync::CancellationToken; use tracing::{debug, error, info, warn}; -/// Extra bytes allocated for translator search space in aggregated mode. -/// This allows the translator to manage multiple downstream connections -/// by allocating unique extranonce prefixes to each downstream. -const AGGREGATED_MODE_TRANSLATOR_SEARCH_SPACE_BYTES: usize = 4; +/// Number of bytes the translator uses for its local extranonce prefix allocation +/// in aggregated mode (unique prefix per downstream). +pub const TPROXY_ALLOCATION_BYTES: usize = 4; + +/// Maximum number of downstream channels the translator can allocate prefixes for. +pub const TPROXY_MAX_CHANNELS: usize = 65_536; /// Manages SV2 channels and message routing between upstream and downstream. /// @@ -73,8 +78,10 @@ pub struct ChannelManager { pub share_sequence_counters: Arc>, /// Extensions that have been successfully negotiated with the upstream server pub negotiated_extensions: Arc>>, - /// Extranonce factories containing per channel extranonces - pub extranonce_factories: Arc>, + /// Extranonce allocators containing per channel extranonce management + pub extranonce_allocators: Arc>, + /// Mapping of channel_id → local_prefix_id for freeing extranonces on channel close + pub channel_to_local_prefix_id: Arc>, } #[cfg_attr(not(test), hotpath::measure_all)] @@ -121,7 +128,8 @@ impl ChannelManager { group_channels: Arc::new(DashMap::new()), share_sequence_counters: Arc::new(DashMap::new()), negotiated_extensions: Arc::new(Mutex::new(Vec::new())), - extranonce_factories: Arc::new(DashMap::new()), + extranonce_allocators: Arc::new(DashMap::new()), + channel_to_local_prefix_id: Arc::new(DashMap::new()), } } @@ -172,7 +180,8 @@ impl ChannelManager { self.group_channels.clear(); self.share_sequence_counters.clear(); self.negotiated_extensions.super_safe_lock(|data| data.clear()); - self.extranonce_factories.clear(); + self.extranonce_allocators.clear(); + self.channel_to_local_prefix_id.clear(); break; } res = self.clone().handle_upstream_frame() => { @@ -301,18 +310,22 @@ impl ChannelManager { .get(&AGGREGATED_CHANNEL_ID) .map(|ch| *ch.get_target()) .unwrap(); - let new_extranonce_prefix = self - .extranonce_factories + let alloc_result = self + .extranonce_allocators .get_mut(&AGGREGATED_CHANNEL_ID) - .unwrap() - .next_prefix_extended(open_channel_msg.min_extranonce_size.into()) - .ok(); + .and_then(|mut allocator| { + allocator + .allocate_extended(open_channel_msg.min_extranonce_size.into()) + .ok() + }); let new_extranonce_size = self - .extranonce_factories - .get_mut(&AGGREGATED_CHANNEL_ID) - .unwrap() - .get_range2_len(); - if let Some(new_extranonce_prefix) = new_extranonce_prefix { + .extranonce_allocators + .get(&AGGREGATED_CHANNEL_ID) + .map(|allocator| allocator.rollable_extranonce_size()) + .unwrap_or(0); + if let Some(prefix) = alloc_result { + let new_extranonce_prefix = prefix.as_bytes().to_vec(); + let local_prefix_id = prefix.local_prefix_id(); if new_extranonce_size >= open_channel_msg.min_extranonce_size as usize { // Find max channel ID, excluding AGGREGATED_CHANNEL_ID (u32::MAX) @@ -323,14 +336,12 @@ impl ChannelManager { .filter(|x| *x.key() != AGGREGATED_CHANNEL_ID) .fold(0, |acc, x| std::cmp::max(acc, *x.key())); let next_channel_id = channel_id + 1; + self.channel_to_local_prefix_id + .insert(next_channel_id, local_prefix_id); let new_downstream_extended_channel = ExtendedChannel::new( next_channel_id, user_identity.clone(), - new_extranonce_prefix - .clone() - .into_b032() - .into_static() - .to_vec(), + new_extranonce_prefix.clone(), target, hashrate, true, @@ -344,7 +355,10 @@ impl ChannelManager { channel_id: next_channel_id, target: target.to_le_bytes().into(), extranonce_size: new_extranonce_size as u16, - extranonce_prefix: new_extranonce_prefix.clone().into(), + extranonce_prefix: new_extranonce_prefix + .clone() + .try_into() + .expect("valid prefix"), group_channel_id: 0, /* use a dummy value, this shouldn't * matter for the Sv1 server */ }, @@ -445,9 +459,9 @@ impl ChannelManager { user_identity.as_bytes().to_vec().try_into().unwrap(); } } - // In aggregated mode, add extra bytes for translator search space allocation + // In aggregated mode, add extra bytes for translator's own prefix allocation let upstream_min_extranonce_size = if is_aggregated() { - min_extranonce_size + AGGREGATED_MODE_TRANSLATOR_SEARCH_SPACE_BYTES + min_extranonce_size + TPROXY_ALLOCATION_BYTES } else { min_extranonce_size }; @@ -513,16 +527,17 @@ impl ChannelManager { .extended_channels .get(&m.channel_id) .map(|channel| channel.get_extranonce_prefix().clone()); - // Get the length of the upstream prefix (range0) - let range0_len = self - .extranonce_factories + // Get the length of the upstream prefix + let upstream_prefix_len = self + .extranonce_allocators .get(&AGGREGATED_CHANNEL_ID) - .unwrap() - .get_range0_len(); + .map(|allocator| allocator.upstream_prefix_len()) + .unwrap_or(0); if let Some(downstream_extranonce_prefix) = downstream_extranonce_prefix { - // Skip the upstream prefix (range0) and take the remaining + // Skip the upstream prefix and take the remaining // bytes (translator proxy prefix) - let translator_prefix = &downstream_extranonce_prefix[range0_len..]; + let translator_prefix = + &downstream_extranonce_prefix[upstream_prefix_len..]; // Create new extranonce: translator proxy prefix + miner's // extranonce let mut new_extranonce = translator_prefix.to_vec(); @@ -540,21 +555,22 @@ impl ChannelManager { // counter m.sequence_number = self.next_share_sequence_number(m.channel_id); - // Check if we have a per-channel factory for extranonce adjustment - let channel_factory = self.extranonce_factories.get(&m.channel_id); + // Check if we have a per-channel allocator for extranonce adjustment + let channel_allocator = self.extranonce_allocators.get(&m.channel_id); - if let Some(factory) = channel_factory { + if let Some(allocator) = channel_allocator { // We need to adjust the extranonce for this channel let downstream_extranonce_prefix = self .extended_channels .get(&m.channel_id) .map(|channel| channel.get_extranonce_prefix().clone()); - let range0_len = factory.get_range0_len(); + let upstream_prefix_len = allocator.upstream_prefix_len(); if let Some(downstream_extranonce_prefix) = downstream_extranonce_prefix { - // Skip the upstream prefix (range0) and take the remaining + // Skip the upstream prefix and take the remaining // bytes (translator proxy prefix) - let translator_prefix = &downstream_extranonce_prefix[range0_len..]; + let translator_prefix = + &downstream_extranonce_prefix[upstream_prefix_len..]; // Create new extranonce: translator proxy prefix + miner's // extranonce let mut new_extranonce = translator_prefix.to_vec(); @@ -691,20 +707,40 @@ impl ChannelManager { debug!("Removed channel {} from group channel before sending CloseChannel to upstream", m.channel_id); } } + // Free the extranonce prefix for reuse + if let Some((_, prefix_id)) = self.channel_to_local_prefix_id.remove(&m.channel_id) + { + // In aggregated mode, free from AGGREGATED_CHANNEL_ID allocator + if is_aggregated() { + if let Some(mut allocator) = + self.extranonce_allocators.get_mut(&AGGREGATED_CHANNEL_ID) + { + allocator.free(prefix_id); + } + } else if let Some(mut allocator) = + self.extranonce_allocators.get_mut(&m.channel_id) + { + allocator.free(prefix_id); + } + } - let message = Mining::CloseChannel(m); - let sv2_frame: Sv2Frame = AnyMessage::Mining(message) - .try_into() - .map_err(TproxyError::shutdown)?; - - self.channel_state - .upstream_sender - .send(sv2_frame) - .await - .map_err(|e| { - error!("Failed to send CloseChannel message to upstream: {:?}", e); - TproxyError::fallback(TproxyErrorKind::ChannelErrorSender) - })?; + // In aggregated mode, don't forward CloseChannel to upstream since all + // downstreams share the same upstream channel (AGGREGATED_CHANNEL_ID) + if !is_aggregated() { + let message = Mining::CloseChannel(m); + let sv2_frame: Sv2Frame = AnyMessage::Mining(message) + .try_into() + .map_err(TproxyError::shutdown)?; + + self.channel_state + .upstream_sender + .send(sv2_frame) + .await + .map_err(|e| { + error!("Failed to send CloseChannel message to upstream: {:?}", e); + TproxyError::fallback(TproxyErrorKind::ChannelErrorSender) + })?; + } } _ => { warn!("Unhandled downstream message: {:?}", message); diff --git a/miner-apps/translator/src/lib/sv2/channel_manager/mining_message_handler.rs b/miner-apps/translator/src/lib/sv2/channel_manager/mining_message_handler.rs index 9d067c7e3..5d531e122 100644 --- a/miner-apps/translator/src/lib/sv2/channel_manager/mining_message_handler.rs +++ b/miner-apps/translator/src/lib/sv2/channel_manager/mining_message_handler.rs @@ -1,20 +1,22 @@ use crate::{ error::{self, TproxyError, TproxyErrorKind}, is_aggregated, - sv2::ChannelManager, + sv2::{ChannelManager, TPROXY_MAX_CHANNELS}, utils::{proxy_extranonce_prefix_len, AGGREGATED_CHANNEL_ID}, }; use stratum_apps::{ stratum_core::{ bitcoin::Target, - channels_sv2::client::{extended::ExtendedChannel, group::GroupChannel}, + channels_sv2::{ + client::{extended::ExtendedChannel, group::GroupChannel}, + extranonce_manager::ExtranonceAllocator, + }, handlers_sv2::{HandleMiningMessagesFromServerAsync, SupportedChannelTypes}, mining_sv2::{ - CloseChannel, ExtendedExtranonce, Extranonce, NewExtendedMiningJob, NewMiningJob, - OpenExtendedMiningChannelSuccess, OpenMiningChannelError, - OpenStandardMiningChannelSuccess, SetCustomMiningJobError, SetCustomMiningJobSuccess, - SetExtranoncePrefix, SetGroupChannel, SetNewPrevHash, SetTarget, SubmitSharesError, - SubmitSharesSuccess, UpdateChannelError, + CloseChannel, NewExtendedMiningJob, NewMiningJob, OpenExtendedMiningChannelSuccess, + OpenMiningChannelError, OpenStandardMiningChannelSuccess, SetCustomMiningJobError, + SetCustomMiningJobSuccess, SetExtranoncePrefix, SetGroupChannel, SetNewPrevHash, + SetTarget, SubmitSharesError, SubmitSharesSuccess, UpdateChannelError, MESSAGE_TYPE_OPEN_STANDARD_MINING_CHANNEL_SUCCESS, MESSAGE_TYPE_SET_CUSTOM_MINING_JOB_ERROR, MESSAGE_TYPE_SET_CUSTOM_MINING_JOB_SUCCESS, }, @@ -129,45 +131,42 @@ impl HandleMiningMessagesFromServerAsync for ChannelManager { self.extended_channels .insert(AGGREGATED_CHANNEL_ID, extended_channel.clone()); - let upstream_extranonce_prefix: Extranonce = m.extranonce_prefix.clone().into(); let translator_proxy_extranonce_prefix_len = proxy_extranonce_prefix_len( m.extranonce_size.into(), downstream_extranonce_len, ); - // range 0 is the extranonce_prefix from upstream - // range 1 is the extranonce_prefix added by the tproxy - // range 2 is the extranonce_size used by the miner for rolling - let range_0 = 0..extranonce_prefix.len(); - let range1 = range_0.end..range_0.end + translator_proxy_extranonce_prefix_len; - let range2 = range1.end..range1.end + downstream_extranonce_len; debug!( - "\n\nrange_0: {:?}, range1: {:?}, range2: {:?}\n\n", - range_0, range1, range2 + "Creating allocator: upstream_prefix_len={}, local_prefix_len={}, downstream_extranonce_len={}", + extranonce_prefix.len(), translator_proxy_extranonce_prefix_len, downstream_extranonce_len ); - let extended_extranonce_factory = ExtendedExtranonce::from_upstream_extranonce( - upstream_extranonce_prefix, - range_0, - range1, - range2, + + let allocator = ExtranonceAllocator::from_upstream( + extranonce_prefix.clone(), + translator_proxy_extranonce_prefix_len, + full_extranonce_size, + TPROXY_MAX_CHANNELS, ) - .expect("Failed to create ExtendedExtranonce from upstream extranonce"); - self.extranonce_factories - .insert(AGGREGATED_CHANNEL_ID, extended_extranonce_factory); + .expect("Failed to create ExtranonceAllocator from upstream extranonce"); - let mut factory = self - .extranonce_factories + self.extranonce_allocators + .insert(AGGREGATED_CHANNEL_ID, allocator); + + let mut allocator = self + .extranonce_allocators .get_mut(&AGGREGATED_CHANNEL_ID) - .expect("extranonce_prefix_factory should be set after creation"); - let new_extranonce_size = factory.get_range2_len() as u16; - let new_extranonce_prefix = factory - .next_prefix_extended(new_extranonce_size as usize) - .expect("next_prefix_extended should return a value for valid input") - .into_b032(); + .expect("extranonce_allocator should be set after creation"); + let new_extranonce_size = allocator.rollable_extranonce_size() as u16; + let prefix = allocator + .allocate_extended(new_extranonce_size as usize) + .expect("allocate_extended should return a value for valid input"); + let new_extranonce_prefix = prefix.as_bytes().to_vec(); + self.channel_to_local_prefix_id + .insert(m.channel_id, prefix.local_prefix_id()); let new_downstream_extended_channel = ExtendedChannel::new( m.channel_id, user_identity.clone(), - new_extranonce_prefix.clone().into_static().to_vec(), + new_extranonce_prefix.clone(), target, nominal_hashrate, true, @@ -178,7 +177,7 @@ impl HandleMiningMessagesFromServerAsync for ChannelManager { let new_open_extended_mining_channel_success = OpenExtendedMiningChannelSuccess { request_id: m.request_id, channel_id: m.channel_id, - extranonce_prefix: new_extranonce_prefix, + extranonce_prefix: new_extranonce_prefix.try_into().expect("valid prefix"), extranonce_size: new_extranonce_size, target: m.target.clone(), group_channel_id: m.group_channel_id, @@ -189,42 +188,38 @@ impl HandleMiningMessagesFromServerAsync for ChannelManager { } else { // Non-aggregated mode: check if we need to adjust extranonce size if m.extranonce_size as usize != downstream_extranonce_len { - // We need to create an extranonce factory to ensure proper extranonce2_size - let upstream_extranonce_prefix: Extranonce = m.extranonce_prefix.clone().into(); + // We need to create an extranonce allocator to ensure proper extranonce2_size let translator_proxy_extranonce_prefix_len = proxy_extranonce_prefix_len( m.extranonce_size.into(), downstream_extranonce_len, ); - // range 0 is the extranonce1 from upstream - // range 1 is the extranonce1 added by the tproxy - // range 2 is the extranonce2 used by the miner for rolling - let range_0 = 0..extranonce_prefix.len(); - let range1 = range_0.end..range_0.end + translator_proxy_extranonce_prefix_len; - let range2 = range1.end..range1.end + downstream_extranonce_len; debug!( - "\n\nrange_0: {:?}, range1: {:?}, range2: {:?}\n\n", - range_0, range1, range2 + "Creating allocator (non-agg): upstream_prefix_len={}, local_prefix_len={}, downstream_extranonce_len={}", + extranonce_prefix.len(), translator_proxy_extranonce_prefix_len, downstream_extranonce_len ); - // Create the factory - this should succeed if configuration is valid - let extended_extranonce_factory = ExtendedExtranonce::from_upstream_extranonce( - upstream_extranonce_prefix, - range_0, - range1, - range2, - ) - .expect("Failed to create ExtendedExtranonce factory - likely extranonce size configuration issue"); - // Store the factory for this specific channel - let mut factory = extended_extranonce_factory; - let new_extranonce_prefix = factory - .next_prefix_extended(downstream_extranonce_len) - .expect("Failed to generate extranonce prefix") - .into_b032(); + + // Create the allocator - this should succeed if configuration is valid + let mut allocator = ExtranonceAllocator::from_upstream( + extranonce_prefix.clone(), + translator_proxy_extranonce_prefix_len, + full_extranonce_size, + TPROXY_MAX_CHANNELS, + ) + .expect("Failed to create ExtranonceAllocator - likely extranonce size configuration issue"); + + let prefix = allocator + .allocate_extended(downstream_extranonce_len) + .expect("Failed to generate extranonce prefix"); + let new_extranonce_prefix = prefix.as_bytes().to_vec(); + self.channel_to_local_prefix_id + .insert(m.channel_id, prefix.local_prefix_id()); + // Create channel with the configured extranonce size let new_downstream_extended_channel = ExtendedChannel::new( m.channel_id, user_identity.clone(), - new_extranonce_prefix.clone().into_static().to_vec(), + new_extranonce_prefix.clone(), target, nominal_hashrate, true, @@ -233,13 +228,15 @@ impl HandleMiningMessagesFromServerAsync for ChannelManager { self.extended_channels .insert(m.channel_id, new_downstream_extended_channel); - self.extranonce_factories.insert(m.channel_id, factory); + self.extranonce_allocators.insert(m.channel_id, allocator); let new_open_extended_mining_channel_success = OpenExtendedMiningChannelSuccess { request_id: m.request_id, channel_id: m.channel_id, - extranonce_prefix: new_extranonce_prefix, + extranonce_prefix: new_extranonce_prefix + .try_into() + .expect("valid prefix"), extranonce_size: downstream_extranonce_len as u16, target: m.target.clone(), group_channel_id: m.group_channel_id, diff --git a/miner-apps/translator/src/lib/sv2/mod.rs b/miner-apps/translator/src/lib/sv2/mod.rs index d8cb5e360..9a456a73c 100644 --- a/miner-apps/translator/src/lib/sv2/mod.rs +++ b/miner-apps/translator/src/lib/sv2/mod.rs @@ -1,5 +1,7 @@ pub mod channel_manager; pub mod upstream; -pub use channel_manager::channel_manager::ChannelManager; +pub use channel_manager::channel_manager::{ + ChannelManager, TPROXY_ALLOCATION_BYTES, TPROXY_MAX_CHANNELS, +}; pub use upstream::upstream::Upstream; diff --git a/pool-apps/Cargo.lock b/pool-apps/Cargo.lock index 474a899b7..4d0f8b4ca 100644 --- a/pool-apps/Cargo.lock +++ b/pool-apps/Cargo.lock @@ -273,7 +273,7 @@ checksum = "32637268377fc7b10a8c6d51de3e7fba1ce5dd371a96e342b34e6078db558e7f" [[package]] name = "binary_sv2" version = "5.0.1" -source = "git+https://github.com/stratum-mining/stratum?branch=main#ece2b035697ec35a8d1506ee1a321bccc9707b09" +source = "git+https://github.com/GitGab19/stratum?branch=extranonce-allocation#834b6cedd33239eead15db198b529e9450e96ccd" dependencies = [ "buffer_sv2", "derive_codec_sv2", @@ -419,7 +419,7 @@ dependencies = [ [[package]] name = "buffer_sv2" version = "3.0.1" -source = "git+https://github.com/stratum-mining/stratum?branch=main#ece2b035697ec35a8d1506ee1a321bccc9707b09" +source = "git+https://github.com/GitGab19/stratum?branch=extranonce-allocation#834b6cedd33239eead15db198b529e9450e96ccd" dependencies = [ "aes-gcm", ] @@ -531,7 +531,7 @@ dependencies = [ [[package]] name = "channels_sv2" version = "4.0.0" -source = "git+https://github.com/stratum-mining/stratum?branch=main#ece2b035697ec35a8d1506ee1a321bccc9707b09" +source = "git+https://github.com/GitGab19/stratum?branch=extranonce-allocation#834b6cedd33239eead15db198b529e9450e96ccd" dependencies = [ "binary_sv2", "bitcoin", @@ -601,7 +601,7 @@ checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831" [[package]] name = "codec_sv2" version = "5.0.0" -source = "git+https://github.com/stratum-mining/stratum?branch=main#ece2b035697ec35a8d1506ee1a321bccc9707b09" +source = "git+https://github.com/GitGab19/stratum?branch=extranonce-allocation#834b6cedd33239eead15db198b529e9450e96ccd" dependencies = [ "binary_sv2", "buffer_sv2", @@ -629,7 +629,7 @@ dependencies = [ [[package]] name = "common_messages_sv2" version = "7.0.0" -source = "git+https://github.com/stratum-mining/stratum?branch=main#ece2b035697ec35a8d1506ee1a321bccc9707b09" +source = "git+https://github.com/GitGab19/stratum?branch=extranonce-allocation#834b6cedd33239eead15db198b529e9450e96ccd" dependencies = [ "binary_sv2", ] @@ -783,7 +783,7 @@ dependencies = [ [[package]] name = "derive_codec_sv2" version = "1.1.2" -source = "git+https://github.com/stratum-mining/stratum?branch=main#ece2b035697ec35a8d1506ee1a321bccc9707b09" +source = "git+https://github.com/GitGab19/stratum?branch=extranonce-allocation#834b6cedd33239eead15db198b529e9450e96ccd" [[package]] name = "digest" @@ -912,7 +912,7 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "extensions_sv2" version = "0.1.0" -source = "git+https://github.com/stratum-mining/stratum?branch=main#ece2b035697ec35a8d1506ee1a321bccc9707b09" +source = "git+https://github.com/GitGab19/stratum?branch=extranonce-allocation#834b6cedd33239eead15db198b529e9450e96ccd" dependencies = [ "binary_sv2", ] @@ -973,7 +973,7 @@ dependencies = [ [[package]] name = "framing_sv2" version = "6.0.1" -source = "git+https://github.com/stratum-mining/stratum?branch=main#ece2b035697ec35a8d1506ee1a321bccc9707b09" +source = "git+https://github.com/GitGab19/stratum?branch=extranonce-allocation#834b6cedd33239eead15db198b529e9450e96ccd" dependencies = [ "binary_sv2", "buffer_sv2", @@ -1108,7 +1108,7 @@ dependencies = [ [[package]] name = "handlers_sv2" version = "0.2.2" -source = "git+https://github.com/stratum-mining/stratum?branch=main#ece2b035697ec35a8d1506ee1a321bccc9707b09" +source = "git+https://github.com/GitGab19/stratum?branch=extranonce-allocation#834b6cedd33239eead15db198b529e9450e96ccd" dependencies = [ "binary_sv2", "common_messages_sv2", @@ -1495,8 +1495,8 @@ checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "job_declaration_sv2" -version = "6.0.0" -source = "git+https://github.com/stratum-mining/stratum?branch=main#ece2b035697ec35a8d1506ee1a321bccc9707b09" +version = "7.0.0" +source = "git+https://github.com/GitGab19/stratum?branch=extranonce-allocation#834b6cedd33239eead15db198b529e9450e96ccd" dependencies = [ "binary_sv2", ] @@ -1616,8 +1616,8 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "mining_sv2" -version = "7.0.0" -source = "git+https://github.com/stratum-mining/stratum?branch=main#ece2b035697ec35a8d1506ee1a321bccc9707b09" +version = "8.0.0" +source = "git+https://github.com/GitGab19/stratum?branch=extranonce-allocation#834b6cedd33239eead15db198b529e9450e96ccd" dependencies = [ "binary_sv2", ] @@ -1657,7 +1657,7 @@ dependencies = [ [[package]] name = "noise_sv2" version = "1.4.2" -source = "git+https://github.com/stratum-mining/stratum?branch=main#ece2b035697ec35a8d1506ee1a321bccc9707b09" +source = "git+https://github.com/GitGab19/stratum?branch=extranonce-allocation#834b6cedd33239eead15db198b529e9450e96ccd" dependencies = [ "aes-gcm", "chacha20poly1305", @@ -1781,7 +1781,7 @@ dependencies = [ [[package]] name = "parsers_sv2" version = "0.2.2" -source = "git+https://github.com/stratum-mining/stratum?branch=main#ece2b035697ec35a8d1506ee1a321bccc9707b09" +source = "git+https://github.com/GitGab19/stratum?branch=extranonce-allocation#834b6cedd33239eead15db198b529e9450e96ccd" dependencies = [ "binary_sv2", "common_messages_sv2", @@ -2439,7 +2439,7 @@ dependencies = [ [[package]] name = "stratum-core" version = "0.2.1" -source = "git+https://github.com/stratum-mining/stratum?branch=main#ece2b035697ec35a8d1506ee1a321bccc9707b09" +source = "git+https://github.com/GitGab19/stratum?branch=extranonce-allocation#834b6cedd33239eead15db198b529e9450e96ccd" dependencies = [ "binary_sv2", "bitcoin", @@ -2506,7 +2506,7 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "template_distribution_sv2" version = "5.0.0" -source = "git+https://github.com/stratum-mining/stratum?branch=main#ece2b035697ec35a8d1506ee1a321bccc9707b09" +source = "git+https://github.com/GitGab19/stratum?branch=extranonce-allocation#834b6cedd33239eead15db198b529e9450e96ccd" dependencies = [ "binary_sv2", ] diff --git a/pool-apps/pool/src/lib/channel_manager/mining_message_handler.rs b/pool-apps/pool/src/lib/channel_manager/mining_message_handler.rs index 643716fbc..6b7bdc435 100644 --- a/pool-apps/pool/src/lib/channel_manager/mining_message_handler.rs +++ b/pool-apps/pool/src/lib/channel_manager/mining_message_handler.rs @@ -99,6 +99,13 @@ impl HandleMiningMessagesFromClientAsync for ChannelManager { channel_manager_data .vardiff .remove(&(downstream_id, msg.channel_id).into()); + + if let Some(prefix_id) = channel_manager_data + .channel_to_local_prefix_id + .remove(&(downstream_id, msg.channel_id)) + { + channel_manager_data.extranonce_allocator.free(prefix_id); + } Ok(()) }) } @@ -150,12 +157,15 @@ impl HandleMiningMessagesFromClientAsync for ChannelManager { downstream.downstream_data.super_safe_lock(|downstream_data| { let nominal_hash_rate = msg.nominal_hash_rate; let requested_max_target = Target::from_le_bytes(msg.max_target.inner_as_ref().try_into().unwrap()); - let extranonce_prefix = channel_manager_data.extranonce_prefix_factory_standard.next_prefix_standard().map_err(PoolError::shutdown)?; + let prefix = channel_manager_data.extranonce_allocator.allocate_standard().map_err(PoolError::shutdown)?; + let extranonce_prefix = prefix.as_bytes().to_vec(); + let local_prefix_id = prefix.local_prefix_id(); let channel_id = downstream_data.channel_id_factory.fetch_add(1, Ordering::SeqCst); + channel_manager_data.channel_to_local_prefix_id.insert((downstream_id, channel_id), local_prefix_id); let job_store = DefaultJobStore::new(); - let mut standard_channel = match StandardChannel::new_for_pool(channel_id, user_identity.to_string(), extranonce_prefix.to_vec(), requested_max_target, nominal_hash_rate, self.share_batch_size, self.shares_per_minute, job_store, self.pool_tag_string.clone()) { + let mut standard_channel = match StandardChannel::new_for_pool(channel_id, user_identity.to_string(), extranonce_prefix, requested_max_target, nominal_hash_rate, self.share_batch_size, self.shares_per_minute, job_store, self.pool_tag_string.clone()) { Ok(channel) => channel, Err(e) => match e { StandardChannelError::InvalidNominalHashrate => { @@ -282,11 +292,11 @@ impl HandleMiningMessagesFromClientAsync for ChannelManager { .super_safe_lock(|downstream_data| { let mut messages: Vec = Vec::new(); - let extranonce_prefix = match channel_manager_data - .extranonce_prefix_factory_extended - .next_prefix_extended(requested_min_rollable_extranonce_size.into()) + let (extranonce_prefix, local_prefix_id) = match channel_manager_data + .extranonce_allocator + .allocate_extended(requested_min_rollable_extranonce_size.into()) { - Ok(extranonce_prefix) => extranonce_prefix.to_vec(), + Ok(prefix) => (prefix.as_bytes().to_vec(), prefix.local_prefix_id()), Err(_) => { error!("OpenMiningChannelError: min-extranonce-size-too-large"); let open_extended_mining_channel_error = OpenMiningChannelError { @@ -309,6 +319,7 @@ impl HandleMiningMessagesFromClientAsync for ChannelManager { let channel_id = downstream_data .channel_id_factory .fetch_add(1, Ordering::SeqCst); + channel_manager_data.channel_to_local_prefix_id.insert((downstream_id, channel_id), local_prefix_id); let job_store = DefaultJobStore::new(); let mut extended_channel = match ExtendedChannel::new_for_pool( diff --git a/pool-apps/pool/src/lib/channel_manager/mod.rs b/pool-apps/pool/src/lib/channel_manager/mod.rs index 47c7e34f2..2ed011ae0 100644 --- a/pool-apps/pool/src/lib/channel_manager/mod.rs +++ b/pool-apps/pool/src/lib/channel_manager/mod.rs @@ -19,6 +19,7 @@ use stratum_apps::{ stratum_core::{ bitcoin::{Amount, TxOut}, channels_sv2::{ + extranonce_manager::ExtranonceAllocator, server::{ extended::ExtendedChannel, group::GroupChannel, @@ -30,7 +31,7 @@ use stratum_apps::{ handlers_sv2::{ HandleMiningMessagesFromClientAsync, HandleTemplateDistributionMessagesFromServerAsync, }, - mining_sv2::{ExtendedExtranonce, SetTarget}, + mining_sv2::SetTarget, parsers_sv2::{Mining, TemplateDistribution, Tlv}, template_distribution_sv2::{NewTemplate, SetNewPrevHash}, }, @@ -58,12 +59,11 @@ pub struct ChannelManagerData { // Mapping of `downstream_id` → `Downstream` object, // used by the channel manager to locate and interact with downstream clients. pub(crate) downstream: HashMap, - // Extranonce prefix factory for **extended downstream channels**. - // Each new extended downstream receives a unique extranonce prefix. - extranonce_prefix_factory_extended: ExtendedExtranonce, - // Extranonce prefix factory for **standard downstream channels**. - // Each new standard downstream receives a unique extranonce prefix. - extranonce_prefix_factory_standard: ExtendedExtranonce, + // Extranonce allocator for both standard and extended downstream channels. + // Each new downstream channel receives a unique extranonce prefix. + pub(crate) extranonce_allocator: ExtranonceAllocator, + // Mapping of `(downstream_id, channel_id)` → `local_prefix_id` for freeing extranonces. + pub(crate) channel_to_local_prefix_id: HashMap<(DownstreamId, ChannelId), usize>, // Factory that assigns a unique ID to each new **downstream connection**. downstream_id_factory: AtomicUsize, // Mapping of `(downstream_id, channel_id)` → vardiff controller. @@ -114,32 +114,22 @@ impl ChannelManager { downstream_receiver: Receiver<(DownstreamId, Mining<'static>, Option>)>, coinbase_outputs: Vec, ) -> PoolResult { - let range_0 = 0..0; - let range_1 = 0..POOL_ALLOCATION_BYTES; - let range_2 = POOL_ALLOCATION_BYTES..POOL_ALLOCATION_BYTES + CLIENT_SEARCH_SPACE_BYTES; - - let make_extranonce_factory = || { - // simulating a scenario where there are multiple mining servers - // this static prefix allows unique extranonce_prefix allocation - // for this mining server - let static_prefix = config.server_id().to_be_bytes().to_vec(); - - ExtendedExtranonce::new( - range_0.clone(), - range_1.clone(), - range_2.clone(), - Some(static_prefix), - ) - .expect("Failed to create ExtendedExtranonce with valid ranges") - }; - - let extranonce_prefix_factory_extended = make_extranonce_factory(); - let extranonce_prefix_factory_standard = make_extranonce_factory(); + // Server ID is used to distinguish multiple pool server instances. + // It takes 2 bytes of the local_prefix, leaving 2 bytes for channel IDs (65,536 channels). + let server_id = config.server_id().to_be_bytes().to_vec(); + + let extranonce_allocator = ExtranonceAllocator::new( + FULL_EXTRANONCE_SIZE, // 20 bytes total extranonce + POOL_ALLOCATION_BYTES, // 4 bytes for local prefix (2 server_id + 2 channel id) + Some(server_id), // 2-byte server identifier + 65_536, // max concurrent channels (2^16, fits in 2 bytes) + ) + .expect("Failed to create ExtranonceAllocator with valid configuration"); let channel_manager_data = Arc::new(Mutex::new(ChannelManagerData { downstream: HashMap::new(), - extranonce_prefix_factory_extended, - extranonce_prefix_factory_standard, + extranonce_allocator, + channel_to_local_prefix_id: HashMap::new(), downstream_id_factory: AtomicUsize::new(1), vardiff: HashMap::new(), coinbase_outputs, @@ -430,6 +420,19 @@ impl ChannelManager { cm_data .vardiff .retain(|key, _| key.downstream_id != downstream_id); + // Free extranonce prefixes for all channels belonging to this downstream + let prefix_ids_to_free: Vec = cm_data + .channel_to_local_prefix_id + .iter() + .filter(|((ds_id, _), _)| *ds_id == downstream_id) + .map(|(_, prefix_id)| *prefix_id) + .collect(); + for prefix_id in prefix_ids_to_free { + cm_data.extranonce_allocator.free(prefix_id); + } + cm_data + .channel_to_local_prefix_id + .retain(|(ds_id, _), _| *ds_id != downstream_id); }); Ok(()) } diff --git a/pool-apps/pool/src/lib/error.rs b/pool-apps/pool/src/lib/error.rs index dd9f37d9c..3f1f7fd0e 100644 --- a/pool-apps/pool/src/lib/error.rs +++ b/pool-apps/pool/src/lib/error.rs @@ -9,6 +9,7 @@ use stratum_apps::{ stratum_core::{ binary_sv2, bitcoin, channels_sv2::{ + extranonce_manager::ExtranonceAllocatorError, server::{ error::{ExtendedChannelError, GroupChannelError, StandardChannelError}, share_accounting::ShareValidationError, @@ -17,7 +18,6 @@ use stratum_apps::{ }, codec_sv2, framing_sv2, handlers_sv2::HandlerErrorType, - mining_sv2::ExtendedExtranonceError, noise_sv2, parsers_sv2::{Mining, ParserError}, }, @@ -105,7 +105,7 @@ pub enum ChannelSv2Error { ExtendedChannelServerSide(ExtendedChannelError), StandardChannelServerSide(StandardChannelError), GroupChannelServerSide(GroupChannelError), - ExtranonceError(ExtendedExtranonceError), + ExtranonceError(ExtranonceAllocatorError), ShareValidationError(ShareValidationError), } @@ -374,8 +374,8 @@ impl From for PoolErrorKind { } } -impl From for PoolErrorKind { - fn from(value: ExtendedExtranonceError) -> Self { +impl From for PoolErrorKind { + fn from(value: ExtranonceAllocatorError) -> Self { PoolErrorKind::ChannelSv2(ChannelSv2Error::ExtranonceError(value)) } } diff --git a/stratum-apps/Cargo.toml b/stratum-apps/Cargo.toml index ed52ff742..e443fda53 100644 --- a/stratum-apps/Cargo.toml +++ b/stratum-apps/Cargo.toml @@ -16,7 +16,8 @@ keywords = ["stratum", "mining", "bitcoin", "protocol", "sv2"] # fetching from github enables synchronizing development workflows across sv2-apps and stratum repos # it MUST be changed before stratum-apps is published to crates.io # with the proper version of stratum-core being fetched from crates.io as well -stratum-core = { git = "https://github.com/stratum-mining/stratum", branch = "main", optional = true } +# TODO: points this to https://github.com/stratum-mining/stratum once https://github.com/stratum-mining/stratum/pull/2098 is merged +stratum-core = { git = "https://github.com/GitGab19/stratum", branch = "extranonce-allocation", optional = true } # External dependencies needed by the modules # Network helpers dependencies