@@ -1205,7 +1205,7 @@ impl PeerDB {
1205
1205
peer_addr : & PeerAddress ,
1206
1206
peer_port : u16 ,
1207
1207
) -> Result < ( ) , db_error > {
1208
- tx. execute ( "UPDATE frontier SET initial = 1 WHERE network_id = ?1 AND addrbytes = ?2 AND port = ?3" ,
1208
+ tx. execute ( "UPDATE frontier SET initial = 1 WHERE network_id = ?1 AND addrbytes = ?2 AND port = ?3" ,
1209
1209
params ! [ network_id, peer_addr. to_bin( ) , peer_port] )
1210
1210
. map_err ( db_error:: SqliteError ) ?;
1211
1211
@@ -1695,7 +1695,14 @@ impl PeerDB {
1695
1695
1696
1696
if always_include_allowed {
1697
1697
// always include allowed neighbors, freshness be damned
1698
- let allow_qry = "SELECT * FROM frontier WHERE network_id = ?1 AND denied < ?2 AND (allowed < 0 OR ?3 < allowed) AND (peer_version & 0x000000ff) >= ?4" ;
1698
+ let allow_qry = r#"
1699
+ SELECT *
1700
+ FROM frontier
1701
+ WHERE network_id = ?1
1702
+ AND denied < ?2
1703
+ AND (allowed < 0 OR ?3 < allowed)
1704
+ AND (peer_version & 0x000000ff) >= ?4"# ;
1705
+
1699
1706
let allow_args = params ! [
1700
1707
network_id,
1701
1708
u64_to_sql( now_secs) ?,
@@ -1716,18 +1723,34 @@ impl PeerDB {
1716
1723
if ( ret. len ( ) as u32 ) >= count {
1717
1724
return Ok ( ret) ;
1718
1725
}
1726
+ // In case we don't have enough allowed peers, fill in also with non-allowed, randomly-chosen, fresh peers
1719
1727
1720
- // fill in with non-allowed, randomly-chosen, fresh peers
1721
- let use_public = if public_only { "AND public = 1" } else { "" } ;
1728
+ // only include public peers if requested
1729
+ let use_public_condition = if public_only { "AND public = 1" } else { "" } ;
1722
1730
1723
- let random_peers_qry = if always_include_allowed {
1724
- format ! ( "SELECT * FROM frontier WHERE network_id = ?1 AND last_contact_time >= ?2 AND ?3 < expire_block_height AND denied < ?4 AND \
1725
- (allowed >= 0 AND allowed <= ?5) AND (peer_version & 0x000000ff) >= ?6 {use_public} ORDER BY RANDOM() LIMIT ?7")
1731
+ // If always_include_allowed is true, we've already collected all allowed peers above,
1732
+ // so exclude them from this query to avoid duplicates
1733
+ let include_allowed_condition = if always_include_allowed {
1734
+ "AND (allowed >= 0 AND allowed <= ?5)"
1726
1735
} else {
1727
- format ! ( "SELECT * FROM frontier WHERE network_id = ?1 AND last_contact_time >= ?2 AND ?3 < expire_block_height AND denied < ?4 AND \
1728
- (allowed < 0 OR (allowed >= 0 AND allowed <= ?5)) AND (peer_version & 0x000000ff) >= ?6 {use_public} ORDER BY RANDOM() LIMIT ?7")
1736
+ ""
1729
1737
} ;
1730
1738
1739
+ let random_peers_qry = format ! (
1740
+ r#"
1741
+ SELECT *
1742
+ FROM frontier
1743
+ WHERE network_id = ?1
1744
+ AND last_contact_time >= ?2
1745
+ AND ?3 < expire_block_height
1746
+ AND denied < ?4
1747
+ {include_allowed_condition}
1748
+ AND (peer_version & 0x000000ff) >= ?6
1749
+ {use_public_condition}
1750
+ ORDER BY RANDOM()
1751
+ LIMIT ?7"#
1752
+ ) ;
1753
+
1731
1754
let random_peers_args = params ! [
1732
1755
network_id,
1733
1756
u64_to_sql( min_age) ?,
@@ -1873,12 +1896,18 @@ impl PeerDB {
1873
1896
1874
1897
#[ cfg( any( test, feature = "testing" ) ) ]
1875
1898
mod test {
1899
+ use std:: collections:: HashSet ;
1900
+
1876
1901
use clarity:: vm:: types:: { StacksAddressExtensions , StandardPrincipalData } ;
1877
1902
use stacks_common:: types:: chainstate:: StacksAddress ;
1878
1903
use stacks_common:: types:: net:: { PeerAddress , PeerHost } ;
1879
1904
use stacks_common:: util:: hash:: Hash160 ;
1880
1905
1881
1906
use super :: * ;
1907
+ use crate :: core:: {
1908
+ NETWORK_ID_MAINNET , PEER_VERSION_EPOCH_2_0 , PEER_VERSION_EPOCH_3_0 , PEER_VERSION_EPOCH_3_1 ,
1909
+ PEER_VERSION_TESTNET_MAJOR ,
1910
+ } ;
1882
1911
use crate :: net:: { Neighbor , NeighborKey } ;
1883
1912
1884
1913
impl PeerDB {
@@ -3809,4 +3838,288 @@ mod test {
3809
3838
assert_eq ! ( peer. addr. port, 1033 ) ;
3810
3839
assert_eq ! ( peer. last_contact_time, 1552509651 ) ;
3811
3840
}
3841
+
3842
+ #[ test]
3843
+ fn test_get_fresh_random_neighbors_allowed_logic ( ) {
3844
+ let now_secs = util:: get_epoch_time_secs ( ) ;
3845
+ let current_block_height = 1000 ;
3846
+
3847
+ let network_id = NETWORK_ID_MAINNET ;
3848
+ let query_network_epoch_param = PEER_VERSION_EPOCH_3_0 ; // Query for peers supporting at least 3.0
3849
+
3850
+ let min_age_fresh = now_secs - 7200 ; // Fresh if contacted in last 2 hours
3851
+
3852
+ let mut db =
3853
+ PeerDB :: connect_memory ( network_id, 0 , 0 , "http://test.com" . into ( ) , & [ ] , & [ ] ) . unwrap ( ) ;
3854
+
3855
+ let base_neighbor = Neighbor {
3856
+ addr : NeighborKey {
3857
+ peer_version : PEER_VERSION_TESTNET_MAJOR | PEER_VERSION_EPOCH_3_0 as u32 , // Default to a matching epoch
3858
+ network_id,
3859
+ addrbytes : PeerAddress :: from_ipv4 ( 127 , 0 , 0 , 1 ) ,
3860
+ port : 10000 , // Will change per peer
3861
+ } ,
3862
+ public_key : Secp256k1PublicKey :: from_private ( & Secp256k1PrivateKey :: random ( ) ) ,
3863
+ expire_block : current_block_height + 100 ,
3864
+ last_contact_time : now_secs - 10 ,
3865
+ allowed : 0 ,
3866
+ denied : 0 ,
3867
+ asn : 123 ,
3868
+ org : 456 ,
3869
+ in_degree : 1 ,
3870
+ out_degree : 1 ,
3871
+ } ;
3872
+
3873
+ let mut peers_to_insert = Vec :: new ( ) ;
3874
+
3875
+ // 1. Always Allowed (Fresh, Epoch 3.0)
3876
+ let mut n_always_allowed = base_neighbor. clone ( ) ;
3877
+ n_always_allowed. addr . port = 10001 ;
3878
+ n_always_allowed. addr . peer_version =
3879
+ PEER_VERSION_TESTNET_MAJOR | PEER_VERSION_EPOCH_3_0 as u32 ;
3880
+ n_always_allowed. public_key =
3881
+ Secp256k1PublicKey :: from_private ( & Secp256k1PrivateKey :: random ( ) ) ;
3882
+ n_always_allowed. allowed = -1 ;
3883
+ peers_to_insert. push ( n_always_allowed. clone ( ) ) ;
3884
+
3885
+ // 2. Temporarily Allowed - Valid (Fresh, Epoch 3.1 - newer)
3886
+ let mut n_temp_allowed_valid = base_neighbor. clone ( ) ;
3887
+ n_temp_allowed_valid. addr . port = 10002 ;
3888
+ n_temp_allowed_valid. addr . peer_version =
3889
+ PEER_VERSION_TESTNET_MAJOR | PEER_VERSION_EPOCH_3_1 as u32 ;
3890
+ n_temp_allowed_valid. public_key =
3891
+ Secp256k1PublicKey :: from_private ( & Secp256k1PrivateKey :: random ( ) ) ;
3892
+ n_temp_allowed_valid. allowed = ( now_secs + 3600 ) as i64 ;
3893
+ peers_to_insert. push ( n_temp_allowed_valid. clone ( ) ) ;
3894
+
3895
+ // 3. Temporarily Allowed - Expired (Fresh, Epoch 3.0)
3896
+ let mut n_temp_allowed_expired = base_neighbor. clone ( ) ;
3897
+ n_temp_allowed_expired. addr . port = 10003 ;
3898
+ n_temp_allowed_expired. addr . peer_version =
3899
+ PEER_VERSION_TESTNET_MAJOR | PEER_VERSION_EPOCH_3_0 as u32 ;
3900
+ n_temp_allowed_expired. public_key =
3901
+ Secp256k1PublicKey :: from_private ( & Secp256k1PrivateKey :: random ( ) ) ;
3902
+ n_temp_allowed_expired. allowed = ( now_secs - 3600 ) as i64 ;
3903
+ peers_to_insert. push ( n_temp_allowed_expired. clone ( ) ) ;
3904
+
3905
+ // 4. Neutral (allowed = 0) (Fresh, Epoch 3.0)
3906
+ let mut n_neutral = base_neighbor. clone ( ) ;
3907
+ n_neutral. addr . port = 10004 ;
3908
+ n_neutral. addr . peer_version = PEER_VERSION_TESTNET_MAJOR | PEER_VERSION_EPOCH_3_0 as u32 ;
3909
+ n_neutral. public_key = Secp256k1PublicKey :: from_private ( & Secp256k1PrivateKey :: random ( ) ) ;
3910
+ n_neutral. allowed = 0 ;
3911
+ peers_to_insert. push ( n_neutral. clone ( ) ) ;
3912
+
3913
+ // 5. Denied (Fresh, Epoch 3.0) - Should not be picked
3914
+ let mut n_denied = base_neighbor. clone ( ) ;
3915
+ n_denied. addr . port = 10005 ;
3916
+ n_denied. addr . peer_version = PEER_VERSION_TESTNET_MAJOR | PEER_VERSION_EPOCH_3_0 as u32 ;
3917
+ n_denied. public_key = Secp256k1PublicKey :: from_private ( & Secp256k1PrivateKey :: random ( ) ) ;
3918
+ n_denied. denied = ( now_secs + 3600 ) as i64 ;
3919
+ peers_to_insert. push ( n_denied. clone ( ) ) ;
3920
+
3921
+ // 6. Denied - Expired (Effectively NOT Denied) (Fresh, Epoch 3.0)
3922
+ let mut n_denied_expired = base_neighbor. clone ( ) ;
3923
+ n_denied_expired. addr . port = 10006 ;
3924
+ n_denied_expired. addr . peer_version =
3925
+ PEER_VERSION_TESTNET_MAJOR | PEER_VERSION_EPOCH_3_0 as u32 ;
3926
+ n_denied_expired. public_key =
3927
+ Secp256k1PublicKey :: from_private ( & Secp256k1PrivateKey :: random ( ) ) ;
3928
+ n_denied_expired. denied = ( now_secs - 3600 ) as i64 ;
3929
+ peers_to_insert. push ( n_denied_expired. clone ( ) ) ;
3930
+
3931
+ // 7. Always Allowed - But actually Denied (Fresh, Epoch 3.0) - Should not be picked
3932
+ let mut n_always_allowed_denied = base_neighbor. clone ( ) ;
3933
+ n_always_allowed_denied. addr . port = 10007 ;
3934
+ n_always_allowed_denied. addr . peer_version =
3935
+ PEER_VERSION_TESTNET_MAJOR | PEER_VERSION_EPOCH_3_0 as u32 ;
3936
+ n_always_allowed_denied. public_key =
3937
+ Secp256k1PublicKey :: from_private ( & Secp256k1PrivateKey :: random ( ) ) ;
3938
+ n_always_allowed_denied. allowed = -1 ;
3939
+ n_always_allowed_denied. denied = ( now_secs + 3600 ) as i64 ;
3940
+ peers_to_insert. push ( n_always_allowed_denied. clone ( ) ) ;
3941
+
3942
+ // 8. Temp Allowed Valid - But actually Denied (Fresh, Epoch 3.0) - Should not be picked
3943
+ let mut n_temp_allowed_valid_denied = base_neighbor. clone ( ) ;
3944
+ n_temp_allowed_valid_denied. addr . port = 10008 ;
3945
+ n_temp_allowed_valid_denied. addr . peer_version =
3946
+ PEER_VERSION_TESTNET_MAJOR | PEER_VERSION_EPOCH_3_0 as u32 ;
3947
+ n_temp_allowed_valid_denied. public_key =
3948
+ Secp256k1PublicKey :: from_private ( & Secp256k1PrivateKey :: random ( ) ) ;
3949
+ n_temp_allowed_valid_denied. allowed = ( now_secs + 3600 ) as i64 ;
3950
+ n_temp_allowed_valid_denied. denied = ( now_secs + 3600 ) as i64 ;
3951
+ peers_to_insert. push ( n_temp_allowed_valid_denied. clone ( ) ) ;
3952
+
3953
+ // 9. Not Fresh LCT - But Always Allowed (Epoch 3.0) (For always_include_allowed=true test)
3954
+ let mut n_not_fresh_lct_always_allowed = base_neighbor. clone ( ) ;
3955
+ n_not_fresh_lct_always_allowed. addr . port = 10009 ;
3956
+ n_not_fresh_lct_always_allowed. addr . peer_version =
3957
+ PEER_VERSION_TESTNET_MAJOR | PEER_VERSION_EPOCH_3_0 as u32 ;
3958
+ n_not_fresh_lct_always_allowed. public_key =
3959
+ Secp256k1PublicKey :: from_private ( & Secp256k1PrivateKey :: random ( ) ) ;
3960
+ n_not_fresh_lct_always_allowed. allowed = -1 ;
3961
+ n_not_fresh_lct_always_allowed. last_contact_time = min_age_fresh - 10 ;
3962
+ peers_to_insert. push ( n_not_fresh_lct_always_allowed. clone ( ) ) ;
3963
+
3964
+ // 10. Not Fresh Expire - But Temp Allowed Valid (Epoch 3.0) (For always_include_allowed=true test)
3965
+ let mut n_not_fresh_expire_temp_allowed = base_neighbor. clone ( ) ;
3966
+ n_not_fresh_expire_temp_allowed. addr . port = 10010 ;
3967
+ n_not_fresh_expire_temp_allowed. addr . peer_version =
3968
+ PEER_VERSION_TESTNET_MAJOR | PEER_VERSION_EPOCH_3_0 as u32 ;
3969
+ n_not_fresh_expire_temp_allowed. public_key =
3970
+ Secp256k1PublicKey :: from_private ( & Secp256k1PrivateKey :: random ( ) ) ;
3971
+ n_not_fresh_expire_temp_allowed. allowed = ( now_secs + 3600 ) as i64 ;
3972
+ n_not_fresh_expire_temp_allowed. expire_block = current_block_height - 10 ;
3973
+ peers_to_insert. push ( n_not_fresh_expire_temp_allowed. clone ( ) ) ;
3974
+
3975
+ // 11. Old Epoch Peer (Fresh, Neutral) - Should be filtered by query_network_epoch_param
3976
+ let mut n_old_epoch_peer = base_neighbor. clone ( ) ;
3977
+ n_old_epoch_peer. addr . port = 10012 ; // New port for this peer
3978
+ n_old_epoch_peer. addr . peer_version =
3979
+ PEER_VERSION_TESTNET_MAJOR | PEER_VERSION_EPOCH_2_0 as u32 ; // Older epoch
3980
+ n_old_epoch_peer. public_key =
3981
+ Secp256k1PublicKey :: from_private ( & Secp256k1PrivateKey :: random ( ) ) ;
3982
+ n_old_epoch_peer. allowed = 0 ; // Neutral
3983
+ n_old_epoch_peer. denied = 0 ; // Not denied
3984
+ n_old_epoch_peer. last_contact_time = now_secs - 10 ; // Fresh LCT
3985
+ n_old_epoch_peer. expire_block = current_block_height + 100 ; // Fresh expire
3986
+ peers_to_insert. push ( n_old_epoch_peer. clone ( ) ) ;
3987
+
3988
+ {
3989
+ let tx = db. tx_begin ( ) . unwrap ( ) ;
3990
+ for peer in & peers_to_insert {
3991
+ assert ! ( PeerDB :: try_insert_peer( & tx, peer, & [ ] ) . unwrap( ) ) ;
3992
+ }
3993
+ tx. commit ( ) . unwrap ( ) ;
3994
+ }
3995
+
3996
+ // --- Test Case 1: always_include_allowed = false ---
3997
+ // Expected to pick from fresh, non-denied peers matching query_network_epoch_param.
3998
+ // Candidates: n_always_allowed, n_temp_allowed_valid, n_temp_allowed_expired, n_neutral, n_denied_expired (5 total)
3999
+ // n_old_epoch_peer is filtered out due to its older epoch.
4000
+ let count_false = 5 ;
4001
+ let results_false = PeerDB :: get_fresh_random_neighbors (
4002
+ db. conn ( ) ,
4003
+ network_id,
4004
+ query_network_epoch_param,
4005
+ min_age_fresh,
4006
+ count_false,
4007
+ current_block_height,
4008
+ false ,
4009
+ false ,
4010
+ )
4011
+ . unwrap ( ) ;
4012
+
4013
+ assert_eq ! ( results_false. len( ) as u32 , count_false, "Should get all fresh, non-denied peers matching epoch for always_include_allowed=false" ) ;
4014
+
4015
+ let result_ports_false: HashSet < u16 > = results_false. iter ( ) . map ( |p| p. addr . port ) . collect ( ) ;
4016
+ let expected_candidate_ports_false: HashSet < u16 > = [
4017
+ n_always_allowed. addr . port ,
4018
+ n_temp_allowed_valid. addr . port ,
4019
+ n_temp_allowed_expired. addr . port ,
4020
+ n_neutral. addr . port ,
4021
+ n_denied_expired. addr . port ,
4022
+ ]
4023
+ . iter ( )
4024
+ . cloned ( )
4025
+ . collect ( ) ;
4026
+
4027
+ assert_eq ! (
4028
+ result_ports_false, expected_candidate_ports_false,
4029
+ "Mismatch in candidates for always_include_allowed=false with epoch filtering"
4030
+ ) ;
4031
+ assert ! (
4032
+ !result_ports_false. contains( & n_old_epoch_peer. addr. port) ,
4033
+ "Old epoch peer should be filtered out"
4034
+ ) ;
4035
+
4036
+ // --- Test Case 2: always_include_allowed = true ---
4037
+ // Phase 1 (allow_qry picks, matching query_network_epoch_param):
4038
+ // - n_always_allowed (Fresh, Allowed<0, Epoch 3.0) -> YES
4039
+ // - n_temp_allowed_valid (Fresh, TempAllowedValid, Epoch 3.1) -> YES
4040
+ // - n_not_fresh_lct_always_allowed (NotFreshLCT, Allowed<0, Epoch 3.0) -> YES
4041
+ // - n_not_fresh_expire_temp_allowed (NotFreshExpire, TempAllowedValid, Epoch 3.0) -> YES
4042
+ // Phase 1 count = 4.
4043
+ // Phase 2 (random_peers_qry with include_allowed_condition, freshness from params, and matching query_network_epoch_param):
4044
+ // - n_temp_allowed_expired (Fresh, TempAllowedExpired, Epoch 3.0) -> YES
4045
+ // - n_neutral (Fresh, Neutral, Epoch 3.0) -> YES
4046
+ // - n_denied_expired (Fresh, DeniedExpired, NeutralAllowed, Epoch 3.0) -> YES
4047
+ // Phase 2 count = 3.
4048
+ // Total possible unique peers = 7 (n_old_epoch_peer still filtered out)
4049
+
4050
+ let count_true = 7 ;
4051
+ let results_true = PeerDB :: get_fresh_random_neighbors (
4052
+ db. conn ( ) ,
4053
+ network_id,
4054
+ query_network_epoch_param,
4055
+ min_age_fresh,
4056
+ count_true,
4057
+ current_block_height,
4058
+ true ,
4059
+ false ,
4060
+ )
4061
+ . unwrap ( ) ;
4062
+
4063
+ assert_eq ! (
4064
+ results_true. len( ) as u32 ,
4065
+ count_true,
4066
+ "Should get all 7 expected peers for always_include_allowed=true with epoch filtering"
4067
+ ) ;
4068
+
4069
+ let result_ports_true: HashSet < u16 > = results_true. iter ( ) . map ( |p| p. addr . port ) . collect ( ) ;
4070
+ let expected_candidate_ports_true: HashSet < u16 > = [
4071
+ n_always_allowed. addr . port ,
4072
+ n_temp_allowed_valid. addr . port ,
4073
+ n_temp_allowed_expired. addr . port ,
4074
+ n_neutral. addr . port ,
4075
+ n_denied_expired. addr . port ,
4076
+ n_not_fresh_lct_always_allowed. addr . port ,
4077
+ n_not_fresh_expire_temp_allowed. addr . port ,
4078
+ ]
4079
+ . iter ( )
4080
+ . cloned ( )
4081
+ . collect ( ) ;
4082
+
4083
+ assert_eq ! (
4084
+ result_ports_true, expected_candidate_ports_true,
4085
+ "Mismatch in candidates for always_include_allowed=true with epoch filtering"
4086
+ ) ;
4087
+ assert ! (
4088
+ !result_ports_true. contains( & n_old_epoch_peer. addr. port) ,
4089
+ "Old epoch peer should be filtered out for always_include_allowed=true as well"
4090
+ ) ;
4091
+
4092
+ // Test Case 2 Small: Verify that when count is less than or equal to the number of Phase 1 candidates,
4093
+ // the result should only contain Phase 1 candidates (only allowed peers), when always_include_allowed=true.
4094
+ // This ensures we prioritize Phase 1 candidates over Phase 2 candidates when we have a limited count.
4095
+ let count_true_small = 4 ;
4096
+ let results_true_small = PeerDB :: get_fresh_random_neighbors (
4097
+ db. conn ( ) ,
4098
+ network_id,
4099
+ query_network_epoch_param,
4100
+ min_age_fresh,
4101
+ count_true_small,
4102
+ current_block_height,
4103
+ true ,
4104
+ false ,
4105
+ )
4106
+ . unwrap ( ) ;
4107
+ assert_eq ! ( results_true_small. len( ) as u32 , count_true_small) ;
4108
+ let result_ports_true_small: HashSet < u16 > =
4109
+ results_true_small. iter ( ) . map ( |p| p. addr . port ) . collect ( ) ;
4110
+
4111
+ let phase1_candidate_ports: HashSet < u16 > = [
4112
+ n_always_allowed. addr . port , // Epoch 3.0
4113
+ n_temp_allowed_valid. addr . port , // Epoch 3.1
4114
+ n_not_fresh_lct_always_allowed. addr . port , // Epoch 3.0
4115
+ n_not_fresh_expire_temp_allowed. addr . port , // Epoch 3.0
4116
+ ]
4117
+ . iter ( )
4118
+ . cloned ( )
4119
+ . collect ( ) ;
4120
+
4121
+ for port in result_ports_true_small {
4122
+ assert ! ( phase1_candidate_ports. contains( & port) , "Peers for always_include_allowed=true with small count should come from Phase 1 candidates (epoch filtered)" ) ;
4123
+ }
4124
+ }
3812
4125
}
0 commit comments