Skip to content

Commit f589e28

Browse files
committed
fix get_fresh_random_neighbors to include allowed neigbours
1 parent c125757 commit f589e28

File tree

1 file changed

+322
-9
lines changed

1 file changed

+322
-9
lines changed

stackslib/src/net/db.rs

Lines changed: 322 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1205,7 +1205,7 @@ impl PeerDB {
12051205
peer_addr: &PeerAddress,
12061206
peer_port: u16,
12071207
) -> 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",
12091209
params![network_id, peer_addr.to_bin(), peer_port])
12101210
.map_err(db_error::SqliteError)?;
12111211

@@ -1695,7 +1695,14 @@ impl PeerDB {
16951695

16961696
if always_include_allowed {
16971697
// 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+
16991706
let allow_args = params![
17001707
network_id,
17011708
u64_to_sql(now_secs)?,
@@ -1716,18 +1723,34 @@ impl PeerDB {
17161723
if (ret.len() as u32) >= count {
17171724
return Ok(ret);
17181725
}
1726+
// In case we don't have enough allowed peers, fill in also with non-allowed, randomly-chosen, fresh peers
17191727

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 { "" };
17221730

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)"
17261735
} 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+
""
17291737
};
17301738

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+
17311754
let random_peers_args = params![
17321755
network_id,
17331756
u64_to_sql(min_age)?,
@@ -1873,12 +1896,18 @@ impl PeerDB {
18731896

18741897
#[cfg(any(test, feature = "testing"))]
18751898
mod test {
1899+
use std::collections::HashSet;
1900+
18761901
use clarity::vm::types::{StacksAddressExtensions, StandardPrincipalData};
18771902
use stacks_common::types::chainstate::StacksAddress;
18781903
use stacks_common::types::net::{PeerAddress, PeerHost};
18791904
use stacks_common::util::hash::Hash160;
18801905

18811906
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+
};
18821911
use crate::net::{Neighbor, NeighborKey};
18831912

18841913
impl PeerDB {
@@ -3809,4 +3838,288 @@ mod test {
38093838
assert_eq!(peer.addr.port, 1033);
38103839
assert_eq!(peer.last_contact_time, 1552509651);
38113840
}
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+
}
38124125
}

0 commit comments

Comments
 (0)