#![deny(missing_docs)]
use std::collections::BTreeSet;
use ethereum_types::H160;
use sha1::Digest;
use sha1::Sha1;
use crate::dht::topology;
use crate::dht::Did;
use crate::dht::DEFAULT_FINGER_TABLE_SIZE;
const VIRTUAL_NODE_DOMAIN: &[u8] = b"rings:vnode";
pub const MAX_STORAGE_VIRTUAL_POSITIONS_PER_OWNER: u16 = 256;
pub const DEFAULT_STORAGE_VIRTUAL_POSITIONS_PER_OWNER: u16 = DEFAULT_FINGER_TABLE_SIZE as u16;
pub const fn default_storage_virtual_positions_per_owner() -> u16 {
DEFAULT_STORAGE_VIRTUAL_POSITIONS_PER_OWNER
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct VirtualNodeConfig {
network_id: u32,
positions_per_owner: u16,
}
impl VirtualNodeConfig {
pub const fn disabled() -> Self {
Self {
network_id: 0,
positions_per_owner: 0,
}
}
pub const fn new(network_id: u32, positions_per_owner: u16) -> Self {
Self {
network_id,
positions_per_owner: Self::bounded_positions_per_owner(positions_per_owner),
}
}
pub const fn is_enabled(self) -> bool {
self.positions_per_owner > 0
}
pub const fn network_id(self) -> u32 {
self.network_id
}
pub const fn positions_per_owner(self) -> u16 {
self.positions_per_owner
}
pub const fn positions_per_owner_within_limit(positions_per_owner: u16) -> bool {
positions_per_owner <= MAX_STORAGE_VIRTUAL_POSITIONS_PER_OWNER
}
const fn bounded_positions_per_owner(positions_per_owner: u16) -> u16 {
if positions_per_owner > MAX_STORAGE_VIRTUAL_POSITIONS_PER_OWNER {
MAX_STORAGE_VIRTUAL_POSITIONS_PER_OWNER
} else {
positions_per_owner
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct VirtualNode {
pub owner_did: Did,
pub vnode_did: Did,
pub index: u16,
}
impl VirtualNode {
pub fn derive(network_id: u32, owner_did: Did, index: u16) -> Self {
let mut hasher = Sha1::new();
hasher.update(VIRTUAL_NODE_DOMAIN);
hasher.update(network_id.to_be_bytes());
hasher.update(owner_did.as_bytes());
hasher.update(index.to_be_bytes());
let bytes: [u8; 20] = hasher.finalize().into();
Self {
owner_did,
vnode_did: Did::from(H160::from(bytes)),
index,
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct StorageVirtualNodes {
config: VirtualNodeConfig,
owners: BTreeSet<Did>,
}
impl StorageVirtualNodes {
pub fn new(config: VirtualNodeConfig) -> Self {
Self {
config,
owners: BTreeSet::new(),
}
}
pub fn from_owners(config: VirtualNodeConfig, owners: impl IntoIterator<Item = Did>) -> Self {
let mut registry = Self::new(config);
for owner in owners {
registry.register_owner(owner);
}
registry
}
pub const fn config(&self) -> VirtualNodeConfig {
self.config
}
pub const fn is_enabled(&self) -> bool {
self.config.is_enabled()
}
pub fn register_owner(&mut self, owner_did: Did) {
if self.is_enabled() {
self.owners.insert(owner_did);
}
}
pub fn unregister_owner(&mut self, owner_did: Did) {
self.owners.remove(&owner_did);
}
pub fn contains_owner(&self, owner_did: Did) -> bool {
self.owners.contains(&owner_did)
}
pub fn positions_for_owner(&self, owner_did: Did) -> Vec<VirtualNode> {
if !self.contains_owner(owner_did) {
return Vec::new();
}
self.derive_owner_positions(owner_did)
}
pub fn positions(&self) -> Vec<VirtualNode> {
let mut positions = Vec::new();
for owner in self.owners.iter().copied() {
positions.extend(self.derive_owner_positions(owner));
}
positions.sort_by_key(|position| (position.vnode_did, position.owner_did, position.index));
positions
}
pub fn owner_for_key(&self, key: Did) -> Option<Did> {
self.positions()
.into_iter()
.min_by(|left, right| {
topology::dist(key, left.vnode_did)
.cmp(&topology::dist(key, right.vnode_did))
.then_with(|| left.owner_did.cmp(&right.owner_did))
.then_with(|| left.index.cmp(&right.index))
})
.map(|position| position.owner_did)
}
fn derive_owner_positions(&self, owner_did: Did) -> Vec<VirtualNode> {
(0..self.config.positions_per_owner())
.map(|index| VirtualNode::derive(self.config.network_id(), owner_did, index))
.collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_virtual_node_derivation_is_domain_separated_by_network() {
let owner = Did::from(42u32);
let first = VirtualNode::derive(7, owner, 0);
let repeated = VirtualNode::derive(7, owner, 0);
let other_network = VirtualNode::derive(8, owner, 0);
assert_eq!(first, repeated);
assert_ne!(first.vnode_did, owner);
assert_ne!(first.vnode_did, other_network.vnode_did);
}
#[test]
fn test_virtual_node_config_caps_positions_at_cost_bound() {
let config =
VirtualNodeConfig::new(1, MAX_STORAGE_VIRTUAL_POSITIONS_PER_OWNER.saturating_add(1));
assert_eq!(
config.positions_per_owner(),
MAX_STORAGE_VIRTUAL_POSITIONS_PER_OWNER
);
}
#[test]
fn test_storage_virtual_nodes_resolve_owner_from_virtual_position() -> crate::error::Result<()>
{
let owner_a = Did::from(10u32);
let owner_b = Did::from(20u32);
let registry =
StorageVirtualNodes::from_owners(VirtualNodeConfig::new(1, 2), [owner_a, owner_b]);
let owner_b_position = registry
.positions_for_owner(owner_b)
.into_iter()
.next()
.map(|position| position.vnode_did)
.ok_or_else(|| {
crate::error::Error::InvalidMessage(
"owner should have virtual positions".to_string(),
)
})?;
assert_eq!(registry.owner_for_key(owner_b_position), Some(owner_b));
Ok(())
}
#[test]
fn test_storage_virtual_nodes_resolve_successor_owner_for_interval_key(
) -> crate::error::Result<()> {
let owners = [Did::from(10u32), Did::from(20u32), Did::from(30u32)];
let registry = StorageVirtualNodes::from_owners(VirtualNodeConfig::new(1, 2), owners);
let positions = registry.positions();
let Some((left, successor)) =
positions
.iter()
.zip(positions.iter().cycle().skip(1))
.find(|(left, successor)| {
let key = left.vnode_did + Did::from(1u32);
key != successor.vnode_did
})
else {
return Err(crate::error::Error::InvalidMessage(
"expected a non-empty virtual interval".to_string(),
));
};
let key = left.vnode_did + Did::from(1u32);
assert_eq!(registry.owner_for_key(key), Some(successor.owner_did));
Ok(())
}
#[test]
fn test_unregister_owner_removes_virtual_positions() {
let owner = Did::from(10u32);
let mut registry = StorageVirtualNodes::new(VirtualNodeConfig::new(1, 3));
registry.register_owner(owner);
registry.unregister_owner(owner);
assert!(registry.positions().is_empty());
assert_eq!(registry.owner_for_key(Did::from(11u32)), None);
}
}