Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(sdk)!: ban addresses failed in sdk #2351

Merged
merged 15 commits into from
Dec 4, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/rs-dapi-client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,6 @@ lru = { version = "0.12.3" }
serde = { version = "1.0.197", optional = true, features = ["derive"] }
serde_json = { version = "1.0.120", optional = true }
chrono = { version = "0.4.38", features = ["serde"] }

[dev-dependencies]
tokio = { version = "1.40", features = ["macros"] }
168 changes: 97 additions & 71 deletions packages/rs-dapi-client/src/address_list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,20 @@
use dapi_grpc::tonic::codegen::http;
use dapi_grpc::tonic::transport::Uri;
use rand::{rngs::SmallRng, seq::IteratorRandom, SeedableRng};
use std::collections::HashSet;
use std::collections::hash_map::Entry;
use std::collections::HashMap;
use std::hash::{Hash, Hasher};
use std::mem;
use std::str::FromStr;
use std::sync::{Arc, RwLock};
use std::time::Duration;

const DEFAULT_BASE_BAN_PERIOD: Duration = Duration::from_secs(60);

/// DAPI address.
#[derive(Debug, Clone, Eq)]
#[cfg_attr(feature = "mocks", derive(serde::Serialize, serde::Deserialize))]
pub struct Address {
ban_count: usize,
banned_until: Option<chrono::DateTime<Utc>>,
#[cfg_attr(feature = "mocks", serde(with = "http_serde::uri"))]
uri: Uri,
}
pub struct Address(#[cfg_attr(feature = "mocks", serde(with = "http_serde::uri"))] Uri);
lklimek marked this conversation as resolved.
Show resolved Hide resolved

impl FromStr for Address {
type Err = AddressListError;
Expand All @@ -33,35 +31,46 @@

impl PartialEq<Self> for Address {
fn eq(&self, other: &Self) -> bool {
self.uri == other.uri
self.0 == other.0
}
}

impl PartialEq<Uri> for Address {
fn eq(&self, other: &Uri) -> bool {
self.uri == *other
self.0 == *other
}
}

impl Hash for Address {
fn hash<H: Hasher>(&self, state: &mut H) {
self.uri.hash(state);
self.0.hash(state);
}
}

impl From<Uri> for Address {
fn from(uri: Uri) -> Self {
Address {
ban_count: 0,
banned_until: None,
uri,
}
Address(uri)
}
}

impl Address {
/// Get [Uri] of a node.
pub fn uri(&self) -> &Uri {
&self.0
}
}

/// Address status
/// Contains information about the number of bans and the time until the next ban is lifted.
#[derive(Debug, Default, Clone)]
pub struct AddressStatus {
ban_count: usize,
banned_until: Option<chrono::DateTime<Utc>>,
}

impl AddressStatus {
/// Ban the [Address] so it won't be available through [AddressList::get_live_address] for some time.
fn ban(&mut self, base_ban_period: &Duration) {
pub fn ban(&mut self, base_ban_period: &Duration) {
let coefficient = (self.ban_count as f64).exp();
let ban_period = Duration::from_secs_f64(base_ban_period.as_secs_f64() * coefficient);

Expand All @@ -75,24 +84,16 @@
}

/// Clears ban record.
fn unban(&mut self) {
pub fn unban(&mut self) {
self.ban_count = 0;
self.banned_until = None;
}

/// Get [Uri] of a node.
pub fn uri(&self) -> &Uri {
&self.uri
}
}

/// [AddressList] errors
#[derive(Debug, thiserror::Error)]
#[cfg_attr(feature = "mocks", derive(serde::Serialize, serde::Deserialize))]
pub enum AddressListError {
/// Specified address is not present in the list
#[error("address {0} not found in the list")]
AddressNotFound(#[cfg_attr(feature = "mocks", serde(with = "http_serde::uri"))] Uri),
/// A valid uri is required to create an Address
#[error("unable parse address: {0}")]
#[cfg_attr(feature = "mocks", serde(skip))]
Expand All @@ -103,7 +104,7 @@
/// for [DapiRequest](crate::DapiRequest) execution.
#[derive(Debug, Clone)]
pub struct AddressList {
addresses: HashSet<Address>,
addresses: Arc<RwLock<HashMap<Address, AddressStatus>>>,
base_ban_period: Duration,
}

Expand All @@ -115,7 +116,7 @@

impl std::fmt::Display for Address {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.uri.fmt(f)
self.0.fmt(f)
}
}

Expand All @@ -128,43 +129,70 @@
/// Creates an empty [AddressList] with adjustable base ban time.
pub fn with_settings(base_ban_period: Duration) -> Self {
AddressList {
addresses: HashSet::new(),
addresses: Arc::new(RwLock::new(HashMap::new())),
base_ban_period,
}
}

/// Bans address
pub(crate) fn ban_address(&mut self, address: &Address) -> Result<(), AddressListError> {
if !self.addresses.remove(address) {
return Err(AddressListError::AddressNotFound(address.uri.clone()));
};
/// Returns false if the address is not in the list.
pub fn ban(&self, address: &Address) -> bool {
let mut guard = self.addresses.write().unwrap();

let mut banned_address = address.clone();
banned_address.ban(&self.base_ban_period);
let Some(mut status) = guard.get_mut(address) else {

Check warning on line 142 in packages/rs-dapi-client/src/address_list.rs

View workflow job for this annotation

GitHub Actions / Rust packages (dash-sdk) / Linting

variable does not need to be mutable

warning: variable does not need to be mutable --> packages/rs-dapi-client/src/address_list.rs:142:18 | 142 | let Some(mut status) = guard.get_mut(address) else { | ----^^^^^^ | | | help: remove this `mut` | = note: `#[warn(unused_mut)]` on by default

Check warning on line 142 in packages/rs-dapi-client/src/address_list.rs

View workflow job for this annotation

GitHub Actions / Rust packages (rs-dapi-client) / Linting

variable does not need to be mutable

warning: variable does not need to be mutable --> packages/rs-dapi-client/src/address_list.rs:142:18 | 142 | let Some(mut status) = guard.get_mut(address) else { | ----^^^^^^ | | | help: remove this `mut` | = note: `#[warn(unused_mut)]` on by default
return false;
};

self.addresses.insert(banned_address);
status.ban(&self.base_ban_period);

Ok(())
true
}

/// Clears address' ban record
pub(crate) fn unban_address(&mut self, address: &Address) -> Result<(), AddressListError> {
if !self.addresses.remove(address) {
return Err(AddressListError::AddressNotFound(address.uri.clone()));
/// Returns false if the address is not in the list.
pub fn unban(&self, address: &Address) -> bool {
let mut guard = self.addresses.write().unwrap();

let Some(mut status) = guard.get_mut(address) else {

Check warning on line 156 in packages/rs-dapi-client/src/address_list.rs

View workflow job for this annotation

GitHub Actions / Rust packages (dash-sdk) / Linting

variable does not need to be mutable

warning: variable does not need to be mutable --> packages/rs-dapi-client/src/address_list.rs:156:18 | 156 | let Some(mut status) = guard.get_mut(address) else { | ----^^^^^^ | | | help: remove this `mut`

Check warning on line 156 in packages/rs-dapi-client/src/address_list.rs

View workflow job for this annotation

GitHub Actions / Rust packages (rs-dapi-client) / Linting

variable does not need to be mutable

warning: variable does not need to be mutable --> packages/rs-dapi-client/src/address_list.rs:156:18 | 156 | let Some(mut status) = guard.get_mut(address) else { | ----^^^^^^ | | | help: remove this `mut`
return false;
};

let mut unbanned_address = address.clone();
unbanned_address.unban();
status.unban();

true
}

self.addresses.insert(unbanned_address);
/// Check if the address is banned.
pub fn is_banned(&self, address: &Address) -> bool {
let guard = self.addresses.read().unwrap();

Ok(())
guard
.get(address)
.map(|status| status.is_banned())
.unwrap_or(false)
}

/// Adds a node [Address] to [AddressList]
/// Returns false if the address is already in the list.
pub fn add(&mut self, address: Address) -> bool {
self.addresses.insert(address)
let mut guard = self.addresses.write().unwrap();

match guard.entry(address) {
Entry::Occupied(_) => false,
Entry::Vacant(e) => {
e.insert(AddressStatus::default());

true
}
}
}

/// Remove address from the list
/// Returns [AddressStatus] if the address was in the list.
pub fn remove(&mut self, address: &Address) -> Option<AddressStatus> {
let mut guard = self.addresses.write().unwrap();

guard.remove(address)
}

// TODO: this is the most simple way to add an address
Expand All @@ -173,46 +201,53 @@
/// Add a node [Address] to [AddressList] by [Uri].
/// Returns false if the address is already in the list.
pub fn add_uri(&mut self, uri: Uri) -> bool {
self.addresses.insert(uri.into())
self.add(Address::from(uri))
}

/// Randomly select a not banned address.
pub fn get_live_address(&self) -> Option<&Address> {
let mut rng = SmallRng::from_entropy();
pub fn get_live_address(&self) -> Option<Address> {
let mut guard = self.addresses.read().unwrap();

Check warning on line 209 in packages/rs-dapi-client/src/address_list.rs

View workflow job for this annotation

GitHub Actions / Rust packages (dash-sdk) / Linting

variable does not need to be mutable

warning: variable does not need to be mutable --> packages/rs-dapi-client/src/address_list.rs:209:13 | 209 | let mut guard = self.addresses.read().unwrap(); | ----^^^^^ | | | help: remove this `mut`

Check warning on line 209 in packages/rs-dapi-client/src/address_list.rs

View workflow job for this annotation

GitHub Actions / Rust packages (rs-dapi-client) / Linting

variable does not need to be mutable

warning: variable does not need to be mutable --> packages/rs-dapi-client/src/address_list.rs:209:13 | 209 | let mut guard = self.addresses.read().unwrap(); | ----^^^^^ | | | help: remove this `mut`

self.unbanned().into_iter().choose(&mut rng)
}
let mut rng = SmallRng::from_entropy();

/// Get all addresses that are not banned.
fn unbanned(&self) -> Vec<&Address> {
let now = chrono::Utc::now();

self.addresses
guard
.iter()
.filter(|addr| {
addr.banned_until
.filter(|(addr, status)| {

Check warning on line 217 in packages/rs-dapi-client/src/address_list.rs

View workflow job for this annotation

GitHub Actions / Rust packages (dash-sdk) / Linting

unused variable: `addr`

warning: unused variable: `addr` --> packages/rs-dapi-client/src/address_list.rs:217:23 | 217 | .filter(|(addr, status)| { | ^^^^ help: if this is intentional, prefix it with an underscore: `_addr` | = note: `#[warn(unused_variables)]` on by default

Check warning on line 217 in packages/rs-dapi-client/src/address_list.rs

View workflow job for this annotation

GitHub Actions / Rust packages (rs-dapi-client) / Linting

unused variable: `addr`

warning: unused variable: `addr` --> packages/rs-dapi-client/src/address_list.rs:217:23 | 217 | .filter(|(addr, status)| { | ^^^^ help: if this is intentional, prefix it with an underscore: `_addr` | = note: `#[warn(unused_variables)]` on by default
status
.banned_until
.map(|banned_until| banned_until < now)
.unwrap_or(true)
})
.collect()
}

/// Get number of available, not banned addresses.
pub fn available(&self) -> usize {
self.unbanned().len()
.choose(&mut rng)
.map(|(addr, _)| addr.clone())
lklimek marked this conversation as resolved.
Show resolved Hide resolved
}

/// Get number of all addresses, both banned and not banned.
pub fn len(&self) -> usize {
self.addresses.len()
self.addresses.read().unwrap().len()
}

/// Check if the list is empty.
/// Returns true if there are no addresses in the list.
/// Returns false if there is at least one address in the list.
/// Banned addresses are also counted.
pub fn is_empty(&self) -> bool {
self.addresses.is_empty()
self.addresses.read().unwrap().is_empty()
}
}

impl IntoIterator for AddressList {
type Item = (Address, AddressStatus);
type IntoIter = std::collections::hash_map::IntoIter<Address, AddressStatus>;

fn into_iter(self) -> Self::IntoIter {
let mut guard = self.addresses.write().unwrap();

let addresses_map = mem::take(&mut *guard);

addresses_map.into_iter()
lklimek marked this conversation as resolved.
Show resolved Hide resolved
}
}

Expand All @@ -238,12 +273,3 @@
address_list
}
}

impl IntoIterator for AddressList {
type Item = Address;
type IntoIter = std::collections::hash_set::IntoIter<Address>;

fn into_iter(self) -> Self::IntoIter {
self.addresses.into_iter()
}
}
Loading
Loading