Proof of Concept
diff --git a/miner-apps/translator/src/lib/sv1/sv1_server/mod.rs b/miner-apps/translator/src/lib/sv1/sv1_server/mod.rs
--- a/miner-apps/translator/src/lib/sv1/sv1_server/mod.rs
+++ b/miner-apps/translator/src/lib/sv1/sv1_server/mod.rs
@@ -1630,4 +1630,40 @@ mod tests {
assert_eq!(seq_id, 1);
assert_eq!(server.sequence_counter.load(Ordering::SeqCst), 2);
}
+
+ #[tokio::test]
+ async fn test_open_success_for_disconnected_downstream_closes_orphaned_channel() {
+ use stratum_apps::stratum_core::mining_sv2::OpenExtendedMiningChannelSuccess;
+
+ let (channel_manager_sender, server_to_channel_manager_receiver) = unbounded();
+ let (channel_manager_to_server_sender, channel_manager_receiver) = unbounded();
+ let config = create_test_config();
+ let addr = "127.0.0.1:3333".parse().unwrap();
+ let tproxy_mode = TproxyMode::from(config.aggregate_channels);
+ let server = Sv1Server::new(addr, channel_manager_receiver, channel_manager_sender, config, tproxy_mode);
+ let target = hash_rate_to_target(200.0, 5.0).unwrap();
+
+ server.request_id_to_downstream_id.insert(42, 7);
+ channel_manager_to_server_sender
+ .send((
+ Mining::OpenExtendedMiningChannelSuccess(OpenExtendedMiningChannelSuccess {
+ request_id: 42,
+ channel_id: 9,
+ target: target.to_le_bytes().into(),
+ extranonce_size: 4,
+ extranonce_prefix: vec![0u8; 4].try_into().unwrap(),
+ group_channel_id: 0,
+ }),
+ None,
+ ))
+ .await
+ .unwrap();
+
+ server.handle_upstream_message(target).await.unwrap();
+
+ let (message, _) = server_to_channel_manager_receiver
+ .try_recv()
+ .expect("orphaned channel should be closed when its downstream vanished");
+ assert!(matches!(message, Mining::CloseChannel(close) if close.channel_id == 9));
+ }
}
Suggested Fix
diff --git a/miner-apps/translator/src/lib/sv1/sv1_server/mod.rs b/miner-apps/translator/src/lib/sv1/sv1_server/mod.rs
--- a/miner-apps/translator/src/lib/sv1/sv1_server/mod.rs
+++ b/miner-apps/translator/src/lib/sv1/sv1_server/mod.rs
@@ -854,6 +854,19 @@
}
} else {
error!("Downstream not found for downstream_id: {}", downstream_id);
+ let reason_code =
+ Str0255::try_from("downstream disconnected".to_string()).unwrap();
+ _ = self
+ .sv1_server_io
+ .channel_manager_sender
+ .send((
+ Mining::CloseChannel(CloseChannel {
+ channel_id: m.channel_id,
+ reason_code,
+ }),
+ None,
+ ))
+ .await;
}
}
Proof of Concept
Suggested Fix