ant-protocol 1.0.14

Defines the network protocol for Autonomi
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
// Copyright 2024 MaidSafe.net limited.
//
// This SAFE Network Software is licensed to you under The General Public License (GPL), version 3.
// Unless required by applicable law or agreed to in writing, the SAFE Network Software distributed
// under the GPL Licence is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. Please review the Licences for the specific language governing
// permissions and limitations relating to use of the SAFE Network Software.

use crate::{NetworkAddress, PrettyPrintRecordKey};
use libp2p::kad::store;
use serde::{Deserialize, Serialize};
use thiserror::Error;

/// A specialised `Result` type for protocol crate.
pub type Result<T> = std::result::Result<T, Error>;

/// Main error types for the SAFE protocol.
//
// IMPORTANT DEV NOTE: when adding new variants to our Protocol Error enum,
// make sure to keep them simple variants and not complex ones to keep retro compatibility.
// this is a simple variant:  OK
//    `NewErrorVariant`
// this is a complex variant: NOT OK
//    `NewErrorVariant( some other data type )`
//
// This test test_error_retro_compatibility_complex_types demonstrates the issue with complex types.
//
#[derive(Error, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub enum Error {
    // ---------- Chunk Proof errors
    #[error("Chunk does not exist {0:?}")]
    ChunkDoesNotExist(NetworkAddress),

    // ---------- Scratchpad errors
    /// The provided SecretyKey failed to decrypt the data
    #[error("Failed to derive CipherText from encrypted_data")]
    ScratchpadCipherTextFailed,
    /// The provided cypher text is invalid
    #[error("Provided cypher text is invalid")]
    ScratchpadCipherTextInvalid,

    // ---------- payment errors
    #[error("There was an error getting the storecost from kademlia store")]
    GetStoreQuoteFailed,
    #[error("There was an error generating the payment quote")]
    QuoteGenerationFailed,

    // ---------- replication errors
    /// Replication not found.
    #[error("Peer {holder:?} cannot find Record {key:?}")]
    ReplicatedRecordNotFound {
        /// Holder that being contacted
        holder: Box<NetworkAddress>,
        /// Key of the missing record
        key: Box<NetworkAddress>,
    },

    // ---------- record errors
    // Could not Serialize/Deserialize RecordHeader from Record
    #[error("Could not Serialize/Deserialize RecordHeader to/from Record")]
    RecordHeaderParsingFailed,
    // Could not Serialize/Deserialize Record
    #[error("Could not Serialize/Deserialize Record")]
    RecordParsingFailed,
    // The record already exists at this node
    #[error("The record already exists, so do not charge for it: {0:?}")]
    RecordExists(PrettyPrintRecordKey<'static>),

    // ---------- Record Put errors
    #[error("Error handling record put: {0}")]
    PutRecordFailed(String),
    #[error("Outdated record: with counter {counter}, expected any above {expected}")]
    OutdatedRecordCounter { counter: u64, expected: u64 },

    // ---------- Merkle payment errors
    #[error("There was an error getting the Merkle candidate quote: {0}")]
    GetMerkleCandidateQuoteFailed(String),
    #[error("Failed to sign Merkle candidate node: {0}")]
    FailedToSignMerkleCandidate(String),
    #[error("Merkle payment verification failed: {0}")]
    MerklePaymentVerificationFailed(String),
    #[error(
        "Topology verification failed: only {valid_count}/{total_paid} paid nodes in closest {closest_count}"
    )]
    TopologyVerificationFailed {
        /// Target address for distance calculations (reward pool midpoint)
        target_address: Box<NetworkAddress>,
        /// Number of paid nodes that were in the node's closest peers
        valid_count: usize,
        /// Total number of nodes that were paid
        total_paid: usize,
        /// Number of closest peers the node has
        closest_count: usize,
        /// The node's view of closest peers to the target
        #[serde(with = "crate::peer_id_serde")]
        node_peers: Vec<libp2p::PeerId>,
        /// The peers that were paid (client's view)
        #[serde(with = "crate::peer_id_serde")]
        paid_peers: Vec<libp2p::PeerId>,
    },

    // Dev Note: add new variants above this one for backward compatibility with older protocol versions
    // ---------- Unknown/fallback variant for retro compatibility
    /// Unknown error variant (for backward compatibility with newer protocol versions)
    #[error("Unknown error: the peer and you are using different protocol versions")]
    #[serde(other)]
    Unknown,
}

