Skip to content

Commit 562c29e

Browse files
committed
feat: add translator search space allocation for non-aggregated mode
- Rename min_extranonce2_size -> downstream_extranonce2_size for clarity - Add 4 bytes translator search space in aggregated mode via AGGREGATED_MODE_TRANSLATOR_SEARCH_SPACE_BYTES constant - Add per-channel extranonce factories for non-aggregated mode size adjustment
1 parent 73fcf86 commit 562c29e

File tree

11 files changed

+165
-47
lines changed

11 files changed

+165
-47
lines changed

roles/translator/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ max_supported_version = 2
3535
min_supported_version = 2
3636

3737
# Extranonce Configuration
38-
min_extranonce2_size = 4 # Min: 2, Max: 16 (CGminer max: 8)
38+
downstream_extranonce2_size = 4 # Min: 2, Max: 16 (CGminer max: 8)
3939

4040
# User Identity (appended with counter for each miner)
4141
user_identity = "your_username_here"

roles/translator/config-examples/tproxy-config-hosted-pool-example.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@ downstream_port = 34255
66
max_supported_version = 2
77
min_supported_version = 2
88

9-
# Minimum extranonce2 size for downstream
10-
# Max value: 16 (leaves 0 bytes for search space splitting of downstreams)
9+
# Extranonce2 size for downstream connections
10+
# This controls the rollable part of the extranonce for downstream SV1 miners
1111
# Max value for CGminer: 8
1212
# Min value: 2
13-
min_extranonce2_size = 4
13+
downstream_extranonce2_size = 4
1414

1515
# User identity/username for pool connection
1616
# This will be appended with a counter for each mining client (e.g., username.miner1, username.miner2)

roles/translator/config-examples/tproxy-config-local-jdc-example.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@ downstream_port = 34255
66
max_supported_version = 2
77
min_supported_version = 2
88

9-
# Minimum extranonce2 size for downstream
10-
# Max value: 16 (leaves 0 bytes for search space splitting of downstreams)
9+
# Extranonce2 size for downstream connections
10+
# This controls the rollable part of the extranonce for downstream miners
1111
# Max value for CGminer: 8
1212
# Min value: 2
13-
min_extranonce2_size = 4
13+
downstream_extranonce2_size = 4
1414

1515
# User identity/username for pool connection
1616
# This will be appended with a counter for each mining client (e.g., username.miner1, username.miner2)

roles/translator/config-examples/tproxy-config-local-pool-example.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@ downstream_port = 34255
66
max_supported_version = 2
77
min_supported_version = 2
88

9-
# Minimum extranonce2 size for downstream
10-
# Max value: 16 (leaves 0 bytes for search space splitting of downstreams)
9+
# Extranonce2 size for downstream connections
10+
# This controls the rollable part of the extranonce for downstream miners
1111
# Max value for CGminer: 8
1212
# Min value: 2
13-
min_extranonce2_size = 4
13+
downstream_extranonce2_size = 4
1414

1515
# User identity/username for pool connection
1616
# This will be appended with a counter for each mining client (e.g., username.miner1, username.miner2)

