rings_core/dht/
virtual_node.rs1#![deny(missing_docs)]
2use std::collections::BTreeSet;
9
10use ethereum_types::H160;
11use sha1::Digest;
12use sha1::Sha1;
13
14use crate::dht::topology;
15use crate::dht::Did;
16use crate::dht::DEFAULT_FINGER_TABLE_SIZE;
17
18const VIRTUAL_NODE_DOMAIN: &[u8] = b"rings:vnode";
19
20pub const MAX_STORAGE_VIRTUAL_POSITIONS_PER_OWNER: u16 = 256;
22
23pub const DEFAULT_STORAGE_VIRTUAL_POSITIONS_PER_OWNER: u16 = DEFAULT_FINGER_TABLE_SIZE as u16;
31
32pub const fn default_storage_virtual_positions_per_owner() -> u16 {
34 DEFAULT_STORAGE_VIRTUAL_POSITIONS_PER_OWNER
35}
36
37#[derive(Clone, Copy, Debug, Eq, PartialEq)]
39pub struct VirtualNodeConfig {
40 network_id: u32,
42 positions_per_owner: u16,
44}
45
46impl VirtualNodeConfig {
47 pub const fn disabled() -> Self {
49 Self {
50 network_id: 0,
51 positions_per_owner: 0,
52 }
53 }
54
55 pub const fn new(network_id: u32, positions_per_owner: u16) -> Self {
59 Self {
60 network_id,
61 positions_per_owner: Self::bounded_positions_per_owner(positions_per_owner),
62 }
63 }
64
65 pub const fn is_enabled(self) -> bool {
67 self.positions_per_owner > 0
68 }
69
70 pub const fn network_id(self) -> u32 {
72 self.network_id
73 }
74
75 pub const fn positions_per_owner(self) -> u16 {
77 self.positions_per_owner
78 }
79
80 pub const fn positions_per_owner_within_limit(positions_per_owner: u16) -> bool {
82 positions_per_owner <= MAX_STORAGE_VIRTUAL_POSITIONS_PER_OWNER
83 }
84
85 const fn bounded_positions_per_owner(positions_per_owner: u16) -> u16 {
86 if positions_per_owner > MAX_STORAGE_VIRTUAL_POSITIONS_PER_OWNER {
87 MAX_STORAGE_VIRTUAL_POSITIONS_PER_OWNER
88 } else {
89 positions_per_owner
90 }
91 }
92}
93
94#[derive(Clone, Copy, Debug, Eq, PartialEq)]
96pub struct VirtualNode {
97 pub owner_did: Did,
99 pub vnode_did: Did,
101 pub index: u16,
103}
104
105impl VirtualNode {
106 pub fn derive(network_id: u32, owner_did: Did, index: u16) -> Self {
108 let mut hasher = Sha1::new();
109 hasher.update(VIRTUAL_NODE_DOMAIN);
110 hasher.update(network_id.to_be_bytes());
111 hasher.update(owner_did.as_bytes());
112 hasher.update(index.to_be_bytes());
113 let bytes: [u8; 20] = hasher.finalize().into();
114 Self {
115 owner_did,
116 vnode_did: Did::from(H160::from(bytes)),
117 index,
118 }
119 }
120}
121
122#[derive(Clone, Debug, Eq, PartialEq)]
124pub struct StorageVirtualNodes {
125 config: VirtualNodeConfig,
126 owners: BTreeSet<Did>,
127}
128
129impl StorageVirtualNodes {
130 pub fn new(config: VirtualNodeConfig) -> Self {
132 Self {
133 config,
134 owners: BTreeSet::new(),
135 }
136 }
137
138 pub fn from_owners(config: VirtualNodeConfig, owners: impl IntoIterator<Item = Did>) -> Self {
140 let mut registry = Self::new(config);
141 for owner in owners {
142 registry.register_owner(owner);
143 }
144 registry
145 }
146
147 pub const fn config(&self) -> VirtualNodeConfig {
149 self.config
150 }
151
152 pub const fn is_enabled(&self) -> bool {
154 self.config.is_enabled()
155 }
156
157 pub fn register_owner(&mut self, owner_did: Did) {
161 if self.is_enabled() {
162 self.owners.insert(owner_did);
163 }
164 }
165
166 pub fn unregister_owner(&mut self, owner_did: Did) {
168 self.owners.remove(&owner_did);
169 }
170
171 pub fn contains_owner(&self, owner_did: Did) -> bool {
173 self.owners.contains(&owner_did)
174 }
175
176 pub fn positions_for_owner(&self, owner_did: Did) -> Vec<VirtualNode> {
178 if !self.contains_owner(owner_did) {
179 return Vec::new();
180 }
181 self.derive_owner_positions(owner_did)
182 }
183
184 pub fn positions(&self) -> Vec<VirtualNode> {
186 let mut positions = Vec::new();
187 for owner in self.owners.iter().copied() {
188 positions.extend(self.derive_owner_positions(owner));
189 }
190 positions.sort_by_key(|position| (position.vnode_did, position.owner_did, position.index));
191 positions
192 }
193
194 pub fn owner_for_key(&self, key: Did) -> Option<Did> {
199 self.positions()
200 .into_iter()
201 .min_by(|left, right| {
202 topology::dist(key, left.vnode_did)
203 .cmp(&topology::dist(key, right.vnode_did))
204 .then_with(|| left.owner_did.cmp(&right.owner_did))
205 .then_with(|| left.index.cmp(&right.index))
206 })
207 .map(|position| position.owner_did)
208 }
209
210 fn derive_owner_positions(&self, owner_did: Did) -> Vec<VirtualNode> {
211 (0..self.config.positions_per_owner())
212 .map(|index| VirtualNode::derive(self.config.network_id(), owner_did, index))
213 .collect()
214 }
215}
216
217#[cfg(test)]
218mod tests {
219 use super::*;
220
221 #[test]
222 fn test_virtual_node_derivation_is_domain_separated_by_network() {
223 let owner = Did::from(42u32);
224 let first = VirtualNode::derive(7, owner, 0);
225 let repeated = VirtualNode::derive(7, owner, 0);
226 let other_network = VirtualNode::derive(8, owner, 0);
227
228 assert_eq!(first, repeated);
229 assert_ne!(first.vnode_did, owner);
230 assert_ne!(first.vnode_did, other_network.vnode_did);
231 }
232
233 #[test]
234 fn test_virtual_node_config_caps_positions_at_cost_bound() {
235 let config =
236 VirtualNodeConfig::new(1, MAX_STORAGE_VIRTUAL_POSITIONS_PER_OWNER.saturating_add(1));
237
238 assert_eq!(
239 config.positions_per_owner(),
240 MAX_STORAGE_VIRTUAL_POSITIONS_PER_OWNER
241 );
242 }
243
244 #[test]
245 fn test_storage_virtual_nodes_resolve_owner_from_virtual_position() -> crate::error::Result<()>
246 {
247 let owner_a = Did::from(10u32);
248 let owner_b = Did::from(20u32);
249 let registry =
250 StorageVirtualNodes::from_owners(VirtualNodeConfig::new(1, 2), [owner_a, owner_b]);
251
252 let owner_b_position = registry
253 .positions_for_owner(owner_b)
254 .into_iter()
255 .next()
256 .map(|position| position.vnode_did)
257 .ok_or_else(|| {
258 crate::error::Error::InvalidMessage(
259 "owner should have virtual positions".to_string(),
260 )
261 })?;
262
263 assert_eq!(registry.owner_for_key(owner_b_position), Some(owner_b));
264 Ok(())
265 }
266
267 #[test]
268 fn test_storage_virtual_nodes_resolve_successor_owner_for_interval_key(
269 ) -> crate::error::Result<()> {
270 let owners = [Did::from(10u32), Did::from(20u32), Did::from(30u32)];
271 let registry = StorageVirtualNodes::from_owners(VirtualNodeConfig::new(1, 2), owners);
272 let positions = registry.positions();
273
274 let Some((left, successor)) =
275 positions
276 .iter()
277 .zip(positions.iter().cycle().skip(1))
278 .find(|(left, successor)| {
279 let key = left.vnode_did + Did::from(1u32);
280 key != successor.vnode_did
281 })
282 else {
283 return Err(crate::error::Error::InvalidMessage(
284 "expected a non-empty virtual interval".to_string(),
285 ));
286 };
287
288 let key = left.vnode_did + Did::from(1u32);
289 assert_eq!(registry.owner_for_key(key), Some(successor.owner_did));
290 Ok(())
291 }
292
293 #[test]
294 fn test_unregister_owner_removes_virtual_positions() {
295 let owner = Did::from(10u32);
296 let mut registry = StorageVirtualNodes::new(VirtualNodeConfig::new(1, 3));
297 registry.register_owner(owner);
298 registry.unregister_owner(owner);
299
300 assert!(registry.positions().is_empty());
301 assert_eq!(registry.owner_for_key(Did::from(11u32)), None);
302 }
303}