impl From<Error> for store::Error {
    fn from(_err: Error) -> Self {
        store::Error::ValueTooLarge
    }
}

impl From<store::Error> for Error {
    fn from(_err: store::Error) -> Self {
        Error::RecordParsingFailed
    }
}

#[cfg(test)]
mod tests {
    use libp2p::PeerId;

    use super::*;

    #[test]
    fn test_error_retro_compatibility() {
        // Test with a new struct that has a new variant
        #[derive(Error, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
        #[non_exhaustive]
        enum ExtendedError {
            #[error("Chunk does not exist {0:?}")]
            ChunkDoesNotExist(NetworkAddress),
            #[error("Failed to deserialize hex ScratchpadAddress")]
            ScratchpadHexDeserializeFailed,
            #[error("Failed to derive CipherText from encrypted_data")]
            ScratchpadCipherTextFailed,
            #[error("Provided cypher text is invalid")]
            ScratchpadCipherTextInvalid,
            #[error("There was an error getting the storecost from kademlia store")]
            GetStoreQuoteFailed,
            #[error("There was an error generating the payment quote")]
            QuoteGenerationFailed,
            #[error("Peer {holder:?} cannot find Record {key:?}")]
            ReplicatedRecordNotFound {
                holder: Box<NetworkAddress>,
                key: Box<NetworkAddress>,
            },
            #[error("Could not Serialize/Deserialize RecordHeader to/from Record")]
            RecordHeaderParsingFailed,
            #[error("Could not Serialize/Deserialize Record")]
            RecordParsingFailed,
            #[error("The record already exists, so do not charge for it: {0:?}")]
            RecordExists(PrettyPrintRecordKey<'static>),
            // New variant that doesn't exist in the original Error enum
            #[error("New error variant for testing")]
            NewErrorVariant,
            #[error("Unknown error variant")]
            #[serde(other)]
            Unknown,
        }

        // Test serialization and deserialization of ExtendedError
        let extended_error = ExtendedError::NewErrorVariant;
        let serialized = rmp_serde::to_vec(&extended_error).unwrap();

        // Test that we can deserialize into the current Error enum
        let deserialized: Error = rmp_serde::from_slice(&serialized).unwrap();
        assert_eq!(deserialized, Error::Unknown);
    }

    #[test]
    fn test_error_retro_compatibility_reduced() {
        // Test with a struct that has a missing variant (simulating older version)
        #[derive(Error, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
        #[non_exhaustive]
        enum ReducedError {
            #[error("Chunk does not exist {0:?}")]
            ChunkDoesNotExist(NetworkAddress),
            #[error("Failed to deserialize hex ScratchpadAddress")]
            ScratchpadHexDeserializeFailed,
            // removed this variant
            // - #[error("Failed to derive CipherText from encrypted_data")]
            // - ScratchpadCipherTextFailed,
            #[error("Provided cypher text is invalid")]
            ScratchpadCipherTextInvalid,
            #[error("There was an error getting the storecost from kademlia store")]
            GetStoreQuoteFailed,
            #[error("There was an error generating the payment quote")]
            QuoteGenerationFailed,
            #[error("Peer {holder:?} cannot find Record {key:?}")]
            ReplicatedRecordNotFound {
                holder: Box<NetworkAddress>,
                key: Box<NetworkAddress>,
            },
            #[error("Could not Serialize/Deserialize RecordHeader to/from Record")]
            RecordHeaderParsingFailed,
            #[error("Could not Serialize/Deserialize Record")]
            RecordParsingFailed,
            #[error("The record already exists, so do not charge for it: {0:?}")]
            RecordExists(PrettyPrintRecordKey<'static>),
            // Missing some variants that exist in the current Error enum
            #[error("Unknown error variant")]
            #[serde(other)]
            Unknown,
        }

        // Test serialization and deserialization of current Error
        let current_error = Error::ScratchpadCipherTextInvalid;
        let serialized_current = rmp_serde::to_vec(&current_error).unwrap();

        // Test that we can deserialize into ReducedError (older version)
        let deserialized_reduced: ReducedError =
            rmp_serde::from_slice(&serialized_current).unwrap();
        assert_eq!(
            deserialized_reduced,
            ReducedError::ScratchpadCipherTextInvalid
        );

        // Test that unknown variants fall back to Unknown
        let unknown_variant = Error::ScratchpadCipherTextFailed;
        let serialized_unknown = rmp_serde::to_vec(&unknown_variant).unwrap();
        let deserialized_unknown: ReducedError =
            rmp_serde::from_slice(&serialized_unknown).unwrap();
        assert_eq!(deserialized_unknown, ReducedError::Unknown);

        // Test reverse direction: ReducedError -> Error for simple variants
        let reduced_simple_error = ReducedError::GetStoreQuoteFailed;
        let serialized_reduced_simple = rmp_serde::to_vec(&reduced_simple_error).unwrap();
        let deserialized_reduced_simple: Error =
            rmp_serde::from_slice(&serialized_reduced_simple).unwrap();
        assert_eq!(deserialized_reduced_simple, Error::GetStoreQuoteFailed);

        // Test reverse direction: ReducedError -> Error for complex variants
        let addr = NetworkAddress::from(PeerId::random());
        let reduced_complex_error = ReducedError::ChunkDoesNotExist(addr.clone());
        let serialized_reduced_complex = rmp_serde::to_vec(&reduced_complex_error).unwrap();
        let deserialized_reduced_complex: Error =
            rmp_serde::from_slice(&serialized_reduced_complex).unwrap();
        assert_eq!(deserialized_reduced_complex, Error::ChunkDoesNotExist(addr));

        // Test reverse direction: ReducedError -> Error for complex struct variants
        let holder = NetworkAddress::from(PeerId::random());
        let key = NetworkAddress::from(PeerId::random());
        let reduced_struct_error = ReducedError::ReplicatedRecordNotFound {
            holder: Box::new(holder.clone()),
            key: Box::new(key.clone()),
        };
        let serialized_reduced_struct = rmp_serde::to_vec(&reduced_struct_error).unwrap();
        let deserialized_reduced_struct: Error =
            rmp_serde::from_slice(&serialized_reduced_struct).unwrap();
        assert_eq!(
            deserialized_reduced_struct,
            Error::ReplicatedRecordNotFound {
                holder: Box::new(holder),
                key: Box::new(key),
            }
        );
    }

    #[test]
    fn test_error_retro_compatibility_many_missing() {
        // test with many missing variants
        #[derive(Error, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
        #[non_exhaustive]
        enum ManyMissingVariants {
            #[error("Chunk does not exist {0:?}")]
            ChunkDoesNotExist(NetworkAddress),
            #[error("There was an error getting the storecost from kademlia store")]
            GetStoreQuoteFailed,
            // Only keeping ChunkDoesNotExist and GetStoreQuoteFailed, removing all others
            #[error("Unknown error variant")]
            #[serde(other)]
            Unknown,
        }

        // Test with ManyMissingVariants
        let current_error = Error::ScratchpadCipherTextInvalid;
        let serialized_current = rmp_serde::to_vec(&current_error).unwrap();
        let deserialized_many_missing: ManyMissingVariants =
            rmp_serde::from_slice(&serialized_current).unwrap();
        assert_eq!(deserialized_many_missing, ManyMissingVariants::Unknown);

        // Test that GetStoreQuoteFailed works with ManyMissingVariants
        let chunk_error = Error::GetStoreQuoteFailed;
        let serialized_chunk = rmp_serde::to_vec(&chunk_error).unwrap();
        let deserialized_chunk: ManyMissingVariants =
            rmp_serde::from_slice(&serialized_chunk).unwrap();
        assert_eq!(deserialized_chunk, ManyMissingVariants::GetStoreQuoteFailed);

        // Test the reverse direction: ManyMissingVariants::GetStoreQuoteFailed can be parsed as Error::GetStoreQuoteFailed
        let many_missing_error = ManyMissingVariants::GetStoreQuoteFailed;
        let serialized_many_missing = rmp_serde::to_vec(&many_missing_error).unwrap();
        let deserialized_to_error: Error = rmp_serde::from_slice(&serialized_many_missing).unwrap();
        assert_eq!(deserialized_to_error, Error::GetStoreQuoteFailed);

        // Test bidirectional compatibility for complex variant ChunkDoesNotExist
        let addr = NetworkAddress::from(PeerId::random());

        // Test Error::ChunkDoesNotExist -> ManyMissingVariants::ChunkDoesNotExist
        let chunk_error = Error::ChunkDoesNotExist(addr.clone());
        let serialized_chunk = rmp_serde::to_vec(&chunk_error).unwrap();
        let deserialized_chunk: ManyMissingVariants =
            rmp_serde::from_slice(&serialized_chunk).unwrap();
        assert_eq!(
            deserialized_chunk,
            ManyMissingVariants::ChunkDoesNotExist(addr.clone())
        );

        // Test ManyMissingVariants::ChunkDoesNotExist -> Error::ChunkDoesNotExist
        let many_missing_chunk = ManyMissingVariants::ChunkDoesNotExist(addr.clone());
        let serialized_many_missing_chunk = rmp_serde::to_vec(&many_missing_chunk).unwrap();
        let deserialized_many_missing_chunk: Error =
            rmp_serde::from_slice(&serialized_many_missing_chunk).unwrap();
        assert_eq!(
            deserialized_many_missing_chunk,
            Error::ChunkDoesNotExist(addr)
        );

        // Test that other simple variants fall back to Unknown in ManyMissingVariants
        let record_error = Error::RecordParsingFailed;
        let serialized_record = rmp_serde::to_vec(&record_error).unwrap();
        let deserialized_record: ManyMissingVariants =
            rmp_serde::from_slice(&serialized_record).unwrap();
        assert_eq!(deserialized_record, ManyMissingVariants::Unknown);
    }

    // ignore this test proves complex types retro compatibility is not supported yet
    #[test]
    fn test_error_retro_compatibility_complex_types() {
        // test with complex types
        #[derive(Error, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
        #[non_exhaustive]
        enum ComplexTypesRemoved {
            #[error("Chunk does not exist {0:?}")]
            ChunkDoesNotExist(NetworkAddress),
            #[error("Failed to deserialize hex ScratchpadAddress")]
            ScratchpadHexDeserializeFailed,
            #[error("Failed to derive CipherText from encrypted_data")]
            ScratchpadCipherTextFailed,
            #[error("Provided cypher text is invalid")]
            ScratchpadCipherTextInvalid,
            #[error("There was an error getting the storecost from kademlia store")]
            GetStoreQuoteFailed,
            #[error("There was an error generating the payment quote")]
            QuoteGenerationFailed,
            //removed this variant
            // - #[error("Peer {holder:?} cannot find Record {key:?}")]
            // - ReplicatedRecordNotFound {
            // -     holder: Box<NetworkAddress>,
            // -     key: Box<NetworkAddress>,
            // - },
            #[error("Could not Serialize/Deserialize RecordHeader to/from Record")]
            RecordHeaderParsingFailed,
            #[error("Could not Serialize/Deserialize Record")]
            RecordParsingFailed,
            #[error("The record already exists, so do not charge for it: {0:?}")]
            RecordExists(PrettyPrintRecordKey<'static>),
            // Missing some variants that exist in the current Error enum
            #[error("Unknown error variant")]
            #[serde(other)]
            Unknown,
        }

        // Test that complex types (ReplicatedRecordNotFound) fall back to Unknown
        // when the variant is missing from the older version
        let holder = NetworkAddress::from(PeerId::random());
        let key = NetworkAddress::from(PeerId::random());
        let complex_error = Error::ReplicatedRecordNotFound {
            holder: Box::new(holder),
            key: Box::new(key),
        };
        let serialized_complex = rmp_serde::to_vec(&complex_error).unwrap();

        // // Below is what we would want to do, but it's not supported yet
        // let deserialized_complex: ComplexTypesRemoved =
        //     rmp_serde::from_slice(&serialized_complex).unwrap();
        // assert_eq!(deserialized_complex, ComplexTypesRemoved::Unknown);
        // // for now it's an error
        assert!(rmp_serde::from_slice::<ComplexTypesRemoved>(&serialized_complex).is_err());

        // Test that simple variants that exist in both work correctly
        let simple_error = Error::ScratchpadCipherTextInvalid;
        let serialized_simple = rmp_serde::to_vec(&simple_error).unwrap();
        let deserialized_simple: ComplexTypesRemoved =
            rmp_serde::from_slice(&serialized_simple).unwrap();
        assert_eq!(
            deserialized_simple,
            ComplexTypesRemoved::ScratchpadCipherTextInvalid
        );

        // Test that other simple variants also work
        let addr = NetworkAddress::from(PeerId::random());
        let chunk_error = Error::ChunkDoesNotExist(addr.clone());
        let serialized_chunk = rmp_serde::to_vec(&chunk_error).unwrap();
        let deserialized_chunk: ComplexTypesRemoved =
            rmp_serde::from_slice(&serialized_chunk).unwrap();
        assert_eq!(
            deserialized_chunk,
            ComplexTypesRemoved::ChunkDoesNotExist(addr)
        );
    }
}