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}