#[cfg(feature = "rocksdb")]
use crate::{
error::EyeError,
types::{EyeCell, NodeMatrixRow},
YedadEyeProvider,
};
#[cfg(feature = "rocksdb")]
use async_trait::async_trait;
#[cfg(feature = "rocksdb")]
use rocksdb::{Options, DB};
#[cfg(feature = "rocksdb")]
use std::{collections::HashMap, sync::Arc};
#[cfg(feature = "rocksdb")]
const ADDRESS_SIZE: usize = 12;
#[cfg(feature = "rocksdb")]
const HASH_SIZE: usize = 32;
#[cfg(feature = "rocksdb")]
const SLOT_SIZE: usize = 64;
#[cfg(feature = "rocksdb")]
pub struct DatabaseEyeProvider {
db: Arc<DB>,
}
#[cfg(feature = "rocksdb")]
impl DatabaseEyeProvider {
pub fn new(db_path: &str) -> Result<Self, EyeError> {
let mut opts = Options::default();
opts.create_if_missing(false);
let db = DB::open_for_read_only(&opts, db_path, false)
.map_err(|e| EyeError::DatabaseError(e.to_string()))?;
Ok(Self { db: Arc::new(db) })
}
fn slot_from_bytes(bytes: &[u8]) -> Result<yedad_common::Slot, EyeError> {
if bytes.len() != SLOT_SIZE {
return Err(EyeError::DatabaseError("Invalid slot size".to_string()));
}
let pulse_id_u32 = u32::from_be_bytes(
bytes[0..4]
.try_into()
.map_err(|_| EyeError::DatabaseError("Invalid pulse_id".to_string()))?,
);
let mut user_address = [0u8; ADDRESS_SIZE];
user_address.copy_from_slice(&bytes[4..16]);
let step_number = u32::from_be_bytes(
bytes[16..20]
.try_into()
.map_err(|_| EyeError::DatabaseError("Invalid step_number".to_string()))?,
);
let ladder_id = u32::from_be_bytes(
bytes[20..24]
.try_into()
.map_err(|_| EyeError::DatabaseError("Invalid ladder_id".to_string()))?,
);
let balance = u64::from_be_bytes(
bytes[24..32]
.try_into()
.map_err(|_| EyeError::DatabaseError("Invalid balance".to_string()))?,
);
let mut last_hash = [0u8; HASH_SIZE];
last_hash.copy_from_slice(&bytes[32..64]);
Ok(yedad_common::Slot {
pulse_id: u64::from(pulse_id_u32),
user_address,
step_number,
ladder_id,
balance,
last_hash,
})
}
}
#[cfg(feature = "rocksdb")]
#[async_trait]
impl YedadEyeProvider for DatabaseEyeProvider {
async fn fetch_all_active_addresses(&self) -> Result<Vec<String>, EyeError> {
let mut addresses = Vec::new();
let prefix = b"s:";
let iter = self.db.prefix_iterator(prefix);
for (key, _) in iter.flatten() {
if key.starts_with(prefix) && key.len() == prefix.len() + ADDRESS_SIZE {
let addr = &key[prefix.len()..];
addresses.push(hex::encode(addr));
}
}
Ok(addresses)
}
#[cfg(feature = "rocksdb")]
async fn fetch_node_row(&self, address: &str) -> Result<NodeMatrixRow, EyeError> {
let addr_bytes = hex::decode(address).map_err(|e| EyeError::ParsingError(e.to_string()))?;
if addr_bytes.len() != ADDRESS_SIZE {
return Err(EyeError::ParsingError("Invalid address length".to_string()));
}
let mut key = Vec::with_capacity(2 + ADDRESS_SIZE);
key.extend_from_slice(b"s:");
key.extend_from_slice(&addr_bytes);
let slot_bytes = self
.db
.get(&key)
.map_err(|e| EyeError::DatabaseError(e.to_string()))?
.ok_or_else(|| EyeError::NodeNotFound(address.to_string()))?;
let slot = Self::slot_from_bytes(&slot_bytes)?;
let mut history = HashMap::new();
let mut prefix = Vec::with_capacity(2 + addr_bytes.len());
prefix.extend_from_slice(b"n:");
prefix.extend_from_slice(&addr_bytes);
let mut iter = self.db.prefix_iterator(&prefix);
while let Some(Ok((key, value))) = iter.next() {
if key.starts_with(&prefix) && value.len() == 16 {
let pulse_id = u32::from_be_bytes(
value[0..4]
.try_into()
.map_err(|_| EyeError::DatabaseError("Invalid pulse_id".to_string()))?,
);
let state_crc = u32::from_be_bytes(
value[4..8]
.try_into()
.map_err(|_| EyeError::DatabaseError("Invalid crc".to_string()))?,
);
history.insert(
pulse_id,
EyeCell {
pulse_id,
state_crc,
},
);
}
}
Ok(NodeMatrixRow {
address: address.to_string(),
current_rank: "Full Node Local".to_string(),
balance: slot.balance,
step_number: slot.step_number,
history,
})
}
}