Skip to content

Commit b35db7c

Browse files
apskhemMr-Leshiy
andauthored
refactor(rust/signed-doc): Move validate_id_and_ver as separate rules IdRule and VerRule (#497)
* feat: ver * chore: check version * feat: id * finalize * feat: apply rules * chore: fmtfix * refactor: move to rules * chore: minor doc * move unit tests for IdRule and VerRule * update VerRule validation * fix tests and spelling * wip * chore: cspellfix * fix clippy --------- Co-authored-by: Alex Pozhylenkov <[email protected]>
1 parent b24805b commit b35db7c

File tree

8 files changed

+621
-214
lines changed

8 files changed

+621
-214
lines changed

rust/signed_doc/src/providers.rs

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
33
use std::{future::Future, time::Duration};
44

5-
use catalyst_types::catalyst_id::CatalystId;
5+
use catalyst_types::{catalyst_id::CatalystId, uuid::UuidV7};
66
use ed25519_dalek::VerifyingKey;
77

88
use crate::{CatalystSignedDocument, DocumentRef};
@@ -18,12 +18,19 @@ pub trait VerifyingKeyProvider {
1818

1919
/// `CatalystSignedDocument` Provider trait
2020
pub trait CatalystSignedDocumentProvider: Send + Sync {
21-
/// Try to get `CatalystSignedDocument`from document reference
21+
/// Try to get `CatalystSignedDocument` from document reference
2222
fn try_get_doc(
2323
&self,
2424
doc_ref: &DocumentRef,
2525
) -> impl Future<Output = anyhow::Result<Option<CatalystSignedDocument>>> + Send;
2626

27+
/// Try to get the last known version of the `CatalystSignedDocument`, same
28+
/// `id` and the highest known `ver`.
29+
fn try_get_last_doc(
30+
&self,
31+
id: UuidV7,
32+
) -> impl Future<Output = anyhow::Result<Option<CatalystSignedDocument>>> + Send;
33+
2734
/// Returns a future threshold value, which is used in the validation of the `ver`
2835
/// field that it is not too far in the future.
2936
/// If `None` is returned, skips "too far in the future" validation.
@@ -48,7 +55,6 @@ pub mod tests {
4855

4956
/// Simple testing implementation of `CatalystSignedDocumentProvider`
5057
#[derive(Default, Debug)]
51-
5258
pub struct TestCatalystSignedDocumentProvider(HashMap<DocumentRef, CatalystSignedDocument>);
5359

5460
impl TestCatalystSignedDocumentProvider {
@@ -82,6 +88,18 @@ pub mod tests {
8288
Ok(self.0.get(doc_ref).cloned())
8389
}
8490

91+
async fn try_get_last_doc(
92+
&self,
93+
id: catalyst_types::uuid::UuidV7,
94+
) -> anyhow::Result<Option<CatalystSignedDocument>> {
95+
Ok(self
96+
.0
97+
.iter()
98+
.filter(|(doc_ref, _)| doc_ref.id() == &id)
99+
.max_by_key(|(doc_ref, _)| doc_ref.ver().uuid())
100+
.map(|(_, doc)| doc.clone()))
101+
}
102+
85103
fn future_threshold(&self) -> Option<std::time::Duration> {
86104
Some(Duration::from_secs(5))
87105
}

rust/signed_doc/src/validator/mod.rs

Lines changed: 9 additions & 183 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,13 @@ pub(crate) mod rules;
66
use std::{
77
collections::HashMap,
88
sync::{Arc, LazyLock},
9-
time::{Duration, SystemTime},
109
};
1110

1211
use anyhow::Context;
1312
use catalyst_types::{catalyst_id::role_index::RoleId, problem_report::ProblemReport};
1413
use rules::{
15-
ContentEncodingRule, ContentRule, ContentSchema, ContentTypeRule, ParametersRule, RefRule,
16-
ReplyRule, Rules, SectionRule, SignatureKidRule,
14+
ContentEncodingRule, ContentRule, ContentSchema, ContentTypeRule, IdRule, ParametersRule,
15+
RefRule, ReplyRule, Rules, SectionRule, SignatureKidRule, VerRule,
1716
};
1817

1918
use crate::{
@@ -41,6 +40,8 @@ fn proposal_rule() -> Rules {
4140
CATEGORY_PARAMETERS.clone(),
4241
];
4342
Rules {
43+
id: IdRule,
44+
ver: VerRule,
4445
content_type: ContentTypeRule {
4546
exp: ContentType::Json,
4647
},
@@ -75,6 +76,8 @@ fn proposal_comment_rule() -> Rules {
7576
CATEGORY_PARAMETERS.clone(),
7677
];
7778
Rules {
79+
id: IdRule,
80+
ver: VerRule,
7881
content_type: ContentTypeRule {
7982
exp: ContentType::Json,
8083
},
@@ -126,6 +129,8 @@ fn proposal_submission_action_rule() -> Rules {
126129
.expect("Must be a valid json scheme file");
127130

128131
Rules {
132+
id: IdRule,
133+
ver: VerRule,
129134
content_type: ContentTypeRule {
130135
exp: ContentType::Json,
131136
},
@@ -190,10 +195,6 @@ where
190195
return Ok(false);
191196
};
192197

193-
if !validate_id_and_ver(doc, provider)? {
194-
return Ok(false);
195-
}
196-
197198
let Some(rules) = DOCUMENT_RULES.get(doc_type) else {
198199
doc.report().invalid_value(
199200
"`type`",
@@ -206,106 +207,6 @@ where
206207
rules.check(doc, provider).await
207208
}
208209

209-
/// Validates document `id` and `ver` fields on the timestamps:
210-
/// 1. document `ver` cannot be smaller than document id field
211-
/// 2. If `provider.future_threshold()` not `None`, document `id` cannot be too far in the
212-
/// future (`future_threshold` arg) from `SystemTime::now()` based on the provide
213-
/// threshold
214-
/// 3. If `provider.future_threshold()` not `None`, document `id` cannot be too far behind
215-
/// (`past_threshold` arg) from `SystemTime::now()` based on the provide threshold
216-
fn validate_id_and_ver<Provider>(
217-
doc: &CatalystSignedDocument,
218-
provider: &Provider,
219-
) -> anyhow::Result<bool>
220-
where
221-
Provider: CatalystSignedDocumentProvider,
222-
{
223-
let id = doc.doc_id().ok();
224-
let ver = doc.doc_ver().ok();
225-
if id.is_none() {
226-
doc.report().missing_field(
227-
"id",
228-
"Can't get a document id during the validation process",
229-
);
230-
}
231-
if ver.is_none() {
232-
doc.report().missing_field(
233-
"ver",
234-
"Can't get a document ver during the validation process",
235-
);
236-
}
237-
match (id, ver) {
238-
(Some(id), Some(ver)) => {
239-
let mut is_valid = true;
240-
if ver < id {
241-
doc.report().invalid_value(
242-
"ver",
243-
&ver.to_string(),
244-
"ver < id",
245-
&format!("Document Version {ver} cannot be smaller than Document ID {id}"),
246-
);
247-
is_valid = false;
248-
}
249-
250-
let (ver_time_secs, ver_time_nanos) = ver
251-
.uuid()
252-
.get_timestamp()
253-
.ok_or(anyhow::anyhow!("Document ver field must be a UUIDv7"))?
254-
.to_unix();
255-
256-
let Some(ver_time) =
257-
SystemTime::UNIX_EPOCH.checked_add(Duration::new(ver_time_secs, ver_time_nanos))
258-
else {
259-
doc.report().invalid_value(
260-
"ver",
261-
&ver.to_string(),
262-
"Must a valid duration since `UNIX_EPOCH`",
263-
"Cannot instantiate a valid `SystemTime` value from the provided `ver` field timestamp.",
264-
);
265-
return Ok(false);
266-
};
267-
268-
let now = SystemTime::now();
269-
270-
if let Ok(version_age) = ver_time.duration_since(now) {
271-
// `now` is earlier than `ver_time`
272-
if let Some(future_threshold) = provider.future_threshold() {
273-
if version_age > future_threshold {
274-
doc.report().invalid_value(
275-
"ver",
276-
&ver.to_string(),
277-
"ver < now + future_threshold",
278-
&format!("Document Version timestamp {id} cannot be too far in future (threshold: {future_threshold:?}) from now: {now:?}"),
279-
);
280-
is_valid = false;
281-
}
282-
}
283-
} else {
284-
// `ver_time` is earlier than `now`
285-
let version_age = now
286-
.duration_since(ver_time)
287-
.context("BUG! `ver_time` must be earlier than `now` at this place")?;
288-
289-
if let Some(past_threshold) = provider.past_threshold() {
290-
if version_age > past_threshold {
291-
doc.report().invalid_value(
292-
"ver",
293-
&ver.to_string(),
294-
"ver > now - past_threshold",
295-
&format!("Document Version timestamp {id} cannot be too far behind (threshold: {past_threshold:?}) from now: {now:?}",),
296-
);
297-
is_valid = false;
298-
}
299-
}
300-
}
301-
302-
Ok(is_valid)
303-
},
304-
305-
_ => Ok(false),
306-
}
307-
}
308-
309210
/// Verify document signatures.
310211
/// Return true if all signatures are valid, otherwise return false.
311212
///
@@ -384,82 +285,7 @@ where
384285

385286
#[cfg(test)]
386287
mod tests {
387-
use std::time::SystemTime;
388-
389-
use uuid::{Timestamp, Uuid};
390-
391-
use crate::{
392-
builder::tests::Builder,
393-
metadata::SupportedField,
394-
providers::{tests::TestCatalystSignedDocumentProvider, CatalystSignedDocumentProvider},
395-
validator::{document_rules_init, validate_id_and_ver},
396-
UuidV7,
397-
};
398-
399-
#[test]
400-
fn document_id_and_ver_test() {
401-
let provider = TestCatalystSignedDocumentProvider::default();
402-
let now = SystemTime::now()
403-
.duration_since(SystemTime::UNIX_EPOCH)
404-
.unwrap()
405-
.as_secs();
406-
407-
let uuid_v7 = UuidV7::new();
408-
let doc = Builder::new()
409-
.with_metadata_field(SupportedField::Id(uuid_v7))
410-
.with_metadata_field(SupportedField::Ver(uuid_v7))
411-
.build();
412-
413-
let is_valid = validate_id_and_ver(&doc, &provider).unwrap();
414-
assert!(is_valid);
415-
416-
let ver = Uuid::new_v7(Timestamp::from_unix_time(now - 1, 0, 0, 0))
417-
.try_into()
418-
.unwrap();
419-
let id = Uuid::new_v7(Timestamp::from_unix_time(now + 1, 0, 0, 0))
420-
.try_into()
421-
.unwrap();
422-
assert!(ver < id);
423-
let doc = Builder::new()
424-
.with_metadata_field(SupportedField::Id(id))
425-
.with_metadata_field(SupportedField::Ver(ver))
426-
.build();
427-
428-
let is_valid = validate_id_and_ver(&doc, &provider).unwrap();
429-
assert!(!is_valid);
430-
431-
let to_far_in_past = Uuid::new_v7(Timestamp::from_unix_time(
432-
now - provider.past_threshold().unwrap().as_secs() - 1,
433-
0,
434-
0,
435-
0,
436-
))
437-
.try_into()
438-
.unwrap();
439-
let doc = Builder::new()
440-
.with_metadata_field(SupportedField::Id(to_far_in_past))
441-
.with_metadata_field(SupportedField::Ver(to_far_in_past))
442-
.build();
443-
444-
let is_valid = validate_id_and_ver(&doc, &provider).unwrap();
445-
assert!(!is_valid);
446-
447-
let to_far_in_future = Uuid::new_v7(Timestamp::from_unix_time(
448-
now + provider.future_threshold().unwrap().as_secs() + 1,
449-
0,
450-
0,
451-
0,
452-
))
453-
.try_into()
454-
.unwrap();
455-
let doc = Builder::new()
456-
.with_metadata_field(SupportedField::Id(to_far_in_future))
457-
.with_metadata_field(SupportedField::Ver(to_far_in_future))
458-
.build();
459-
460-
let is_valid = validate_id_and_ver(&doc, &provider).unwrap();
461-
assert!(!is_valid);
462-
}
288+
use crate::validator::document_rules_init;
463289

464290
#[test]
465291
fn document_rules_init_test() {

0 commit comments

Comments
 (0)