pub const MAX_HINT_LENGTH: usize = 32;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HintAction {
Skip,
Store,
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum HintError {
#[error("hint too long: {length} bytes exceeds maximum {max}")]
HintTooLong {
length: usize,
max: usize,
},
}
pub fn validate_hint(hint: &[u8]) -> Result<HintAction, HintError> {
if hint.is_empty() {
return Ok(HintAction::Skip);
}
if hint.len() > MAX_HINT_LENGTH {
return Err(HintError::HintTooLong {
length: hint.len(),
max: MAX_HINT_LENGTH,
});
}
Ok(HintAction::Store)
}
use std::collections::{HashMap, HashSet};
use chia_protocol::Bytes32;
use crate::coin_store::CoinStore;
use crate::error::CoinStoreError;
use crate::storage::schema;
use crate::types::CoinId;
impl CoinStore {
pub fn add_hint(&self, coin_id: &CoinId, hint: &[u8]) -> Result<(), CoinStoreError> {
match validate_hint(hint)? {
HintAction::Skip => return Ok(()),
HintAction::Store => {}
}
let mut fwd_key = Vec::with_capacity(32 + hint.len());
fwd_key.extend_from_slice(coin_id.as_ref());
fwd_key.extend_from_slice(hint);
if self.backend.get(schema::CF_HINTS, &fwd_key)?.is_some() {
return Ok(());
}
let mut rev_key = Vec::with_capacity(hint.len() + 32);
rev_key.extend_from_slice(hint);
rev_key.extend_from_slice(coin_id.as_ref());
self.backend.put(schema::CF_HINTS, &fwd_key, &[])?;
self.backend.put(schema::CF_HINTS_BY_VALUE, &rev_key, &[])?;
Ok(())
}
pub fn get_coin_ids_by_hint(
&self,
hint: &Bytes32,
max_items: usize,
) -> Result<Vec<CoinId>, CoinStoreError> {
let entries = self
.backend
.prefix_scan(schema::CF_HINTS_BY_VALUE, hint.as_ref())?;
let mut result = Vec::with_capacity(entries.len().min(max_items));
for (key, _value) in entries {
if result.len() >= max_items {
break;
}
if key.len() >= 64 {
let mut coin_bytes = [0u8; 32];
coin_bytes.copy_from_slice(&key[32..64]);
result.push(CoinId::from(coin_bytes));
}
}
Ok(result)
}
pub fn get_coin_ids_by_hints(
&self,
hints: &[Bytes32],
max_items: usize,
) -> Result<Vec<CoinId>, CoinStoreError> {
let mut seen = HashSet::new();
let mut result = Vec::new();
for hint in hints {
let coin_ids = self.get_coin_ids_by_hint(hint, max_items)?;
for cid in coin_ids {
if result.len() >= max_items {
return Ok(result);
}
if seen.insert(cid) {
result.push(cid);
}
}
}
Ok(result)
}
pub fn get_hints_for_coin_ids(
&self,
coin_ids: &[CoinId],
) -> Result<HashMap<CoinId, Vec<Bytes32>>, CoinStoreError> {
let mut result = HashMap::new();
for coin_id in coin_ids {
let entries = self
.backend
.prefix_scan(schema::CF_HINTS, coin_id.as_ref())?;
let mut hints_for_coin = Vec::new();
for (key, _value) in entries {
if key.len() >= 64 {
let mut hint_bytes = [0u8; 32];
hint_bytes.copy_from_slice(&key[32..64]);
hints_for_coin.push(Bytes32::from(hint_bytes));
}
}
if !hints_for_coin.is_empty() {
result.insert(*coin_id, hints_for_coin);
}
}
Ok(result)
}
pub fn count_hints(&self) -> Result<u64, CoinStoreError> {
let entries = self.backend.prefix_scan(schema::CF_HINTS, &[])?;
Ok(entries.len() as u64)
}
pub fn remove_hints_for_coins(&self, coin_ids: &[CoinId]) -> Result<u64, CoinStoreError> {
let mut removed: u64 = 0;
for coin_id in coin_ids {
let entries = self
.backend
.prefix_scan(schema::CF_HINTS, coin_id.as_ref())?;
for (fwd_key, _value) in &entries {
if fwd_key.len() <= 32 {
continue; }
let hint_bytes = &fwd_key[32..];
let mut rev_key = Vec::with_capacity(hint_bytes.len() + 32);
rev_key.extend_from_slice(hint_bytes);
rev_key.extend_from_slice(coin_id.as_ref());
self.backend.delete(schema::CF_HINTS_BY_VALUE, &rev_key)?;
self.backend.delete(schema::CF_HINTS, fwd_key)?;
removed += 1;
}
}
Ok(removed)
}
pub fn get_coin_ids_by_hint_bytes(
&self,
hint: &[u8],
max_items: usize,
) -> Result<Vec<CoinId>, CoinStoreError> {
let entries = self.backend.prefix_scan(schema::CF_HINTS_BY_VALUE, hint)?;
let expected_key_len = hint.len() + 32;
let mut result = Vec::with_capacity(entries.len().min(max_items));
for (key, _value) in entries {
if result.len() >= max_items {
break;
}
if key.len() == expected_key_len {
let mut coin_bytes = [0u8; 32];
coin_bytes.copy_from_slice(&key[hint.len()..hint.len() + 32]);
result.push(CoinId::from(coin_bytes));
}
}
Ok(result)
}
}