Skip to content

Commit 190ec4c

Browse files
committed
test: add integration tests for long worker name truncation
1 parent 5a1565b commit 190ec4c

1 file changed

Lines changed: 215 additions & 0 deletions

File tree

integration-tests/tests/translator_integration.rs

Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)