Skip to main content

ant_node/client/
data_types.rs

1//! Data type definitions for chunk storage.
2//!
3//! This module provides the core data types for content-addressed chunk storage
4//! on the Autonomi network. Chunks are immutable, content-addressed blobs where
5//! the address is the BLAKE3 hash of the content.
6
7use bytes::Bytes;
8/// Compute the content address (BLAKE3 hash) for the given data.
9#[must_use]
10pub fn compute_address(content: &[u8]) -> XorName {
11    *blake3::hash(content).as_bytes()
12}
13
14/// Compute the XOR distance between two 32-byte addresses.
15///
16/// Lexicographic comparison of the result gives correct Kademlia distance ordering.
17#[must_use]
18pub fn xor_distance(a: &XorName, b: &XorName) -> XorName {
19    std::array::from_fn(|i| a[i] ^ b[i])
20}
21
22/// Convert a hex-encoded peer ID string to an `XorName`.
23///
24/// Returns `None` if the string is not valid hex or is not exactly 32 bytes (64 hex chars).
25#[must_use]
26pub fn peer_id_to_xor_name(peer_id: &str) -> Option<XorName> {
27    let bytes = hex::decode(peer_id).ok()?;
28    if bytes.len() != 32 {
29        return None;
30    }
31    let mut name = [0u8; 32];
32    name.copy_from_slice(&bytes);
33    Some(name)
34}
35
36/// A content-addressed identifier (32 bytes).
37///
38/// The address is computed as BLAKE3(content) for chunks,
39/// ensuring content-addressed storage.
40pub type XorName = [u8; 32];
41
42/// A chunk of data with its content-addressed identifier.
43///
44/// Chunks are the fundamental storage unit in Autonomi. They are:
45/// - **Immutable**: Content cannot be changed after storage
46/// - **Content-addressed**: Address = BLAKE3(content)
47/// - **Paid**: Storage requires EVM payment on Arbitrum
48#[derive(Debug, Clone)]
49pub struct DataChunk {
50    /// The content-addressed identifier (BLAKE3 of content).
51    pub address: XorName,
52    /// The raw data content.
53    pub content: Bytes,
54}
55
56impl DataChunk {
57    /// Create a new data chunk.
58    ///
59    /// Note: This does NOT verify that address == BLAKE3(content).
60    /// Use `from_content` for automatic address computation.
61    #[must_use]
62    pub fn new(address: XorName, content: Bytes) -> Self {
63        Self { address, content }
64    }
65
66    /// Create a chunk from content, computing the address automatically.
67    #[must_use]
68    pub fn from_content(content: Bytes) -> Self {
69        let address = compute_address(&content);
70        Self { address, content }
71    }
72
73    /// Get the size of the chunk in bytes.
74    #[must_use]
75    pub fn size(&self) -> usize {
76        self.content.len()
77    }
78
79    /// Verify that the address matches BLAKE3(content).
80    #[must_use]
81    pub fn verify(&self) -> bool {
82        self.address == compute_address(&self.content)
83    }
84}
85
86/// Statistics about chunk operations.
87#[derive(Debug, Default, Clone)]
88pub struct ChunkStats {
89    /// Number of chunks stored.
90    pub chunks_stored: u64,
91    /// Number of chunks retrieved.
92    pub chunks_retrieved: u64,
93    /// Number of cache hits.
94    pub cache_hits: u64,
95    /// Number of misses (not found).
96    pub misses: u64,
97    /// Total bytes stored.
98    pub bytes_stored: u64,
99    /// Total bytes retrieved.
100    pub bytes_retrieved: u64,
101}
102
103#[cfg(test)]
104#[allow(clippy::unwrap_used, clippy::expect_used)]
105mod tests {
106    use super::*;
107
108    #[test]
109    fn test_data_chunk_creation() {
110        let address = [0xAB; 32];
111        let content = Bytes::from("test data");
112        let chunk = DataChunk::new(address, content.clone());
113
114        assert_eq!(chunk.address, address);
115        assert_eq!(chunk.content, content);
116        assert_eq!(chunk.size(), 9);
117    }
118
119    #[test]
120    fn test_chunk_from_content() {
121        let content = Bytes::from("hello world");
122        let chunk = DataChunk::from_content(content.clone());
123
124        // BLAKE3 of "hello world"
125        let expected: [u8; 32] = [
126            0xd7, 0x49, 0x81, 0xef, 0xa7, 0x0a, 0x0c, 0x88, 0x0b, 0x8d, 0x8c, 0x19, 0x85, 0xd0,
127            0x75, 0xdb, 0xcb, 0xf6, 0x79, 0xb9, 0x9a, 0x5f, 0x99, 0x14, 0xe5, 0xaa, 0xf9, 0x6b,
128            0x83, 0x1a, 0x9e, 0x24,
129        ];
130
131        assert_eq!(chunk.address, expected);
132        assert_eq!(chunk.content, content);
133        assert!(chunk.verify());
134    }
135
136    #[test]
137    fn test_xor_distance_identity() {
138        let a = [0xAB; 32];
139        assert_eq!(xor_distance(&a, &a), [0u8; 32]);
140    }
141
142    #[test]
143    fn test_xor_distance_symmetry() {
144        let a = [0x01; 32];
145        let b = [0xFF; 32];
146        assert_eq!(xor_distance(&a, &b), xor_distance(&b, &a));
147    }
148
149    #[test]
150    fn test_xor_distance_known_values() {
151        let a = [0x00; 32];
152        let b = [0xFF; 32];
153        assert_eq!(xor_distance(&a, &b), [0xFF; 32]);
154
155        let mut c = [0x00; 32];
156        c[0] = 0x80;
157        let mut expected = [0x00; 32];
158        expected[0] = 0x80;
159        assert_eq!(xor_distance(&a, &c), expected);
160    }
161
162    #[test]
163    fn test_peer_id_to_xor_name_valid() {
164        let hex_str = "ab".repeat(32);
165        let result = peer_id_to_xor_name(&hex_str);
166        assert_eq!(result, Some([0xAB; 32]));
167    }
168
169    #[test]
170    fn test_peer_id_to_xor_name_invalid_hex() {
171        assert_eq!(peer_id_to_xor_name("not_hex_at_all!"), None);
172    }
173
174    #[test]
175    fn test_peer_id_to_xor_name_wrong_length() {
176        // 16 bytes instead of 32
177        let short = "ab".repeat(16);
178        assert_eq!(peer_id_to_xor_name(&short), None);
179
180        // 33 bytes
181        let long = "ab".repeat(33);
182        assert_eq!(peer_id_to_xor_name(&long), None);
183    }
184
185    #[test]
186    fn test_chunk_verify() {
187        // Valid chunk
188        let content = Bytes::from("test");
189        let valid = DataChunk::from_content(content);
190        assert!(valid.verify());
191
192        // Invalid chunk (wrong address)
193        let invalid = DataChunk::new([0; 32], Bytes::from("test"));
194        assert!(!invalid.verify());
195    }
196}