dpp/identity/
identity_nonce.rs

1use crate::ProtocolError;
2use platform_serialization_derive::{PlatformDeserialize, PlatformSerialize};
3use std::fmt::{Debug, Display, Formatter};
4
5use crate::consensus::state::identity::invalid_identity_contract_nonce_error::InvalidIdentityNonceError;
6use crate::consensus::state::state_error::StateError;
7use crate::consensus::ConsensusError;
8use crate::prelude::IdentityNonce;
9use crate::validation::SimpleConsensusValidationResult;
10use bincode::{Decode, Encode};
11use platform_value::Identifier;
12
13pub const IDENTITY_NONCE_VALUE_FILTER: u64 = 0xFFFFFFFFFF;
14pub const MISSING_IDENTITY_REVISIONS_FILTER: u64 = 0xFFFFFF0000000000;
15pub const MAX_MISSING_IDENTITY_REVISIONS: u64 = 24;
16pub const MISSING_IDENTITY_REVISIONS_MAX_BYTES: u64 = MAX_MISSING_IDENTITY_REVISIONS;
17pub const IDENTITY_NONCE_VALUE_FILTER_MAX_BYTES: u64 = 40;
18
19#[derive(
20    Debug, Clone, Copy, PartialEq, Eq, Encode, Decode, PlatformSerialize, PlatformDeserialize,
21)]
22/// The result of the merge of the identity contract nonce
23pub enum MergeIdentityNonceResult {
24    /// The nonce is an invalid value
25    /// This could be 0
26    InvalidNonce,
27    /// The nonce is too far in the future
28    NonceTooFarInFuture,
29    /// The nonce is too far in the past
30    NonceTooFarInPast,
31    /// The nonce is already present at the tip
32    NonceAlreadyPresentAtTip,
33    /// The nonce is already present in the past
34    NonceAlreadyPresentInPast(u64),
35    /// The merge is a success
36    MergeIdentityNonceSuccess(IdentityNonce),
37}
38
39impl Display for MergeIdentityNonceResult {
40    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
41        f.write_str(self.error_message().unwrap_or("no error"))
42    }
43}
44
45impl MergeIdentityNonceResult {
46    /// Gives a result from the enum
47    pub fn error_message(&self) -> Option<&'static str> {
48        match self {
49            MergeIdentityNonceResult::NonceTooFarInFuture => Some("nonce too far in future"),
50            MergeIdentityNonceResult::NonceTooFarInPast => Some("nonce too far in past"),
51            MergeIdentityNonceResult::NonceAlreadyPresentAtTip => {
52                Some("nonce already present at tip")
53            }
54            MergeIdentityNonceResult::NonceAlreadyPresentInPast(_) => {
55                Some("nonce already present in past")
56            }
57            MergeIdentityNonceResult::MergeIdentityNonceSuccess(_) => None,
58            MergeIdentityNonceResult::InvalidNonce => Some("nonce is an invalid value"),
59        }
60    }
61
62    /// Is this result an error?
63    pub fn is_error(&self) -> bool {
64        !matches!(self, MergeIdentityNonceResult::MergeIdentityNonceSuccess(_))
65    }
66}
67
68pub fn validate_new_identity_nonce(
69    new_revision_nonce: IdentityNonce,
70    identity_id: Identifier,
71) -> SimpleConsensusValidationResult {
72    if new_revision_nonce >= MISSING_IDENTITY_REVISIONS_MAX_BYTES {
73        // we are too far away from the actual revision
74        SimpleConsensusValidationResult::new_with_error(ConsensusError::StateError(
75            StateError::InvalidIdentityNonceError(InvalidIdentityNonceError {
76                identity_id,
77                current_identity_nonce: None,
78                setting_identity_nonce: new_revision_nonce,
79                error: MergeIdentityNonceResult::NonceTooFarInPast,
80            }),
81        ))
82    } else {
83        SimpleConsensusValidationResult::new()
84    }
85}
86
87pub fn validate_identity_nonce_update(
88    existing_nonce: IdentityNonce,
89    new_revision_nonce: IdentityNonce,
90    identity_id: Identifier,
91) -> SimpleConsensusValidationResult {
92    let actual_existing_revision = existing_nonce & IDENTITY_NONCE_VALUE_FILTER;
93    match actual_existing_revision.cmp(&new_revision_nonce) {
94        std::cmp::Ordering::Equal => {
95            // we were not able to update the revision as it is the same as we already had
96            return SimpleConsensusValidationResult::new_with_error(ConsensusError::StateError(
97                StateError::InvalidIdentityNonceError(InvalidIdentityNonceError {
98                    identity_id,
99                    current_identity_nonce: Some(existing_nonce),
100                    setting_identity_nonce: new_revision_nonce,
101                    error: MergeIdentityNonceResult::NonceAlreadyPresentAtTip,
102                }),
103            ));
104        }
105        std::cmp::Ordering::Less => {
106            if new_revision_nonce - actual_existing_revision > MISSING_IDENTITY_REVISIONS_MAX_BYTES
107            {
108                // we are too far away from the actual revision
109                return SimpleConsensusValidationResult::new_with_error(
110                    ConsensusError::StateError(StateError::InvalidIdentityNonceError(
111                        InvalidIdentityNonceError {
112                            identity_id,
113                            current_identity_nonce: Some(existing_nonce),
114                            setting_identity_nonce: new_revision_nonce,
115                            error: MergeIdentityNonceResult::NonceTooFarInFuture,
116                        },
117                    )),
118                );
119            }
120        }
121        std::cmp::Ordering::Greater => {
122            let previous_revision_position_from_top = actual_existing_revision - new_revision_nonce;
123            if previous_revision_position_from_top > MISSING_IDENTITY_REVISIONS_MAX_BYTES {
124                // we are too far away from the actual revision
125                return SimpleConsensusValidationResult::new_with_error(
126                    ConsensusError::StateError(StateError::InvalidIdentityNonceError(
127                        InvalidIdentityNonceError {
128                            identity_id,
129                            current_identity_nonce: Some(existing_nonce),
130                            setting_identity_nonce: new_revision_nonce,
131                            error: MergeIdentityNonceResult::NonceTooFarInPast,
132                        },
133                    )),
134                );
135            } else {
136                let old_missing_revisions = existing_nonce & MISSING_IDENTITY_REVISIONS_FILTER;
137                let old_revision_already_set = if old_missing_revisions == 0 {
138                    true
139                } else {
140                    let byte_to_unset = 1
141                        << (previous_revision_position_from_top - 1
142                            + IDENTITY_NONCE_VALUE_FILTER_MAX_BYTES);
143                    old_missing_revisions | byte_to_unset != old_missing_revisions
144                };
145
146                if old_revision_already_set {
147                    return SimpleConsensusValidationResult::new_with_error(
148                        ConsensusError::StateError(StateError::InvalidIdentityNonceError(
149                            InvalidIdentityNonceError {
150                                identity_id,
151                                current_identity_nonce: Some(existing_nonce),
152                                setting_identity_nonce: new_revision_nonce,
153                                error: MergeIdentityNonceResult::NonceAlreadyPresentInPast(
154                                    previous_revision_position_from_top,
155                                ),
156                            },
157                        )),
158                    );
159                }
160            }
161        }
162    }
163    SimpleConsensusValidationResult::new()
164}
165
166#[cfg(test)]
167mod tests {
168    use crate::consensus::state::state_error::StateError;
169    use crate::consensus::ConsensusError;
170    use crate::identity::identity_nonce::{
171        validate_identity_nonce_update, validate_new_identity_nonce, MergeIdentityNonceResult,
172        MISSING_IDENTITY_REVISIONS_MAX_BYTES,
173    };
174    use platform_value::Identifier;
175
176    #[test]
177    fn validate_new_identity_nonce_valid_zero() {
178        let result = validate_new_identity_nonce(0, Identifier::default());
179        assert!(result.errors.is_empty());
180    }
181
182    #[test]
183    fn validate_new_identity_nonce_invalid_at_max() {
184        let nonce = MISSING_IDENTITY_REVISIONS_MAX_BYTES;
185        let result = validate_new_identity_nonce(nonce, Identifier::default());
186
187        let Some(ConsensusError::StateError(StateError::InvalidIdentityNonceError(e))) =
188            result.errors.first()
189        else {
190            panic!("expected state error");
191        };
192        assert_eq!(e.error, MergeIdentityNonceResult::NonceTooFarInPast);
193    }
194
195    #[test]
196    fn validate_identity_nonce_not_changed() {
197        let tip = 50;
198        let new_nonce = tip;
199        let identity_id = Identifier::default();
200        let result = validate_identity_nonce_update(tip, new_nonce, identity_id);
201
202        let Some(ConsensusError::StateError(StateError::InvalidIdentityNonceError(e))) =
203            result.errors.first()
204        else {
205            panic!("expected state error");
206        };
207        assert_eq!(e.error, MergeIdentityNonceResult::NonceAlreadyPresentAtTip);
208    }
209
210    #[test]
211    fn validate_identity_nonce_update_too_far_in_past() {
212        let tip = 50;
213        let new_nonce = tip - 25;
214        let identity_id = Identifier::default();
215        let result = validate_identity_nonce_update(tip, new_nonce, identity_id);
216
217        let Some(ConsensusError::StateError(StateError::InvalidIdentityNonceError(e))) =
218            result.errors.first()
219        else {
220            panic!("expected state error");
221        };
222        assert_eq!(e.error, MergeIdentityNonceResult::NonceTooFarInPast);
223    }
224
225    #[test]
226    fn validate_identity_nonce_update_too_far_in_future() {
227        let tip = 50;
228        let new_nonce = tip + 25;
229        let identity_id = Identifier::default();
230        let result = validate_identity_nonce_update(tip, new_nonce, identity_id);
231
232        let Some(ConsensusError::StateError(StateError::InvalidIdentityNonceError(e))) =
233            result.errors.first()
234        else {
235            panic!("expected state error");
236        };
237        assert_eq!(e.error, MergeIdentityNonceResult::NonceTooFarInFuture);
238    }
239
240    #[test]
241    fn validate_identity_nonce_update_already_in_past_no_missing_in_nonce() {
242        let tip = 50;
243        let new_nonce = tip - 24;
244        let identity_id = Identifier::default();
245        let result = validate_identity_nonce_update(tip, new_nonce, identity_id);
246
247        let Some(ConsensusError::StateError(StateError::InvalidIdentityNonceError(e))) =
248            result.errors.first()
249        else {
250            panic!("expected state error");
251        };
252        assert_eq!(
253            e.error,
254            MergeIdentityNonceResult::NonceAlreadyPresentInPast(24)
255        );
256    }
257
258    #[test]
259    fn validate_identity_nonce_update_already_in_past_some_missing_in_nonce() {
260        let tip = 50 | 0x0FFF000000000000;
261        let new_nonce = 50 - 24;
262        let identity_id = Identifier::default();
263        let result = validate_identity_nonce_update(tip, new_nonce, identity_id);
264
265        let Some(ConsensusError::StateError(StateError::InvalidIdentityNonceError(e))) =
266            result.errors.first()
267        else {
268            panic!("expected state error");
269        };
270        assert_eq!(
271            e.error,
272            MergeIdentityNonceResult::NonceAlreadyPresentInPast(24)
273        );
274    }
275
276    #[test]
277    fn validate_identity_nonce_update_not_in_past_some_missing_in_nonce() {
278        let tip = 50 | 0x0FFF000000000000;
279        let new_nonce = 50 - 20;
280        let identity_id = Identifier::default();
281        let result = validate_identity_nonce_update(tip, new_nonce, identity_id);
282
283        assert!(result.errors.is_empty())
284    }
285
286    #[test]
287    fn validate_identity_nonce_in_close_future() {
288        let tip = 50 | 0x0FFF000000000000;
289        let new_nonce = 50 + 24;
290        let identity_id = Identifier::default();
291        let result = validate_identity_nonce_update(tip, new_nonce, identity_id);
292
293        assert!(result.errors.is_empty())
294    }
295}