@@ -1473,3 +1473,218 @@ async fn aggregated_translator_triggers_fallback_on_close_channel_message() {
14731473 . wait_for_message_type ( MessageDirection :: ToUpstream , MESSAGE_TYPE_SETUP_CONNECTION )
14741474 . await ;
14751475}
1476+
1477+ /// This test verifies that the translator correctly handles miners with long worker names.
1478+ ///
1479+ /// The UserIdentity TLV field has a 32-byte limit. When a miner sends mining.authorize with
1480+ /// a long "username.workername", the translator should:
1481+ /// - Truncate the total identity to 32 bytes (preserving the username prefix)
1482+ /// - Continue processing shares without panicking
1483+ ///
1484+ /// This test uses a short username with a very long worker name to verify truncation works.
1485+ #[ tokio:: test]
1486+ async fn translator_handles_long_worker_name_with_truncation ( ) {
1487+ start_tracing ( ) ;
1488+ let ( _tp, tp_addr) = start_template_provider ( None , DifficultyLevel :: Low ) ;
1489+ let ( _pool, pool_addr) = start_pool ( sv2_tp_config ( tp_addr) , vec ! [ ] , vec ! [ ] ) . await ;
1490+ let ( pool_translator_sniffer, pool_translator_sniffer_addr) =
1491+ start_sniffer ( "0" , pool_addr, false , vec ! [ ] , None ) ;
1492+
1493+ // Start translator in non-aggregated mode (needed for user identity TLV)
1494+ let ( _, tproxy_addr) =
1495+ start_sv2_translator ( & [ pool_translator_sniffer_addr] , false , vec ! [ ] , vec ! [ ] , None ) . await ;
1496+
1497+ // Use a short username (8 chars) with a very long worker name (50+ chars)
1498+ // Total: "shortusr.very_long_worker_name_that_exceeds_32_bytes_limit_easily" = 65 chars
1499+ // This should be truncated to 32 bytes but still work
1500+ let long_worker_name = Some ( "shortuser.very_long_worker_name_that_exceeds_limit" . to_string ( ) ) ;
1501+
1502+ let ( _minerd_process, _minerd_addr) =
1503+ start_minerd ( tproxy_addr, long_worker_name, Some ( "x" . to_string ( ) ) , false ) . await ;
1504+
1505+ // Verify the translator doesn't panic and can successfully:
1506+ // 1. Complete setup connection
1507+ pool_translator_sniffer
1508+ . wait_for_message_type ( MessageDirection :: ToUpstream , MESSAGE_TYPE_SETUP_CONNECTION )
1509+ . await ;
1510+ pool_translator_sniffer
1511+ . wait_for_message_type (
1512+ MessageDirection :: ToDownstream ,
1513+ MESSAGE_TYPE_SETUP_CONNECTION_SUCCESS ,
1514+ )
1515+ . await ;
1516+
1517+ // 2. Open a mining channel
1518+ pool_translator_sniffer
1519+ . wait_for_message_type (
1520+ MessageDirection :: ToUpstream ,
1521+ MESSAGE_TYPE_OPEN_EXTENDED_MINING_CHANNEL ,
1522+ )
1523+ . await ;
1524+ pool_translator_sniffer
1525+ . wait_for_message_type (
1526+ MessageDirection :: ToDownstream ,
1527+ MESSAGE_TYPE_OPEN_EXTENDED_MINING_CHANNEL_SUCCESS ,
1528+ )
1529+ . await ;
1530+
1531+ // 3. Receive a job
1532+ pool_translator_sniffer
1533+ . wait_for_message_type (
1534+ MessageDirection :: ToDownstream ,
1535+ MESSAGE_TYPE_NEW_EXTENDED_MINING_JOB ,
1536+ )
1537+ . await ;
1538+
1539+ // 4. Submit shares without panicking (this is the critical part -
1540+ // before the fix, this would panic with "UserIdentity exceeds 32 bytes")
1541+ pool_translator_sniffer
1542+ . wait_for_message_type (
1543+ MessageDirection :: ToUpstream ,
1544+ MESSAGE_TYPE_SUBMIT_SHARES_EXTENDED ,
1545+ )
1546+ . await ;
1547+ }
1548+
1549+ /// This test verifies that the translator rejects mining.authorize when the username
1550+ /// portion (before the '.') exceeds 32 bytes.
1551+ ///
1552+ /// The UserIdentity TLV field has a 32-byte limit. When a miner sends mining.authorize with
1553+ /// a username that exceeds this limit, the translator should reject the authorize request
1554+ /// by returning false, rather than accepting it and later panicking during share submission.
1555+ ///
1556+ /// This test uses a username that is 40 characters long (exceeds 32 byte limit).
1557+ #[ tokio:: test]
1558+ async fn translator_rejects_authorize_with_username_exceeding_32_bytes ( ) {
1559+ start_tracing ( ) ;
1560+ let ( _tp, tp_addr) = start_template_provider ( None , DifficultyLevel :: Low ) ;
1561+ let ( _pool, pool_addr) = start_pool ( sv2_tp_config ( tp_addr) , vec ! [ ] , vec ! [ ] ) . await ;
1562+ let ( _pool_translator_sniffer, pool_translator_sniffer_addr) =
1563+ start_sniffer ( "0" , pool_addr, false , vec ! [ ] , None ) ;
1564+
1565+ // Start translator in non-aggregated mode
1566+ let ( _, tproxy_addr) =
1567+ start_sv2_translator ( & [ pool_translator_sniffer_addr] , false , vec ! [ ] , vec ! [ ] , None ) . await ;
1568+
1569+ // Start SV1 sniffer to intercept mining.authorize request and response
1570+ let ( sv1_sniffer, sv1_sniffer_addr) = start_sv1_sniffer ( tproxy_addr) ;
1571+
1572+ // Use a username that exceeds 32 bytes (40 chars) - this should be rejected
1573+ // "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.worker" = 40 char username
1574+ let long_username = Some ( "a" . repeat ( 40 ) + ".worker" ) ;
1575+
1576+ let ( _minerd_process, _minerd_addr) = start_minerd (
1577+ sv1_sniffer_addr,
1578+ long_username,
1579+ Some ( "x" . to_string ( ) ) ,
1580+ false ,
1581+ )
1582+ . await ;
1583+
1584+ // Wait for the mining.authorize request from downstream (miner -> translator)
1585+ sv1_sniffer
1586+ . wait_for_message ( & [ "mining.authorize" ] , MessageDirection :: ToUpstream )
1587+ . await ;
1588+
1589+ // Wait for the mining.authorize response from translator (translator -> miner)
1590+ // The response contains "result": false when authorization is rejected
1591+ // We search for "false" in the response which indicates rejection
1592+ sv1_sniffer
1593+ . wait_for_message ( & [ "false" ] , MessageDirection :: ToDownstream )
1594+ . await ;
1595+
1596+ // Verify the response was indeed a rejection by checking for the authorize response
1597+ // The SV1 sniffer has captured the OkResponse with result: false
1598+ sv1_sniffer
1599+ . wait_and_assert (
1600+ SV1MessageFilter :: WithMessageId ( 2 ) , // mining.authorize is typically id=2 (after subscribe=1)
1601+ MessageDirection :: ToDownstream ,
1602+ |msg| {
1603+ if let sv1_api:: Message :: OkResponse ( res) = msg {
1604+ // The result should be false (authorization rejected)
1605+ assert_eq ! (
1606+ res. result,
1607+ corepc_node:: serde_json:: Value :: Bool ( false ) ,
1608+ "Expected mining.authorize to return false for username exceeding 32 bytes"
1609+ ) ;
1610+ } else {
1611+ panic ! ( "Expected OkResponse for mining.authorize, got {:?}" , msg) ;
1612+ }
1613+ } ,
1614+ )
1615+ . await ;
1616+ }
1617+
1618+ /// This test demonstrates that a miner with a valid short username (under 32 bytes)
1619+ /// but a long worker name gets authorized successfully, with the worker name truncated.
1620+ ///
1621+ /// Contrast with `translator_rejects_authorize_with_username_exceeding_32_bytes` which
1622+ /// shows that a long username (before the '.') causes rejection.
1623+ #[ tokio:: test]
1624+ async fn translator_accepts_authorize_with_short_username_long_worker ( ) {
1625+ start_tracing ( ) ;
1626+ let ( _tp, tp_addr) = start_template_provider ( None , DifficultyLevel :: Low ) ;
1627+ let ( _pool, pool_addr) = start_pool ( sv2_tp_config ( tp_addr) , vec ! [ ] , vec ! [ ] ) . await ;
1628+ let ( _pool_translator_sniffer, pool_translator_sniffer_addr) =
1629+ start_sniffer ( "0" , pool_addr, false , vec ! [ ] , None ) ;
1630+
1631+ // Start translator in non-aggregated mode
1632+ let ( _, tproxy_addr) =
1633+ start_sv2_translator ( & [ pool_translator_sniffer_addr] , false , vec ! [ ] , vec ! [ ] , None ) . await ;
1634+
1635+ // Start SV1 sniffer to intercept mining.authorize response
1636+ let ( sv1_sniffer, sv1_sniffer_addr) = start_sv1_sniffer ( tproxy_addr) ;
1637+
1638+ // Use a short username (10 chars) with a very long worker name (50 chars)
1639+ // Total = 61 chars, but username is only 10, so it should be accepted (and truncated)
1640+ let short_user_long_worker =
1641+ Some ( "shortuser1.this_is_a_very_long_worker_name_exceeding_limit" . to_string ( ) ) ;
1642+
1643+ let ( _minerd_process, _minerd_addr) = start_minerd (
1644+ sv1_sniffer_addr,
1645+ short_user_long_worker,
1646+ Some ( "x" . to_string ( ) ) ,
1647+ false ,
1648+ )
1649+ . await ;
1650+
1651+ // Wait for the mining.authorize request
1652+ sv1_sniffer
1653+ . wait_for_message ( & [ "mining.authorize" ] , MessageDirection :: ToUpstream )
1654+ . await ;
1655+
1656+ // Wait for the mining.authorize response and verify it returns true (accepted)
1657+ // despite the total length exceeding 32 bytes (only the username matters for validation)
1658+ sv1_sniffer
1659+ . wait_for_message ( & [ "true" ] , MessageDirection :: ToDownstream )
1660+ . await ;
1661+
1662+ // Verify the response was indeed an acceptance
1663+ sv1_sniffer
1664+ . wait_and_assert (
1665+ SV1MessageFilter :: WithMessageId ( 2 ) , // mining.authorize is typically id=2
1666+ MessageDirection :: ToDownstream ,
1667+ |msg| {
1668+ if let sv1_api:: Message :: OkResponse ( res) = msg {
1669+ // The result should be true (authorization accepted)
1670+ assert_eq ! (
1671+ res. result,
1672+ corepc_node:: serde_json:: Value :: Bool ( true ) ,
1673+ "Expected mining.authorize to return true for short username"
1674+ ) ;
1675+ } else {
1676+ panic ! ( "Expected OkResponse for mining.authorize, got {:?}" , msg) ;
1677+ }
1678+ } ,
1679+ )
1680+ . await ;
1681+
1682+ // Wait for mining.notify which proves the miner was successfully authorized
1683+ // and received a job
1684+ sv1_sniffer
1685+ . wait_for_message ( & [ "mining.notify" ] , MessageDirection :: ToDownstream )
1686+ . await ;
1687+
1688+ // If we get here without the translator panicking, the test passes.
1689+ // The worker name was truncated (logged as warning) but mining continues.
1690+ }
0 commit comments