Skip to content

Commit c2b6d63

Browse files
committed
add integration test for user_identity extension
1 parent 4a7acf3 commit c2b6d63

2 files changed

Lines changed: 209 additions & 4 deletions

File tree

integration-tests/Cargo.lock

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
// Integration test for translator extension negotiation with extension 0x0002
2+
// (EXTENSION_TYPE_WORKER_HASHRATE_TRACKING) and user_identity TLV validation.
3+
//
4+
// This test validates:
5+
// 1. Pool and translator negotiate extension 0x0002 during SetupConnection
6+
// 2. SV1 miner submits shares through the translator
7+
// 3. Translator forwards SubmitSharesExtended with TLV containing user_identity
8+
// 4. Pool receives and processes the TLV user_identity correctly
9+
10+
use integration_tests_sv2::{interceptor::MessageDirection, template_provider::DifficultyLevel, *};
11+
use stratum_apps::stratum_core::{
12+
binary_sv2::Seq064K,
13+
common_messages_sv2::*,
14+
extensions_sv2::{EXTENSION_TYPE_WORKER_HASHRATE_TRACKING, TLV_FIELD_TYPE_USER_IDENTITY},
15+
mining_sv2::*,
16+
parsers_sv2::{AnyMessage, Extensions, ExtensionsNegotiation, Mining},
17+
};
18+
use tracing::info;
19+
20+
/// Tests that the translator successfully negotiates extension 0x0002 with the pool
21+
/// and sends user_identity TLV in SubmitSharesExtended messages.
22+
#[tokio::test]
23+
async fn test_extension_negotiation_with_tlv_in_submit_shares() {
24+
start_tracing();
25+
// Extension 0x0002 for worker hashrate tracking
26+
let supported_extensions = vec![EXTENSION_TYPE_WORKER_HASHRATE_TRACKING];
27+
let required_extensions = vec![EXTENSION_TYPE_WORKER_HASHRATE_TRACKING];
28+
29+
let (_tp, tp_addr) = start_template_provider(None, DifficultyLevel::Low);
30+
// Start pool with extension 0x0002 support
31+
let (_pool, pool_addr) = start_pool(Some(tp_addr), supported_extensions.clone(), vec![]).await;
32+
let (pool_translator_sniffer, pool_translator_sniffer_addr) =
33+
start_sniffer("pool-translator", pool_addr, false, vec![], None);
34+
// Start translator with extension 0x0002 support and user_identity configured
35+
// aggregate_channels = false ensures TLV fields are added
36+
let (_tproxy, tproxy_addr) = start_sv2_translator(
37+
&[pool_translator_sniffer_addr],
38+
false, // aggregate_channels = false
39+
supported_extensions.clone(),
40+
required_extensions,
41+
)
42+
.await;
43+
// Start SV1 miner (minerd) connected to translator with username "SRI-miner"
44+
let (_minerd_process, _minerd_addr) = start_minerd(
45+
tproxy_addr,
46+
Some("SRI-miner".to_string()),
47+
Some("password".to_string()),
48+
false,
49+
)
50+
.await;
51+
52+
pool_translator_sniffer
53+
.wait_for_message_type_and_clean_queue(
54+
MessageDirection::ToUpstream,
55+
MESSAGE_TYPE_SETUP_CONNECTION,
56+
)
57+
.await;
58+
59+
pool_translator_sniffer
60+
.wait_for_message_type_and_clean_queue(
61+
MessageDirection::ToDownstream,
62+
MESSAGE_TYPE_SETUP_CONNECTION_SUCCESS,
63+
)
64+
.await;
65+
66+
// Verify RequestExtensions includes extension 0x0002
67+
let request_extensions_msg = match pool_translator_sniffer.next_message_from_downstream() {
68+
Some((
69+
_,
70+
AnyMessage::Extensions(Extensions::ExtensionsNegotiation(
71+
ExtensionsNegotiation::RequestExtensions(msg),
72+
)),
73+
)) => msg,
74+
_ => panic!(
75+
"received unexpected message: {:?}",
76+
pool_translator_sniffer.next_message_from_downstream()
77+
),
78+
};
79+
assert_eq!(
80+
request_extensions_msg.requested_extensions,
81+
Seq064K::new(supported_extensions.clone()).unwrap()
82+
);
83+
84+
// Verify RequestExtensionsSuccess acknowledges the extension
85+
let request_extensions_success_msg = pool_translator_sniffer.next_message_from_upstream();
86+
match request_extensions_success_msg {
87+
Some((
88+
_,
89+
AnyMessage::Extensions(Extensions::ExtensionsNegotiation(
90+
ExtensionsNegotiation::RequestExtensionsSuccess(msg),
91+
)),
92+
)) => {
93+
assert_eq!(
94+
msg.supported_extensions,
95+
Seq064K::new(supported_extensions).unwrap()
96+
);
97+
}
98+
_ => panic!("Expected RequestExtensionsSuccess message"),
99+
}
100+
101+
pool_translator_sniffer
102+
.wait_for_message_type(
103+
MessageDirection::ToUpstream,
104+
MESSAGE_TYPE_OPEN_EXTENDED_MINING_CHANNEL,
105+
)
106+
.await;
107+
108+
// Extract and verify user_identity from OpenExtendedMiningChannel
109+
let open_channel_msg = pool_translator_sniffer.next_message_from_downstream();
110+
match open_channel_msg {
111+
Some((_, AnyMessage::Mining(Mining::OpenExtendedMiningChannel(msg)))) => {
112+
let user_identity = msg.user_identity.as_utf8_or_hex();
113+
assert_eq!(user_identity, "user_identity.miner1".to_string());
114+
}
115+
_ => panic!(
116+
"received unexpected message: {:?}",
117+
pool_translator_sniffer.next_message_from_downstream()
118+
),
119+
}
120+
121+
pool_translator_sniffer
122+
.wait_for_message_type(
123+
MessageDirection::ToDownstream,
124+
MESSAGE_TYPE_OPEN_EXTENDED_MINING_CHANNEL_SUCCESS,
125+
)
126+
.await;
127+
128+
pool_translator_sniffer
129+
.wait_for_message_type(
130+
MessageDirection::ToDownstream,
131+
MESSAGE_TYPE_NEW_EXTENDED_MINING_JOB,
132+
)
133+
.await;
134+
135+
pool_translator_sniffer
136+
.wait_for_message_type(
137+
MessageDirection::ToUpstream,
138+
MESSAGE_TYPE_SUBMIT_SHARES_EXTENDED,
139+
)
140+
.await;
141+
// Verify SubmitSharesExtended contains TLV with user_identity
142+
let submit_shares_msg = pool_translator_sniffer.next_message_from_downstream_with_tlvs();
143+
match submit_shares_msg {
144+
Some((_, AnyMessage::Mining(Mining::SubmitSharesExtended(msg)), tlv_fields)) => {
145+
info!(
146+
"SubmitSharesExtended received - channel_id: {}, sequence_number: {}, job_id: {}",
147+
msg.channel_id, msg.sequence_number, msg.job_id
148+
);
149+
let tlvs = tlv_fields.unwrap();
150+
// Find user_identity TLV
151+
let user_identity_tlv = tlvs.iter().find(|tlv| {
152+
tlv.r#type.extension_type == EXTENSION_TYPE_WORKER_HASHRATE_TRACKING
153+
&& tlv.r#type.field_type == TLV_FIELD_TYPE_USER_IDENTITY
154+
});
155+
assert!(
156+
user_identity_tlv.is_some(),
157+
"user_identity TLV should be present with extension 0x0002"
158+
);
159+
160+
// Extract and validate user_identity value
161+
if let Some(tlv) = user_identity_tlv {
162+
// Validate TLV structure
163+
assert_eq!(
164+
tlv.r#type.extension_type, EXTENSION_TYPE_WORKER_HASHRATE_TRACKING,
165+
"TLV extension_type should be 0x0002"
166+
);
167+
assert_eq!(
168+
tlv.r#type.field_type, TLV_FIELD_TYPE_USER_IDENTITY,
169+
"TLV field_type should be user_identity"
170+
);
171+
let payload_len = tlv.value.len();
172+
assert!(
173+
payload_len == 9,
174+
"user_identity TLV payload should be 9 bytes"
175+
);
176+
// Try to convert value to string for logging
177+
if let Ok(user_identity_str) = std::str::from_utf8(&tlv.value) {
178+
// Verify user_identity format (should be "SRI-miner")
179+
assert_eq!(
180+
user_identity_str, "SRI-miner",
181+
"user_identity should be 'SRI-miner', got: {}",
182+
user_identity_str
183+
);
184+
} else {
185+
// If not UTF-8, just log hex representation
186+
let hex_str = tlv
187+
.value
188+
.iter()
189+
.map(|b| format!("{:02x}", b))
190+
.collect::<String>();
191+
info!("✅ user_identity TLV payload (hex): {}", hex_str);
192+
}
193+
}
194+
}
195+
_ => panic!("Expected SubmitSharesExtended message with TLV fields"),
196+
}
197+
198+
// Wait for SubmitSharesSuccess response from pool
199+
pool_translator_sniffer
200+
.wait_for_message_type(
201+
MessageDirection::ToDownstream,
202+
MESSAGE_TYPE_SUBMIT_SHARES_SUCCESS,
203+
)
204+
.await;
205+
}

0 commit comments

Comments
 (0)