|
1 | 1 | // This file contains integration tests for the `TranslatorSv2` module. |
2 | 2 | use integration_tests_sv2::{ |
3 | 3 | interceptor::{IgnoreMessage, MessageDirection, ReplaceMessage}, |
| 4 | + mock_roles::MockUpstream, |
4 | 5 | template_provider::DifficultyLevel, |
| 6 | + utils::get_available_address, |
5 | 7 | *, |
6 | 8 | }; |
7 | 9 | use stratum_apps::stratum_core::mining_sv2::*; |
8 | 10 |
|
9 | 11 | use stratum_apps::stratum_core::{ |
| 12 | + binary_sv2::{Seq0255, Sv2Option}, |
10 | 13 | common_messages_sv2::{ |
11 | | - SetupConnectionError, MESSAGE_TYPE_SETUP_CONNECTION, MESSAGE_TYPE_SETUP_CONNECTION_ERROR, |
12 | | - MESSAGE_TYPE_SETUP_CONNECTION_SUCCESS, |
| 14 | + SetupConnectionError, SetupConnectionSuccess, MESSAGE_TYPE_SETUP_CONNECTION, |
| 15 | + MESSAGE_TYPE_SETUP_CONNECTION_ERROR, MESSAGE_TYPE_SETUP_CONNECTION_SUCCESS, |
13 | 16 | }, |
14 | 17 | mining_sv2::{ |
15 | 18 | OpenMiningChannelError, MESSAGE_TYPE_OPEN_EXTENDED_MINING_CHANNEL, |
16 | 19 | MESSAGE_TYPE_OPEN_EXTENDED_MINING_CHANNEL_SUCCESS, |
17 | 20 | }, |
18 | | - parsers_sv2::{self, AnyMessage}, |
| 21 | + parsers_sv2::{self, AnyMessage, CommonMessages}, |
19 | 22 | template_distribution_sv2::MESSAGE_TYPE_SUBMIT_SOLUTION, |
20 | 23 | }; |
21 | 24 |
|
@@ -712,3 +715,178 @@ async fn non_aggregated_translator_correctly_deals_with_group_channels() { |
712 | 715 | } |
713 | 716 | } |
714 | 717 | } |
| 718 | + |
| 719 | +#[tokio::test] |
| 720 | +async fn non_aggregated_translator_handles_set_group_channel_message() { |
| 721 | + start_tracing(); |
| 722 | + |
| 723 | + let mock_upstream_addr = get_available_address(); |
| 724 | + let mock_upstream = MockUpstream::new(mock_upstream_addr); |
| 725 | + let send_to_tproxy = mock_upstream.start().await; |
| 726 | + |
| 727 | + let (sniffer, sniffer_addr) = start_sniffer("", mock_upstream_addr, false, vec![], None); |
| 728 | + |
| 729 | + let (_tproxy, tproxy_addr) = start_sv2_translator(&[sniffer_addr], false, vec![], vec![]).await; |
| 730 | + |
| 731 | + sniffer |
| 732 | + .wait_for_message_type_and_clean_queue( |
| 733 | + MessageDirection::ToUpstream, |
| 734 | + MESSAGE_TYPE_SETUP_CONNECTION, |
| 735 | + ) |
| 736 | + .await; |
| 737 | + |
| 738 | + let setup_connection_success = AnyMessage::Common(CommonMessages::SetupConnectionSuccess( |
| 739 | + SetupConnectionSuccess { |
| 740 | + used_version: 2, |
| 741 | + flags: 0, |
| 742 | + }, |
| 743 | + )); |
| 744 | + send_to_tproxy.send(setup_connection_success).await.unwrap(); |
| 745 | + |
| 746 | + const N_EXTENDED_CHANNELS: u32 = 10; |
| 747 | + const GROUP_CHANNEL_ID_A: u32 = 100; |
| 748 | + const GROUP_CHANNEL_ID_B: u32 = 200; |
| 749 | + |
| 750 | + // we need to keep references to each minerd |
| 751 | + // otherwise they would be dropped |
| 752 | + let mut minerd_vec = Vec::new(); |
| 753 | + |
| 754 | + // spawn minerd processes to force opening N_EXTENDED_CHANNELS extended channels |
| 755 | + for i in 0..N_EXTENDED_CHANNELS { |
| 756 | + let (minerd_process, _minerd_addr) = start_minerd(tproxy_addr, None, None, false).await; |
| 757 | + minerd_vec.push(minerd_process); |
| 758 | + |
| 759 | + sniffer |
| 760 | + .wait_for_message_type( |
| 761 | + MessageDirection::ToUpstream, |
| 762 | + MESSAGE_TYPE_OPEN_EXTENDED_MINING_CHANNEL, |
| 763 | + ) |
| 764 | + .await; |
| 765 | + let open_extended_mining_channel = match sniffer.next_message_from_downstream() { |
| 766 | + Some((_, AnyMessage::Mining(parsers_sv2::Mining::OpenExtendedMiningChannel(msg)))) => { |
| 767 | + msg |
| 768 | + } |
| 769 | + msg => panic!( |
| 770 | + "Expected OpenExtendedMiningChannel message, found: {:?}", |
| 771 | + msg |
| 772 | + ), |
| 773 | + }; |
| 774 | + |
| 775 | + let open_extended_mining_channel_success = |
| 776 | + AnyMessage::Mining(parsers_sv2::Mining::OpenExtendedMiningChannelSuccess( |
| 777 | + OpenExtendedMiningChannelSuccess { |
| 778 | + request_id: open_extended_mining_channel.request_id, |
| 779 | + channel_id: i, |
| 780 | + target: hex::decode( |
| 781 | + "0000137c578190689425e3ecf8449a1af39db0aed305d9206f45ac32fe8330fc", |
| 782 | + ) |
| 783 | + .unwrap() |
| 784 | + .try_into() |
| 785 | + .unwrap(), |
| 786 | + // full extranonce has a total of 8 bytes |
| 787 | + extranonce_size: 4, |
| 788 | + extranonce_prefix: vec![0x00, 0x01, 0x00, i as u8].try_into().unwrap(), |
| 789 | + group_channel_id: GROUP_CHANNEL_ID_A, |
| 790 | + }, |
| 791 | + )); |
| 792 | + send_to_tproxy |
| 793 | + .send(open_extended_mining_channel_success) |
| 794 | + .await |
| 795 | + .unwrap(); |
| 796 | + |
| 797 | + sniffer |
| 798 | + .wait_for_message_type_and_clean_queue( |
| 799 | + MessageDirection::ToDownstream, |
| 800 | + MESSAGE_TYPE_OPEN_EXTENDED_MINING_CHANNEL_SUCCESS, |
| 801 | + ) |
| 802 | + .await; |
| 803 | + } |
| 804 | + |
| 805 | + // half of the channels belong to GROUP_CHANNEL_ID_A |
| 806 | + let group_channel_a_ids = (0..N_EXTENDED_CHANNELS) |
| 807 | + .filter(|i| i % 2 != 0) |
| 808 | + .collect::<Vec<_>>(); |
| 809 | + |
| 810 | + // half of the channels belong to GROUP_CHANNEL_ID_B |
| 811 | + let group_channel_b_ids = (0..N_EXTENDED_CHANNELS) |
| 812 | + .filter(|i| i % 2 == 0) |
| 813 | + .collect::<Vec<_>>(); |
| 814 | + |
| 815 | + // send a SetGroupChannel message to set GROUP_CHANNEL_ID_B |
| 816 | + let set_group_channel = |
| 817 | + AnyMessage::Mining(parsers_sv2::Mining::SetGroupChannel(SetGroupChannel { |
| 818 | + channel_ids: group_channel_b_ids.clone().into(), |
| 819 | + group_channel_id: GROUP_CHANNEL_ID_B, |
| 820 | + })); |
| 821 | + send_to_tproxy.send(set_group_channel).await.unwrap(); |
| 822 | + |
| 823 | + // send a NewExtendedMiningJob + SetNewPrevHash message pair ONLY to GROUP_CHANNEL_ID_B |
| 824 | + let new_extended_mining_job = AnyMessage::Mining(parsers_sv2::Mining::NewExtendedMiningJob(NewExtendedMiningJob { |
| 825 | + channel_id: GROUP_CHANNEL_ID_B, |
| 826 | + job_id: 1, |
| 827 | + min_ntime: Sv2Option::new(None), |
| 828 | + version: 0x20000000, |
| 829 | + version_rolling_allowed: true, |
| 830 | + merkle_path: Seq0255::new(vec![]).unwrap(), |
| 831 | + // scriptSig for a total of 8 bytes of extranonce |
| 832 | + coinbase_tx_prefix: hex::decode("02000000010000000000000000000000000000000000000000000000000000000000000000ffffffff225200162f5374726174756d2056322053524920506f6f6c2f2f08").unwrap().try_into().unwrap(), |
| 833 | + coinbase_tx_suffix: hex::decode("feffffff0200f2052a01000000160014ebe1b7dcc293ccaa0ee743a86f89df8258c208fc0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf901000000").unwrap().try_into().unwrap(), |
| 834 | + })); |
| 835 | + |
| 836 | + send_to_tproxy.send(new_extended_mining_job).await.unwrap(); |
| 837 | + sniffer |
| 838 | + .wait_for_message_type_and_clean_queue( |
| 839 | + MessageDirection::ToDownstream, |
| 840 | + MESSAGE_TYPE_NEW_EXTENDED_MINING_JOB, |
| 841 | + ) |
| 842 | + .await; |
| 843 | + |
| 844 | + let set_new_prev_hash = |
| 845 | + AnyMessage::Mining(parsers_sv2::Mining::SetNewPrevHash(SetNewPrevHash { |
| 846 | + channel_id: GROUP_CHANNEL_ID_B, |
| 847 | + job_id: 1, |
| 848 | + prev_hash: hex::decode( |
| 849 | + "3ab7089cd2cd30f133552cfde82c4cb239cd3c2310306f9d825e088a1772cc39", |
| 850 | + ) |
| 851 | + .unwrap() |
| 852 | + .try_into() |
| 853 | + .unwrap(), |
| 854 | + min_ntime: 1766782170, |
| 855 | + nbits: 0x207fffff, |
| 856 | + })); |
| 857 | + send_to_tproxy.send(set_new_prev_hash).await.unwrap(); |
| 858 | + sniffer |
| 859 | + .wait_for_message_type_and_clean_queue( |
| 860 | + MessageDirection::ToDownstream, |
| 861 | + MESSAGE_TYPE_MINING_SET_NEW_PREV_HASH, |
| 862 | + ) |
| 863 | + .await; |
| 864 | + |
| 865 | + // all channels in GROUP_CHANNEL_ID_B must submit at least one share |
| 866 | + // channels in GROUP_CHANNEL_ID_A must NOT submit any shares |
| 867 | + let mut channels_submitted_to: HashSet<u32> = group_channel_b_ids.clone().into_iter().collect(); |
| 868 | + loop { |
| 869 | + sniffer |
| 870 | + .wait_for_message_type( |
| 871 | + MessageDirection::ToUpstream, |
| 872 | + MESSAGE_TYPE_SUBMIT_SHARES_EXTENDED, |
| 873 | + ) |
| 874 | + .await; |
| 875 | + let submit_shares_extended = match sniffer.next_message_from_downstream() { |
| 876 | + Some((_, AnyMessage::Mining(parsers_sv2::Mining::SubmitSharesExtended(msg)))) => msg, |
| 877 | + msg => panic!("Expected SubmitSharesExtended message, found: {:?}", msg), |
| 878 | + }; |
| 879 | + |
| 880 | + if group_channel_a_ids.contains(&submit_shares_extended.channel_id) { |
| 881 | + panic!( |
| 882 | + "Channel {} should not have submitted a share", |
| 883 | + submit_shares_extended.channel_id |
| 884 | + ); |
| 885 | + } |
| 886 | + |
| 887 | + channels_submitted_to.remove(&submit_shares_extended.channel_id); |
| 888 | + if channels_submitted_to.is_empty() { |
| 889 | + break; |
| 890 | + } |
| 891 | + } |
| 892 | +} |
0 commit comments