roles/translator/src/lib/config.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ pub struct TranslatorConfig {
2727
pub max_supported_version: u16,
2828
/// The minimum supported protocol version for communication.
2929
pub min_supported_version: u16,
30-
/// The minimum size required for the extranonce2 field in mining submissions.
31-
pub min_extranonce2_size: u16,
30+
/// The size of the extranonce2 field for downstream mining connections.
31+
pub downstream_extranonce2_size: u16,
3232
/// The user identity/username to use when connecting to the pool.
3333
/// This will be appended with a counter for each mining channel (e.g., username.miner1,
3434
/// username.miner2).
@@ -74,7 +74,7 @@ impl TranslatorConfig {
7474
downstream_difficulty_config: DownstreamDifficultyConfig,
7575
max_supported_version: u16,
7676
min_supported_version: u16,
77-
min_extranonce2_size: u16,
77+
downstream_extranonce2_size: u16,
7878
user_identity: String,
7979
aggregate_channels: bool,
8080
) -> Self {
@@ -84,7 +84,7 @@ impl TranslatorConfig {
8484
downstream_port,
8585
max_supported_version,
8686
min_supported_version,
87-
min_extranonce2_size,
87+
downstream_extranonce2_size,
8888
user_identity,
8989
downstream_difficulty_config,
9090
aggregate_channels,
@@ -182,7 +182,7 @@ mod tests {
182182
assert_eq!(config.downstream_port, 3333);
183183
assert_eq!(config.max_supported_version, 2);
184184
assert_eq!(config.min_supported_version, 1);
185-
assert_eq!(config.min_extranonce2_size, 4);
185+
assert_eq!(config.downstream_extranonce2_size, 4);
186186
assert_eq!(config.user_identity, "test_user");
187187
assert!(config.aggregate_channels);
188188
assert!(config.log_file.is_none());

roles/translator/src/lib/sv1/downstream/message_handler.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ impl IsServer<'static> for DownstreamData {
6262
fn handle_submit(&self, request: &client_to_server::Submit<'static>) -> bool {
6363
if let Some(channel_id) = self.channel_id {
6464
info!(
65-
"Received mining.submit from Sv1 downstream for channel id: {}",
65+
"Received mining.submit from SV1 downstream for channel id: {}",
6666
channel_id
6767
);
6868
let is_valid_share = validate_sv1_share(

roles/translator/src/lib/sv1/sv1_server/sv1_server.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -575,7 +575,7 @@ impl Sv1Server {
575575

576576
let hashrate = config.min_individual_miner_hashrate as f64;
577577
let shares_per_min = config.shares_per_minute as f64;
578-
let min_extranonce_size = self.config.min_extranonce2_size;
578+
let min_extranonce_size = self.config.downstream_extranonce2_size;
579579
let vardiff_enabled = config.enable_vardiff;
580580

581581
let max_target = if vardiff_enabled {
@@ -790,7 +790,7 @@ mod tests {
790790
difficulty_config, // downstream_difficulty_config
791791
2, // max_supported_version
792792
1, // min_supported_version
793-
4, // min_extranonce2_size
793+
4, // downstream_extranonce2_size
794794
"test_user".to_string(),
795795
true, // aggregate_channels
796796
)

roles/translator/src/lib/sv2/channel_manager/channel_manager.rs

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ use stratum_common::roles_logic_sv2::{
2424
use tokio::sync::{broadcast, mpsc};
2525
use tracing::{debug, error, info, warn};
2626

27+
/// Extra bytes allocated for translator search space in aggregated mode.
28+
/// This allows the translator to manage multiple downstream connections
29+
/// by allocating unique extranonce prefixes to each downstream.
30+
const AGGREGATED_MODE_TRANSLATOR_SEARCH_SPACE_BYTES: usize = 4;
31+
2732
/// Type alias for SV2 mining messages with static lifetime
2833
pub type Sv2Message = Mining<'static>;
2934

@@ -413,14 +418,31 @@ impl ChannelManager {
413418
user_identity.as_bytes().to_vec().try_into().unwrap();
414419
}
415420
}
416-
// Store the user identity and hashrate
421+
// In aggregated mode, add extra bytes for translator search space allocation
422+
let upstream_min_extranonce_size = self.channel_manager_data.super_safe_lock(|c| {
423+
if c.mode == ChannelMode::Aggregated {
424+
min_extranonce_size + AGGREGATED_MODE_TRANSLATOR_SEARCH_SPACE_BYTES
425+
} else {
426+
min_extranonce_size
427+
}
428+
});
429+
430+
// Update the message with the adjusted extranonce size for upstream
431+
open_channel_msg.min_extranonce_size = upstream_min_extranonce_size as u16;
432+
433+
// Store the user identity, hashrate, and original downstream extranonce size
417434
self.channel_manager_data.super_safe_lock(|c| {
418435
c.pending_channels.insert(
419436
open_channel_msg.request_id,
420437
(user_identity, hashrate, min_extranonce_size),
421438
);
422439
});
423440

441+
info!(
442+
"Sending OpenExtendedMiningChannel message to upstream: {:?}",
443+
open_channel_msg
444+
);
445+
424446
let frame = StdFrame::try_from(Message::Mining(Mining::OpenExtendedMiningChannel(
425447
open_channel_msg,
426448
)))
@@ -510,6 +532,39 @@ impl ChannelManager {
510532
m.sequence_number = self
511533
.channel_manager_data
512534
.super_safe_lock(|c| c.next_share_sequence_number(m.channel_id));
535+
536+
// Check if we have a per-channel factory for extranonce adjustment
537+
let channel_factory = self.channel_manager_data.super_safe_lock(|c| {
538+
c.extranonce_factories
539+
.as_ref()
540+
.and_then(|factories| factories.get(&m.channel_id).cloned())
541+
});
542+
543+
if let Some(factory) = channel_factory {
544+
// We need to adjust the extranonce for this channel
545+
let downstream_extranonce_prefix =
546+
self.channel_manager_data.super_safe_lock(|c| {
547+
c.extended_channels.get(&m.channel_id).map(|channel| {
548+
channel.read().unwrap().get_extranonce_prefix().clone()
549+
})
550+
});
551+
let range0_len = factory
552+
.safe_lock(|e| e.get_range0_len())
553+
.expect("Failed to access extranonce factory range - this should not happen");
554+
if let Some(downstream_extranonce_prefix) = downstream_extranonce_prefix
555+
{
556+
// Skip the upstream prefix (range0) and take the remaining
557+
// bytes (translator proxy prefix)
558+
let translator_prefix = &downstream_extranonce_prefix[range0_len..];
559+
// Create new extranonce: translator proxy prefix + miner's
560+
// extranonce
561+
let mut new_extranonce = translator_prefix.to_vec();
562+
new_extranonce.extend_from_slice(m.extranonce.as_ref());
563+
// Replace the original extranonce with the modified one for
564+
// upstream submission
565+
m.extranonce = new_extranonce.try_into()?;
566+
}
567+
}
513568
}
514569

515570
info!(

roles/translator/src/lib/sv2/channel_manager/data.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ pub struct ChannelManagerData {
4646
/// In aggregated mode: single counter for all shares going to the upstream channel.
4747
/// In non-aggregated mode: one counter per downstream channel.
4848
pub share_sequence_counters: HashMap<u32, u32>,
49+
/// Per-channel extranonce factories for non-aggregated mode when extranonce adjustment is
50+
/// needed
51+
pub extranonce_factories: Option<HashMap<u32, Arc<Mutex<ExtendedExtranonce>>>>,
4952
}
5053

5154
impl ChannelManagerData {
@@ -64,6 +67,7 @@ impl ChannelManagerData {
6467
extranonce_prefix_factory: None,
6568
mode,
6669
share_sequence_counters: HashMap::new(),
70+
extranonce_factories: None,
6771
}
6872
}
6973

@@ -85,6 +89,7 @@ impl ChannelManagerData {
8589
self.upstream_extended_channel = None;
8690
self.extranonce_prefix_factory = None;
8791
self.share_sequence_counters.clear();
92+
self.extranonce_factories = None;
8893
// Note: we intentionally preserve `mode` as it's a configuration setting
8994
}
9095

roles/translator/src/lib/sv2/channel_manager/message_handler.rs

Lines changed: 84 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ use stratum_common::roles_logic_sv2::{
2121
utils::Mutex,
2222
};
2323

24-
use tracing::{error, info, warn};
24+
use tracing::{debug, error, info, warn};
2525

2626
impl HandleMiningMessagesFromServerAsync for ChannelManager {
2727
type Error = TproxyError;
@@ -90,7 +90,6 @@ impl HandleMiningMessagesFromServerAsync for ChannelManager {
9090
Some(Arc::new(RwLock::new(extended_channel.clone())));
9191

9292
let upstream_extranonce_prefix: Extranonce = m.extranonce_prefix.clone().into();
93-
info!(downstream_extranonce_len, "downstream_extranonce_len");
9493
let translator_proxy_extranonce_prefix_len = proxy_extranonce_prefix_len(
9594
m.extranonce_size.into(),
9695
downstream_extranonce_len,
@@ -102,7 +101,8 @@ impl HandleMiningMessagesFromServerAsync for ChannelManager {
102101
// that is used for rolling)
103102
let range_0 = 0..extranonce_prefix.len();
104103
let range1 = range_0.end..range_0.end + translator_proxy_extranonce_prefix_len;
105-
let range2 = range1.end..m.extranonce_size as usize + extranonce_prefix.len();
104+
let range2 = range1.end..range1.end + downstream_extranonce_len;
105+
debug!("\n\nrange_0: {:?}, range1: {:?}, range2: {:?}\n\n", range_0, range1, range2);
106106
let extended_extranonce_factory = ExtendedExtranonce::from_upstream_extranonce(
107107
upstream_extranonce_prefix,
108108
range_0,
@@ -121,44 +121,102 @@ impl HandleMiningMessagesFromServerAsync for ChannelManager {
121121
.safe_lock(|f| f.get_range2_len())
122122
.expect("extranonce_prefix_factory mutex should not be poisoned")
123123
as u16;
124-
if downstream_extranonce_len <= new_extranonce_size as usize {
124+
let new_extranonce_prefix = factory
125+
.safe_lock(|f| f.next_prefix_extended(new_extranonce_size as usize))
126+
.expect("extranonce_prefix_factory mutex should not be poisoned")
127+
.expect("next_prefix_extended should return a value for valid input")
128+
.into_b032();
129+
let new_downstream_extended_channel = ExtendedChannel::new(
130+
m.channel_id,
131+
user_identity.clone(),
132+
new_extranonce_prefix.clone().into_static().to_vec(),
133+
target.clone().into(),
134+
nominal_hashrate,
135+
true,
136+
new_extranonce_size,
137+
);
138+
channel_manager_data.extended_channels.insert(
139+
m.channel_id,
140+
Arc::new(RwLock::new(new_downstream_extended_channel)),
141+
);
142+
let new_open_extended_mining_channel_success =
143+
OpenExtendedMiningChannelSuccess {
144+
request_id: m.request_id,
145+
channel_id: m.channel_id,
146+
extranonce_prefix: new_extranonce_prefix,
147+
extranonce_size: new_extranonce_size,
148+
target: m.target.clone(),
149+
};
150+
new_open_extended_mining_channel_success.into_static()
151+
} else {
152+
// Non-aggregated mode: check if we need to adjust extranonce size
153+
if m.extranonce_size as usize != downstream_extranonce_len {
154+
// We need to create an extranonce factory to ensure proper extranonce2_size
155+
let upstream_extranonce_prefix: Extranonce = m.extranonce_prefix.clone().into();
156+
let translator_proxy_extranonce_prefix_len = proxy_extranonce_prefix_len(
157+
m.extranonce_size.into(),
158+
downstream_extranonce_len,
159+
);
160+
161+
// range 0 is the extranonce1 from upstream
162+
// range 1 is the extranonce1 added by the tproxy
163+
// range 2 is the extranonce2 used by the miner for rolling
164+
let range_0 = 0..extranonce_prefix.len();
165+
let range1 = range_0.end..range_0.end + translator_proxy_extranonce_prefix_len;
166+
let range2 = range1.end..range1.end + downstream_extranonce_len;
167+
debug!("\n\nrange_0: {:?}, range1: {:?}, range2: {:?}\n\n", range_0, range1, range2);
168+
// Create the factory - this should succeed if configuration is valid
169+
let extended_extranonce_factory = ExtendedExtranonce::from_upstream_extranonce(
170+
upstream_extranonce_prefix,
171+
range_0,
172+
range1,
173+
range2,
174+
)
175+
.expect("Failed to create ExtendedExtranonce factory - likely extranonce size configuration issue");
176+
// Store the factory for this specific channel
177+
let factory = Arc::new(Mutex::new(extended_extranonce_factory));
125178
let new_extranonce_prefix = factory
126-
.safe_lock(|f| f.next_prefix_extended(new_extranonce_size as usize))
127-
.expect("extranonce_prefix_factory mutex should not be poisoned")
128-
.expect("next_prefix_extended should return a value for valid input")
179+
.safe_lock(|f| f.next_prefix_extended(downstream_extranonce_len))
180+
.expect("Failed to access extranonce factory")
181+
.expect("Failed to generate extranonce prefix")
129182
.into_b032();
183+
// Create channel with the configured extranonce size
130184
let new_downstream_extended_channel = ExtendedChannel::new(
131185
m.channel_id,
132186
user_identity.clone(),
133187
new_extranonce_prefix.clone().into_static().to_vec(),
134188
target.clone().into(),
135189
nominal_hashrate,
136190
true,
137-
new_extranonce_size,
191+
downstream_extranonce_len as u16,
138192
);
139193
channel_manager_data.extended_channels.insert(
140194
m.channel_id,
141195
Arc::new(RwLock::new(new_downstream_extended_channel)),
142196
);
143-
let new_open_extended_mining_channel_success =
144-
OpenExtendedMiningChannelSuccess {
145-
request_id: m.request_id,
146-
channel_id: m.channel_id,
147-
extranonce_prefix: new_extranonce_prefix,
148-
extranonce_size: new_extranonce_size,
149-
target: m.target.clone(),
150-
};
151-
return new_open_extended_mining_channel_success.into_static();
197+
// Store factory for this channel (we'll need it for share processing)
198+
if channel_manager_data.extranonce_factories.is_none() {
199+
channel_manager_data.extranonce_factories = Some(std::collections::HashMap::new());
200+
}
201+
if let Some(ref mut factories) = channel_manager_data.extranonce_factories {
202+
factories.insert(m.channel_id, factory);
203+
}
204+
let new_open_extended_mining_channel_success = OpenExtendedMiningChannelSuccess {
205+
request_id: m.request_id,
206+
channel_id: m.channel_id,
207+
extranonce_prefix: new_extranonce_prefix,
208+
extranonce_size: downstream_extranonce_len as u16,
209+
target: m.target.clone(),
210+
};
211+
new_open_extended_mining_channel_success.into_static()
212+
} else {
213+
// Extranonce size matches, use as-is
214+
channel_manager_data
215+
.extended_channels
216+
.insert(m.channel_id, Arc::new(RwLock::new(extended_channel)));
217+
m.into_static()
152218
}
153219
}
154-
155-
// If we are not in aggregated mode, we just insert the extended channel into the
156-
// map
157-
channel_manager_data
158-
.extended_channels
159-
.insert(m.channel_id, Arc::new(RwLock::new(extended_channel)));
160-
161-
m.into_static()
162220
})
163221
.map_err(|e| {
164222
error!("Failed to lock channel manager data: {:?}", e);
@@ -228,7 +286,7 @@ impl HandleMiningMessagesFromServerAsync for ChannelManager {
228286
&mut self,
229287
m: SubmitSharesError<'_>,
230288
) -> Result<(), Self::Error> {
231-
warn!("Received: {} ❌", m.channel_id);
289+
warn!("Received: {} ❌", m);
232290
Ok(())
233291
}
234292

0 commit comments

Comments
 (0)