|
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 std::collections::HashSet; |
10 | 12 | use stratum_apps::stratum_core::{ |
| 13 | + binary_sv2::{Seq0255, Sv2Option}, |
11 | 14 | common_messages_sv2::{ |
12 | | - SetupConnectionError, MESSAGE_TYPE_SETUP_CONNECTION, MESSAGE_TYPE_SETUP_CONNECTION_ERROR, |
13 | | - MESSAGE_TYPE_SETUP_CONNECTION_SUCCESS, |
| 15 | + SetupConnectionError, SetupConnectionSuccess, MESSAGE_TYPE_SETUP_CONNECTION, |
| 16 | + MESSAGE_TYPE_SETUP_CONNECTION_ERROR, MESSAGE_TYPE_SETUP_CONNECTION_SUCCESS, |
14 | 17 | }, |
15 | 18 | mining_sv2::{ |
16 | 19 | OpenMiningChannelError, MESSAGE_TYPE_OPEN_EXTENDED_MINING_CHANNEL, |
17 | 20 | MESSAGE_TYPE_OPEN_EXTENDED_MINING_CHANNEL_SUCCESS, |
18 | 21 | }, |
19 | | - parsers_sv2::{self, AnyMessage}, |
| 22 | + parsers_sv2::{self, AnyMessage, CommonMessages}, |
20 | 23 | template_distribution_sv2::MESSAGE_TYPE_SUBMIT_SOLUTION, |
21 | 24 | }; |
22 | 25 |
|
@@ -727,3 +730,226 @@ async fn non_aggregated_translator_correctly_deals_with_group_channels() { |
727 | 730 | } |
728 | 731 | } |
729 | 732 | } |
| 733 | + |
| 734 | +#[tokio::test] |
| 735 | +async fn non_aggregated_translator_handles_set_group_channel_message() { |
| 736 | + start_tracing(); |
| 737 | + |
| 738 | + let mock_upstream_addr = get_available_address(); |
| 739 | + let mock_upstream = MockUpstream::new(mock_upstream_addr); |
| 740 | + let send_to_tproxy = mock_upstream.start().await; |
| 741 | + |
| 742 | + let (sniffer, sniffer_addr) = start_sniffer("", mock_upstream_addr, false, vec![], None); |
| 743 | + |
| 744 | + let (_tproxy, tproxy_addr) = start_sv2_translator(&[sniffer_addr], false, vec![], vec![]).await; |
| 745 | + |
| 746 | + sniffer |
| 747 | + .wait_for_message_type_and_clean_queue( |
| 748 | + MessageDirection::ToUpstream, |
| 749 | + MESSAGE_TYPE_SETUP_CONNECTION, |
| 750 | + ) |
| 751 | + .await; |
| 752 | + |
| 753 | + let setup_connection_success = AnyMessage::Common(CommonMessages::SetupConnectionSuccess( |
| 754 | + SetupConnectionSuccess { |
| 755 | + used_version: 2, |
| 756 | + flags: 0, |
| 757 | + }, |
| 758 | + )); |
| 759 | + send_to_tproxy.send(setup_connection_success).await.unwrap(); |
| 760 | + |
| 761 | + const N_EXTENDED_CHANNELS: u32 = 6; |
| 762 | + const GROUP_CHANNEL_ID_A: u32 = 100; |
| 763 | + const GROUP_CHANNEL_ID_B: u32 = 200; |
| 764 | + |
| 765 | + // we need to keep references to each minerd |
| 766 | + // otherwise they would be dropped |
| 767 | + let mut minerd_vec = Vec::new(); |
| 768 | + |
| 769 | + // spawn minerd processes to force opening N_EXTENDED_CHANNELS extended channels |
| 770 | + for i in 0..N_EXTENDED_CHANNELS { |
| 771 | + let (minerd_process, _minerd_addr) = start_minerd(tproxy_addr, None, None, false).await; |
| 772 | + minerd_vec.push(minerd_process); |
| 773 | + |
| 774 | + sniffer |
| 775 | + .wait_for_message_type( |
| 776 | + MessageDirection::ToUpstream, |
| 777 | + MESSAGE_TYPE_OPEN_EXTENDED_MINING_CHANNEL, |
| 778 | + ) |
| 779 | + .await; |
| 780 | + let open_extended_mining_channel: OpenExtendedMiningChannel = loop { |
| 781 | + match sniffer.next_message_from_downstream() { |
| 782 | + Some(( |
| 783 | + _, |
| 784 | + AnyMessage::Mining(parsers_sv2::Mining::OpenExtendedMiningChannel(msg)), |
| 785 | + )) => { |
| 786 | + break msg; |
| 787 | + } |
| 788 | + _ => continue, |
| 789 | + }; |
| 790 | + }; |
| 791 | + |
| 792 | + let open_extended_mining_channel_success = |
| 793 | + AnyMessage::Mining(parsers_sv2::Mining::OpenExtendedMiningChannelSuccess( |
| 794 | + OpenExtendedMiningChannelSuccess { |
| 795 | + request_id: open_extended_mining_channel.request_id, |
| 796 | + channel_id: i, |
| 797 | + target: hex::decode( |
| 798 | + "0000137c578190689425e3ecf8449a1af39db0aed305d9206f45ac32fe8330fc", |
| 799 | + ) |
| 800 | + .unwrap() |
| 801 | + .try_into() |
| 802 | + .unwrap(), |
| 803 | + // full extranonce has a total of 8 bytes |
| 804 | + extranonce_size: 4, |
| 805 | + extranonce_prefix: vec![0x00, 0x01, 0x00, i as u8].try_into().unwrap(), |
| 806 | + group_channel_id: GROUP_CHANNEL_ID_A, |
| 807 | + }, |
| 808 | + )); |
| 809 | + send_to_tproxy |
| 810 | + .send(open_extended_mining_channel_success) |
| 811 | + .await |
| 812 | + .unwrap(); |
| 813 | + |
| 814 | + sniffer |
| 815 | + .wait_for_message_type_and_clean_queue( |
| 816 | + MessageDirection::ToDownstream, |
| 817 | + MESSAGE_TYPE_OPEN_EXTENDED_MINING_CHANNEL_SUCCESS, |
| 818 | + ) |
| 819 | + .await; |
| 820 | + |
| 821 | + let new_extended_mining_job = AnyMessage::Mining(parsers_sv2::Mining::NewExtendedMiningJob(NewExtendedMiningJob { |
| 822 | + channel_id: i, |
| 823 | + job_id: 1, |
| 824 | + min_ntime: Sv2Option::new(None), |
| 825 | + version: 0x20000000, |
| 826 | + version_rolling_allowed: true, |
| 827 | + merkle_path: Seq0255::new(vec![]).unwrap(), |
| 828 | + // scriptSig for a total of 8 bytes of extranonce |
| 829 | + coinbase_tx_prefix: hex::decode("02000000010000000000000000000000000000000000000000000000000000000000000000ffffffff225200162f5374726174756d2056322053524920506f6f6c2f2f08").unwrap().try_into().unwrap(), |
| 830 | + coinbase_tx_suffix: hex::decode("feffffff0200f2052a01000000160014ebe1b7dcc293ccaa0ee743a86f89df8258c208fc0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf901000000").unwrap().try_into().unwrap(), |
| 831 | + })); |
| 832 | + |
| 833 | + send_to_tproxy.send(new_extended_mining_job).await.unwrap(); |
| 834 | + sniffer |
| 835 | + .wait_for_message_type_and_clean_queue( |
| 836 | + MessageDirection::ToDownstream, |
| 837 | + MESSAGE_TYPE_NEW_EXTENDED_MINING_JOB, |
| 838 | + ) |
| 839 | + .await; |
| 840 | + |
| 841 | + let set_new_prev_hash = |
| 842 | + AnyMessage::Mining(parsers_sv2::Mining::SetNewPrevHash(SetNewPrevHash { |
| 843 | + channel_id: i, |
| 844 | + job_id: 1, |
| 845 | + prev_hash: hex::decode( |
| 846 | + "3ab7089cd2cd30f133552cfde82c4cb239cd3c2310306f9d825e088a1772cc39", |
| 847 | + ) |
| 848 | + .unwrap() |
| 849 | + .try_into() |
| 850 | + .unwrap(), |
| 851 | + min_ntime: 1766782170, |
| 852 | + nbits: 0x207fffff, |
| 853 | + })); |
| 854 | + |
| 855 | + send_to_tproxy.send(set_new_prev_hash).await.unwrap(); |
| 856 | + sniffer |
| 857 | + .wait_for_message_type_and_clean_queue( |
| 858 | + MessageDirection::ToDownstream, |
| 859 | + MESSAGE_TYPE_MINING_SET_NEW_PREV_HASH, |
| 860 | + ) |
| 861 | + .await; |
| 862 | + } |
| 863 | + |
| 864 | + // half of the channels belong to GROUP_CHANNEL_ID_A |
| 865 | + let group_channel_a_ids = (0..N_EXTENDED_CHANNELS) |
| 866 | + .filter(|i| i % 2 != 0) |
| 867 | + .collect::<Vec<_>>(); |
| 868 | + |
| 869 | + // half of the channels belong to GROUP_CHANNEL_ID_B |
| 870 | + let group_channel_b_ids = (0..N_EXTENDED_CHANNELS) |
| 871 | + .filter(|i| i % 2 == 0) |
| 872 | + .collect::<Vec<_>>(); |
| 873 | + |
| 874 | + // send a SetGroupChannel message to set GROUP_CHANNEL_ID_B |
| 875 | + let set_group_channel = |
| 876 | + AnyMessage::Mining(parsers_sv2::Mining::SetGroupChannel(SetGroupChannel { |
| 877 | + channel_ids: group_channel_b_ids.clone().into(), |
| 878 | + group_channel_id: GROUP_CHANNEL_ID_B, |
| 879 | + })); |
| 880 | + send_to_tproxy.send(set_group_channel).await.unwrap(); |
| 881 | + |
| 882 | + // send a NewExtendedMiningJob + SetNewPrevHash message pair ONLY to GROUP_CHANNEL_ID_B |
| 883 | + let new_extended_mining_job = AnyMessage::Mining(parsers_sv2::Mining::NewExtendedMiningJob(NewExtendedMiningJob { |
| 884 | + channel_id: GROUP_CHANNEL_ID_B, |
| 885 | + job_id: 2, |
| 886 | + min_ntime: Sv2Option::new(None), |
| 887 | + version: 0x20000000, |
| 888 | + version_rolling_allowed: true, |
| 889 | + merkle_path: Seq0255::new(vec![]).unwrap(), |
| 890 | + // scriptSig for a total of 8 bytes of extranonce |
| 891 | + coinbase_tx_prefix: hex::decode("02000000010000000000000000000000000000000000000000000000000000000000000000ffffffff225300162f5374726174756d2056322053524920506f6f6c2f2f08").unwrap().try_into().unwrap(), |
| 892 | + coinbase_tx_suffix: hex::decode("feffffff0200f2052a01000000160014ebe1b7dcc293ccaa0ee743a86f89df8258c208fc0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf901000000").unwrap().try_into().unwrap(), |
| 893 | + })); |
| 894 | + |
| 895 | + send_to_tproxy.send(new_extended_mining_job).await.unwrap(); |
| 896 | + sniffer |
| 897 | + .wait_for_message_type_and_clean_queue( |
| 898 | + MessageDirection::ToDownstream, |
| 899 | + MESSAGE_TYPE_NEW_EXTENDED_MINING_JOB, |
| 900 | + ) |
| 901 | + .await; |
| 902 | + |
| 903 | + let set_new_prev_hash = |
| 904 | + AnyMessage::Mining(parsers_sv2::Mining::SetNewPrevHash(SetNewPrevHash { |
| 905 | + channel_id: GROUP_CHANNEL_ID_B, |
| 906 | + job_id: 2, |
| 907 | + prev_hash: hex::decode( |
| 908 | + "2089973501ad229333ae0e9c52fa160f95616890db364a71ccfb77773a8b54cb", |
| 909 | + ) |
| 910 | + .unwrap() |
| 911 | + .try_into() |
| 912 | + .unwrap(), |
| 913 | + min_ntime: 1766782171, |
| 914 | + nbits: 0x207fffff, |
| 915 | + })); |
| 916 | + send_to_tproxy.send(set_new_prev_hash).await.unwrap(); |
| 917 | + sniffer |
| 918 | + .wait_for_message_type_and_clean_queue( |
| 919 | + MessageDirection::ToDownstream, |
| 920 | + MESSAGE_TYPE_MINING_SET_NEW_PREV_HASH, |
| 921 | + ) |
| 922 | + .await; |
| 923 | + |
| 924 | + // all channels in GROUP_CHANNEL_ID_B must submit at least one share with job_id = 2 |
| 925 | + // channels in GROUP_CHANNEL_ID_A must NOT submit any shares with job_id = 2 |
| 926 | + let mut channels_submitted_to: HashSet<u32> = group_channel_b_ids.clone().into_iter().collect(); |
| 927 | + loop { |
| 928 | + sniffer |
| 929 | + .wait_for_message_type( |
| 930 | + MessageDirection::ToUpstream, |
| 931 | + MESSAGE_TYPE_SUBMIT_SHARES_EXTENDED, |
| 932 | + ) |
| 933 | + .await; |
| 934 | + let submit_shares_extended = match sniffer.next_message_from_downstream() { |
| 935 | + Some((_, AnyMessage::Mining(parsers_sv2::Mining::SubmitSharesExtended(msg)))) => msg, |
| 936 | + msg => panic!("Expected SubmitSharesExtended message, found: {:?}", msg), |
| 937 | + }; |
| 938 | + |
| 939 | + if submit_shares_extended.job_id != 2 { |
| 940 | + continue; |
| 941 | + } |
| 942 | + |
| 943 | + if group_channel_a_ids.contains(&submit_shares_extended.channel_id) { |
| 944 | + panic!( |
| 945 | + "Channel {} should not have submitted a share with job_id = 2", |
| 946 | + submit_shares_extended.channel_id |
| 947 | + ); |
| 948 | + } |
| 949 | + |
| 950 | + channels_submitted_to.remove(&submit_shares_extended.channel_id); |
| 951 | + if channels_submitted_to.is_empty() { |
| 952 | + break; |
| 953 | + } |
| 954 | + } |
| 955 | +} |
0 commit comments