|
| 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