use crate::{PostageContext, Stamp, StampError};
use nectar_primitives::SwarmAddress;
#[cfg(any(test, feature = "std"))]
use crate::Batch;
#[cfg(test)]
use crate::StampIndex;
#[cfg(feature = "std")]
use crate::{BatchStore, BatchStoreExt};
pub trait StampValidator {
type Error: From<StampError>;
fn validate(
&self,
stamp: &Stamp,
address: &SwarmAddress,
state: &PostageContext,
) -> Result<(), Self::Error>;
fn validate_structure(
&self,
stamp: &Stamp,
address: &SwarmAddress,
state: &PostageContext,
) -> Result<(), Self::Error> {
self.validate(stamp, address, state)
}
}
#[derive(Debug)]
#[cfg(feature = "std")]
pub struct StoreValidator<S> {
store: S,
confirmation_threshold: u64,
}
#[cfg(feature = "std")]
impl<S> StoreValidator<S> {
pub const fn new(store: S, confirmation_threshold: u64) -> Self {
Self {
store,
confirmation_threshold,
}
}
pub const fn store(&self) -> &S {
&self.store
}
pub const fn confirmation_threshold(&self) -> u64 {
self.confirmation_threshold
}
}
#[cfg(feature = "std")]
impl<S: BatchStore + Sync> StoreValidator<S> {
pub async fn validate(&self, stamp: &Stamp, address: &SwarmAddress) -> Result<(), StampError> {
let batch = self.get_batch_for_stamp(stamp).await?;
self.validate_structure_with_batch(stamp, address, &batch)?;
stamp.verify(address, batch.owner())?;
Ok(())
}
pub async fn validate_structure(
&self,
stamp: &Stamp,
address: &SwarmAddress,
) -> Result<(), StampError> {
let batch = self.get_batch_for_stamp(stamp).await?;
self.validate_structure_with_batch(stamp, address, &batch)
}
async fn get_batch_for_stamp(&self, stamp: &Stamp) -> Result<Batch, StampError> {
self.store
.get_usable(&stamp.batch(), self.confirmation_threshold)
.await
.map_err(|e| match e {
crate::BatchStoreError::NotFound(id) => StampError::BatchNotFound(id),
crate::BatchStoreError::NotUsable {
created,
current,
threshold,
..
} => StampError::BatchNotUsable {
created,
current,
threshold,
},
crate::BatchStoreError::Expired {
value,
total_amount,
..
} => StampError::BatchExpired {
value,
total_amount,
},
crate::BatchStoreError::Store(_) => StampError::BatchNotFound(stamp.batch()),
})
}
fn validate_structure_with_batch(
&self,
stamp: &Stamp,
address: &SwarmAddress,
batch: &Batch,
) -> Result<(), StampError> {
batch.validate_index(&stamp.stamp_index())?;
batch.validate_bucket(&stamp.stamp_index(), address)?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloy_primitives::{Address, B256};
#[test]
fn test_validate_index_valid() {
let batch = Batch::new(B256::ZERO, 0, 0, Address::ZERO, 18, 16, false);
let index = StampIndex::new(1000, 3);
assert!(batch.validate_index(&index).is_ok());
}
#[test]
fn test_validate_index_bucket_out_of_range() {
let batch = Batch::new(B256::ZERO, 0, 0, Address::ZERO, 18, 16, false);
let index = StampIndex::new(70000, 0);
assert!(matches!(
batch.validate_index(&index),
Err(StampError::InvalidIndex)
));
}
#[test]
fn test_validate_index_position_out_of_range() {
let batch = Batch::new(B256::ZERO, 0, 0, Address::ZERO, 18, 16, false);
let index = StampIndex::new(1000, 5);
assert!(matches!(
batch.validate_index(&index),
Err(StampError::InvalidIndex)
));
}
#[test]
fn test_bucket_for_address() {
let batch = Batch::new(B256::ZERO, 0, 0, Address::ZERO, 18, 16, false);
let address = SwarmAddress::new([
0xCB, 0xE5, 0x00, 0x00, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0,
]);
assert_eq!(batch.bucket_for_address(&address), 0xCBE5);
}
#[test]
fn test_validate_bucket_match() {
let batch = Batch::new(B256::ZERO, 0, 0, Address::ZERO, 18, 16, false);
let address = SwarmAddress::new([
0xCB, 0xE5, 0x00, 0x00, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0,
]);
let index = StampIndex::new(0xCBE5, 0);
assert!(batch.validate_bucket(&index, &address).is_ok());
}
#[test]
fn test_validate_bucket_mismatch() {
let batch = Batch::new(B256::ZERO, 0, 0, Address::ZERO, 18, 16, false);
let address = SwarmAddress::new([
0xCB, 0xE5, 0x00, 0x00, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0,
]);
let index = StampIndex::new(0x1234, 0);
assert!(matches!(
batch.validate_bucket(&index, &address),
Err(StampError::BucketMismatch)
));
}
}