Skip to main content

nectar_postage/
util.rs

1//! Utility functions for postage operations.
2
3use nectar_primitives::SwarmAddress;
4
5/// Returns the current timestamp in nanoseconds since the Unix epoch.
6///
7/// This is used when creating stamps to record when they were issued.
8/// In `no_std` environments, this returns 0 and callers should provide
9/// timestamps via other means (e.g., `prepare_stamp` with explicit timestamp).
10#[cfg(feature = "std")]
11#[inline]
12pub fn current_timestamp() -> u64 {
13    use std::time::{SystemTime, UNIX_EPOCH};
14    SystemTime::now()
15        .duration_since(UNIX_EPOCH)
16        .map(|d| d.as_nanos() as u64)
17        .unwrap_or(0)
18}
19
20/// Returns 0 in `no_std` environments.
21///
22/// Callers should provide timestamps via other means when `std` is not available.
23#[cfg(not(feature = "std"))]
24#[inline]
25pub const fn current_timestamp() -> u64 {
26    0
27}
28
29/// Calculates which collision bucket a chunk belongs to based on its address.
30///
31/// The bucket is determined by taking the first `bucket_depth` bits of the
32/// chunk address, interpreted as a big-endian unsigned integer.
33///
34/// # Arguments
35///
36/// * `address` - The chunk's Swarm address
37/// * `bucket_depth` - The number of leading bits to use (from the batch configuration)
38///
39/// # Returns
40///
41/// The bucket number (0 to 2^bucket_depth - 1)
42///
43/// # Example
44///
45/// ```
46/// use nectar_postage::calculate_bucket;
47/// use nectar_primitives::SwarmAddress;
48///
49/// 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]);
50/// let bucket = calculate_bucket(&address, 16);
51/// assert_eq!(bucket, 0xCBE5);
52/// ```
53#[inline]
54pub fn calculate_bucket(address: &SwarmAddress, bucket_depth: u8) -> u32 {
55    // Take the first 4 bytes as a big-endian u32
56    let leading = u32::from_be_bytes(address.as_bytes()[0..4].try_into().unwrap());
57    // Shift right to get only the top `bucket_depth` bits
58    leading >> (32 - bucket_depth)
59}
60
61/// Context for postage validation.
62///
63/// Contains the current state needed to determine whether batches are expired
64/// or usable. This data may come from a blockchain, database, or any other source.
65#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
66#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
67pub struct PostageContext {
68    /// The current block number (or equivalent time reference).
69    block: u64,
70    /// The cumulative payout per chunk.
71    ///
72    /// This represents the total amount that has been distributed to storage providers
73    /// per chunk up to this point. A batch expires when its value (balance per chunk)
74    /// is less than or equal to this amount.
75    total_amount: u128,
76}
77
78impl PostageContext {
79    /// Creates a new postage context.
80    #[inline]
81    pub const fn new(block: u64, total_amount: u128) -> Self {
82        Self {
83            block,
84            total_amount,
85        }
86    }
87
88    /// Returns the current block number.
89    #[inline]
90    pub const fn block(&self) -> u64 {
91        self.block
92    }
93
94    /// Returns the cumulative payout per chunk.
95    #[inline]
96    pub const fn total_amount(&self) -> u128 {
97        self.total_amount
98    }
99
100    /// Updates the block number.
101    #[inline]
102    pub const fn set_block(&mut self, block: u64) {
103        self.block = block;
104    }
105
106    /// Updates the total amount.
107    #[inline]
108    pub const fn set_total_amount(&mut self, total_amount: u128) {
109        self.total_amount = total_amount;
110    }
111}
112
113#[cfg(test)]
114mod tests {
115    use super::*;
116
117    #[test]
118    fn test_calculate_bucket() {
119        // Address starting with 0xCBE5...
120        let address = SwarmAddress::new([
121            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,
122            0, 0, 0, 0, 0, 0, 0,
123        ]);
124
125        // With bucket_depth=16, we should get 0xCBE5
126        assert_eq!(calculate_bucket(&address, 16), 0xCBE5);
127
128        // With bucket_depth=8, we should get 0xCB
129        assert_eq!(calculate_bucket(&address, 8), 0xCB);
130
131        // With bucket_depth=4, we should get 0xC
132        assert_eq!(calculate_bucket(&address, 4), 0xC);
133    }
134
135    #[test]
136    fn test_chain_state() {
137        let mut state = PostageContext::new(100, 5000);
138
139        assert_eq!(state.block(), 100);
140        assert_eq!(state.total_amount(), 5000);
141
142        state.set_block(200);
143        state.set_total_amount(10000);
144
145        assert_eq!(state.block(), 200);
146        assert_eq!(state.total_amount(), 10000);
147    }
148
149    #[test]
150    fn test_chain_state_default() {
151        let state = PostageContext::default();
152        assert_eq!(state.block(), 0);
153        assert_eq!(state.total_amount(), 0);
154    }
155}