From ba2eabacd0f7c71c8317c7aeebf8c4b97e1828c0 Mon Sep 17 00:00:00 2001 From: Apisit Ritruengroj Date: Fri, 19 Sep 2025 21:02:30 +0700 Subject: [PATCH 01/11] feat: initial chain --- rust/signed_doc/src/lib.rs | 3 ++- rust/signed_doc/src/metadata/chain.rs | 7 ++++++ rust/signed_doc/src/metadata/mod.rs | 2 ++ .../src/metadata/supported_field.rs | 24 ++++++++++++------- 4 files changed, 27 insertions(+), 9 deletions(-) create mode 100644 rust/signed_doc/src/metadata/chain.rs diff --git a/rust/signed_doc/src/lib.rs b/rust/signed_doc/src/lib.rs index 38f0d35d78..5fa17f895e 100644 --- a/rust/signed_doc/src/lib.rs +++ b/rust/signed_doc/src/lib.rs @@ -24,7 +24,8 @@ use cbork_utils::{array::Array, decode_context::DecodeCtx, with_cbor_bytes::With pub use content::Content; use decode_context::{CompatibilityPolicy, DecodeContext}; pub use metadata::{ - ContentEncoding, ContentType, DocLocator, DocType, DocumentRef, DocumentRefs, Metadata, Section, + Chain, ContentEncoding, ContentType, DocLocator, DocType, DocumentRef, DocumentRefs, Metadata, + Section, }; use minicbor::{decode, encode, Decode, Decoder, Encode}; pub use signature::{CatalystId, Signatures}; diff --git a/rust/signed_doc/src/metadata/chain.rs b/rust/signed_doc/src/metadata/chain.rs new file mode 100644 index 0000000000..0164cd220c --- /dev/null +++ b/rust/signed_doc/src/metadata/chain.rs @@ -0,0 +1,7 @@ +//! Document Payload Chain. + +use std::hash::Hash; + +/// Document type - `Chain`. +#[derive(Clone, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)] +pub struct Chain; diff --git a/rust/signed_doc/src/metadata/mod.rs b/rust/signed_doc/src/metadata/mod.rs index a90a5acd7c..ae7928b83c 100644 --- a/rust/signed_doc/src/metadata/mod.rs +++ b/rust/signed_doc/src/metadata/mod.rs @@ -4,6 +4,7 @@ use std::{ fmt::{Display, Formatter}, }; +mod chain; mod collaborators; mod content_encoding; mod content_type; @@ -13,6 +14,7 @@ mod section; mod supported_field; use catalyst_types::{catalyst_id::CatalystId, problem_report::ProblemReport, uuid::UuidV7}; +pub use chain::Chain; pub use content_encoding::ContentEncoding; pub use content_type::ContentType; pub use doc_type::DocType; diff --git a/rust/signed_doc/src/metadata/supported_field.rs b/rust/signed_doc/src/metadata/supported_field.rs index 08e4b938e5..191189bb77 100644 --- a/rust/signed_doc/src/metadata/supported_field.rs +++ b/rust/signed_doc/src/metadata/supported_field.rs @@ -7,8 +7,8 @@ use serde::Deserialize; use strum::{EnumDiscriminants, EnumTryAs, IntoDiscriminant as _}; use crate::{ - metadata::collaborators::Collaborators, ContentEncoding, ContentType, DocType, DocumentRefs, - Section, + metadata::collaborators::Collaborators, Chain, ContentEncoding, ContentType, DocType, + DocumentRefs, Section, }; /// COSE label. May be either a signed integer or a string. @@ -100,18 +100,20 @@ pub(crate) enum SupportedField { Ver(UuidV7) = 3, /// `type` field. Type(DocType) = 4, + /// `chain` field. + Chain(Chain) = 5, /// `reply` field. - Reply(DocumentRefs) = 5, + Reply(DocumentRefs) = 6, /// `section` field. - Section(Section) = 6, + Section(Section) = 7, /// `template` field. - Template(DocumentRefs) = 7, + Template(DocumentRefs) = 8, /// `parameters` field. - Parameters(DocumentRefs) = 8, + Parameters(DocumentRefs) = 9, /// `collaborators` field. - Collaborators(Collaborators) = 9, + Collaborators(Collaborators) = 10, /// `Content-Encoding` field. - ContentEncoding(ContentEncoding) = 10, + ContentEncoding(ContentEncoding) = 11, } impl SupportedLabel { @@ -124,6 +126,7 @@ impl SupportedLabel { Label::Str("ref") => Some(Self::Ref), Label::Str("ver") => Some(Self::Ver), Label::Str("type") => Some(Self::Type), + Label::Str("chain") => Some(Self::Chain), Label::Str("reply") => Some(Self::Reply), Label::Str("collaborators") => Some(Self::Collaborators), Label::Str("section") => Some(Self::Section), @@ -146,6 +149,7 @@ impl SupportedLabel { Self::Ref => Label::Str("ref"), Self::Ver => Label::Str("ver"), Self::Type => Label::Str("type"), + Self::Chain => Label::Str("chain"), Self::Reply => Label::Str("reply"), Self::Collaborators => Label::Str("collaborators"), Self::Section => Label::Str("section"), @@ -179,6 +183,7 @@ impl serde::ser::Serialize for SupportedField { match self { Self::Id(v) | Self::Ver(v) => v.serialize(serializer), Self::Type(v) => v.serialize(serializer), + Self::Chain(v) => v.serialize(serializer), Self::ContentType(v) => v.serialize(serializer), Self::ContentEncoding(v) => v.serialize(serializer), Self::Ref(v) | Self::Reply(v) | Self::Template(v) | Self::Parameters(v) => { @@ -205,6 +210,7 @@ impl<'de> serde::de::DeserializeSeed<'de> for SupportedLabel { SupportedLabel::Ref => Deserialize::deserialize(d).map(SupportedField::Ref), SupportedLabel::Ver => Deserialize::deserialize(d).map(SupportedField::Ver), SupportedLabel::Type => Deserialize::deserialize(d).map(SupportedField::Type), + SupportedLabel::Chain => Deserialize::deserialize(d).map(SupportedField::Chain), SupportedLabel::Reply => Deserialize::deserialize(d).map(SupportedField::Reply), SupportedLabel::Collaborators => { Deserialize::deserialize(d).map(SupportedField::Collaborators) @@ -253,6 +259,7 @@ impl minicbor::Decode<'_, crate::decode_context::DecodeContext> for Option d.decode().map(SupportedField::Type), + SupportedLabel::Chain => d.decode().map(SupportedField::Chain), SupportedLabel::Reply => { d.decode_with(&mut ctx.policy().clone()) .map(SupportedField::Reply) @@ -314,6 +321,7 @@ impl minicbor::Encode<()> for SupportedField { | SupportedField::Template(document_ref) | SupportedField::Parameters(document_ref) => document_ref.encode(e, ctx), SupportedField::Type(doc_type) => doc_type.encode(e, ctx), + SupportedField::Chain(chain) => chain.encode(e, ctx), SupportedField::Collaborators(collaborators) => collaborators.encode(e, ctx), SupportedField::Section(section) => section.encode(e, ctx), SupportedField::ContentEncoding(content_encoding) => content_encoding.encode(e, ctx), From 6ef91c784ea297339fca2324fe30ac29be403c66 Mon Sep 17 00:00:00 2001 From: Apisit Ritruengroj Date: Mon, 22 Sep 2025 19:47:01 +0700 Subject: [PATCH 02/11] feat: chain body --- rust/signed_doc/src/metadata/chain.rs | 28 +++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/rust/signed_doc/src/metadata/chain.rs b/rust/signed_doc/src/metadata/chain.rs index 0164cd220c..7349d893c0 100644 --- a/rust/signed_doc/src/metadata/chain.rs +++ b/rust/signed_doc/src/metadata/chain.rs @@ -1,7 +1,31 @@ //! Document Payload Chain. +//! +//! ref: https://input-output-hk.github.io/catalyst-libs/architecture/08_concepts/signed_doc/metadata/#chain-link use std::hash::Hash; +use crate::DocumentRef; + /// Document type - `Chain`. -#[derive(Clone, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)] -pub struct Chain; +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct Chain { + /// The consecutive sequence number of the current document + /// in the chain. + /// The very first document in a sequence is numbered `0` and it + /// *MUST ONLY* increment by one for each successive document in + /// the sequence. + /// + /// The FINAL sequence number is encoded with the current height + /// sequence value, negated. + /// + /// For example the following values for height define a chain + /// that has 5 documents in the sequence 0-4, the final height + /// is negated to indicate the end of the chain: + /// `0, 1, 2, 3, -4` + /// + /// No subsequent document can be chained to a sequence that has + /// a final chain height. + height: i32, + /// Reference to a single Signed Document. + document_ref: Option +} From c43d297560a8dac18db8aa67efdddeee7fb9b431 Mon Sep 17 00:00:00 2001 From: Apisit Ritruengroj Date: Mon, 22 Sep 2025 19:52:29 +0700 Subject: [PATCH 03/11] feat: serde template --- rust/signed_doc/src/metadata/chain.rs | 85 ++++++++++++++++++++------- 1 file changed, 64 insertions(+), 21 deletions(-) diff --git a/rust/signed_doc/src/metadata/chain.rs b/rust/signed_doc/src/metadata/chain.rs index 7349d893c0..7c04fac871 100644 --- a/rust/signed_doc/src/metadata/chain.rs +++ b/rust/signed_doc/src/metadata/chain.rs @@ -1,31 +1,74 @@ //! Document Payload Chain. -//! +//! //! ref: https://input-output-hk.github.io/catalyst-libs/architecture/08_concepts/signed_doc/metadata/#chain-link use std::hash::Hash; use crate::DocumentRef; -/// Document type - `Chain`. +/// Reference to the previous Signed Document in a sequence. #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct Chain { - /// The consecutive sequence number of the current document - /// in the chain. - /// The very first document in a sequence is numbered `0` and it - /// *MUST ONLY* increment by one for each successive document in - /// the sequence. - /// - /// The FINAL sequence number is encoded with the current height - /// sequence value, negated. - /// - /// For example the following values for height define a chain - /// that has 5 documents in the sequence 0-4, the final height - /// is negated to indicate the end of the chain: - /// `0, 1, 2, 3, -4` - /// - /// No subsequent document can be chained to a sequence that has - /// a final chain height. - height: i32, - /// Reference to a single Signed Document. - document_ref: Option + /// The consecutive sequence number of the current document + /// in the chain. + /// The very first document in a sequence is numbered `0` and it + /// *MUST ONLY* increment by one for each successive document in + /// the sequence. + /// + /// The FINAL sequence number is encoded with the current height + /// sequence value, negated. + /// + /// For example the following values for height define a chain + /// that has 5 documents in the sequence 0-4, the final height + /// is negated to indicate the end of the chain: + /// `0, 1, 2, 3, -4` + /// + /// No subsequent document can be chained to a sequence that has + /// a final chain height. + height: i32, + /// Reference to a single Signed Document. + /// + /// Can be *ONLY* omitted in the very first document in a sequence. + document_ref: Option, +} + +impl<'de> serde::Deserialize<'de> for Chain { + fn deserialize(deserializer: D) -> Result + where D: serde::Deserializer<'de> { + // TODO: + unimplemented!() + } +} + +impl serde::Serialize for Chain { + fn serialize( + &self, + serializer: S, + ) -> Result + where + S: serde::Serializer, + { + unimplemented!() + } +} + +impl minicbor::Encode<()> for Chain { + fn encode( + &self, + e: &mut minicbor::Encoder, + _ctx: &mut (), + ) -> Result<(), minicbor::encode::Error> { + // TODO: + Ok(()) + } +} + +impl minicbor::Decode<'_, ()> for Chain { + fn decode( + d: &mut minicbor::Decoder<'_>, + _ctx: &mut (), + ) -> Result { + // TODO: + unimplemented!(); + } } From 01b1aa71a7ad2e95ba646ce762f506dca617b9e6 Mon Sep 17 00:00:00 2001 From: Apisit Ritruengroj Date: Tue, 23 Sep 2025 16:42:29 +0700 Subject: [PATCH 04/11] feat: cbor decoder --- rust/signed_doc/src/metadata/chain.rs | 29 +++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/rust/signed_doc/src/metadata/chain.rs b/rust/signed_doc/src/metadata/chain.rs index 7c04fac871..3bd918d136 100644 --- a/rust/signed_doc/src/metadata/chain.rs +++ b/rust/signed_doc/src/metadata/chain.rs @@ -4,6 +4,8 @@ use std::hash::Hash; +use cbork_utils::{array::Array, decode_context::DecodeCtx}; + use crate::DocumentRef; /// Reference to the previous Signed Document in a sequence. @@ -68,7 +70,30 @@ impl minicbor::Decode<'_, ()> for Chain { d: &mut minicbor::Decoder<'_>, _ctx: &mut (), ) -> Result { - // TODO: - unimplemented!(); + const CONTEXT: &str = "Chain decoding"; + + let arr = Array::decode(d, &mut DecodeCtx::Deterministic)?; + + let Some(height) = arr.get(0) else { + return Err(minicbor::decode::Error::message(format!( + "{CONTEXT}: expected [height, ? document_ref], found empty array" + ))); + }; + + let height = minicbor::Decoder::new(height).int()?; + let height = height.try_into().map_err(minicbor::decode::Error::custom)?; + + let document_ref = match arr.get(1) { + Some(value) => { + let mut d = minicbor::Decoder::new(value); + Some(DocumentRef::decode(&mut d, &mut ())?) + }, + None => None, + }; + + Ok(Self { + height, + document_ref, + }) } } From b4da2d724a1c913da86bd0ee3c639f64541fede1 Mon Sep 17 00:00:00 2001 From: Apisit Ritruengroj Date: Tue, 23 Sep 2025 16:54:31 +0700 Subject: [PATCH 05/11] feat: encoder --- rust/signed_doc/src/metadata/chain.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/rust/signed_doc/src/metadata/chain.rs b/rust/signed_doc/src/metadata/chain.rs index 3bd918d136..63ecdd2187 100644 --- a/rust/signed_doc/src/metadata/chain.rs +++ b/rust/signed_doc/src/metadata/chain.rs @@ -60,7 +60,11 @@ impl minicbor::Encode<()> for Chain { e: &mut minicbor::Encoder, _ctx: &mut (), ) -> Result<(), minicbor::encode::Error> { - // TODO: + e.array(if self.document_ref.is_some() { 2 } else { 1 })?; + self.height.encode(e, &mut ())?; + if let Some(document_ref) = &self.document_ref { + document_ref.encode(e, &mut ())?; + } Ok(()) } } @@ -74,18 +78,18 @@ impl minicbor::Decode<'_, ()> for Chain { let arr = Array::decode(d, &mut DecodeCtx::Deterministic)?; - let Some(height) = arr.get(0) else { + let Some(height_bytes) = arr.get(0) else { return Err(minicbor::decode::Error::message(format!( "{CONTEXT}: expected [height, ? document_ref], found empty array" ))); }; - let height = minicbor::Decoder::new(height).int()?; + let height = minicbor::Decoder::new(height_bytes).int()?; let height = height.try_into().map_err(minicbor::decode::Error::custom)?; let document_ref = match arr.get(1) { - Some(value) => { - let mut d = minicbor::Decoder::new(value); + Some(bytes) => { + let mut d = minicbor::Decoder::new(bytes); Some(DocumentRef::decode(&mut d, &mut ())?) }, None => None, From a006a70310053384d5af9194c2eb6e8240fca4a0 Mon Sep 17 00:00:00 2001 From: Apisit Ritruengroj Date: Tue, 23 Sep 2025 18:29:50 +0700 Subject: [PATCH 06/11] feat: proper serde for doc_refs --- rust/signed_doc/src/metadata/chain.rs | 29 ++++------- .../src/metadata/document_refs/doc_locator.rs | 52 ++++++++++++++----- .../src/metadata/document_refs/doc_ref.rs | 3 +- .../src/metadata/document_refs/mod.rs | 40 ++------------ 4 files changed, 56 insertions(+), 68 deletions(-) diff --git a/rust/signed_doc/src/metadata/chain.rs b/rust/signed_doc/src/metadata/chain.rs index 63ecdd2187..8f160d7c9b 100644 --- a/rust/signed_doc/src/metadata/chain.rs +++ b/rust/signed_doc/src/metadata/chain.rs @@ -2,14 +2,14 @@ //! //! ref: https://input-output-hk.github.io/catalyst-libs/architecture/08_concepts/signed_doc/metadata/#chain-link -use std::hash::Hash; +use std::{fmt::Display, hash::Hash}; use cbork_utils::{array::Array, decode_context::DecodeCtx}; use crate::DocumentRef; /// Reference to the previous Signed Document in a sequence. -#[derive(Clone, Debug, PartialEq, Eq, Hash)] +#[derive(Clone, Debug, PartialEq, Eq, Hash, serde::Deserialize, serde::Serialize)] pub struct Chain { /// The consecutive sequence number of the current document /// in the chain. @@ -34,23 +34,16 @@ pub struct Chain { document_ref: Option, } -impl<'de> serde::Deserialize<'de> for Chain { - fn deserialize(deserializer: D) -> Result - where D: serde::Deserializer<'de> { - // TODO: - unimplemented!() - } -} - -impl serde::Serialize for Chain { - fn serialize( +impl Display for Chain { + fn fmt( &self, - serializer: S, - ) -> Result - where - S: serde::Serializer, - { - unimplemented!() + f: &mut std::fmt::Formatter<'_>, + ) -> std::fmt::Result { + if let Some(document_ref) = &self.document_ref { + write!(f, "height: {}, document_ref: {}", self.height, document_ref) + } else { + write!(f, "height: {}", self.height) + } } } diff --git a/rust/signed_doc/src/metadata/document_refs/doc_locator.rs b/rust/signed_doc/src/metadata/document_refs/doc_locator.rs index 5987ddf848..8982f79f04 100644 --- a/rust/signed_doc/src/metadata/document_refs/doc_locator.rs +++ b/rust/signed_doc/src/metadata/document_refs/doc_locator.rs @@ -2,11 +2,13 @@ //! A [CBOR Encoded IPLD Content Identifier](https://github.com/ipld/cid-cbor/) //! or also known as [IPFS CID](https://docs.ipfs.tech/concepts/content-addressing/#what-is-a-cid). -use std::fmt::Display; +use std::{fmt::Display, ops::Deref, str::FromStr}; use cbork_utils::{decode_context::DecodeCtx, map::Map}; use minicbor::{Decode, Decoder, Encode}; +use crate::metadata::document_refs::DocRefError; + /// CBOR tag of IPLD content identifiers (CIDs). const CID_TAG: u64 = 42; @@ -20,23 +22,17 @@ const DOC_LOC_MAP_ITEM: u64 = 1; #[derive(Clone, Debug, Default, PartialEq, Hash, Eq)] pub struct DocLocator(Vec); -impl DocLocator { - #[must_use] - /// Length of the document locator. - pub fn len(&self) -> usize { - self.0.len() - } +impl Deref for DocLocator { + type Target = Vec; - #[must_use] - /// Is the document locator empty. - pub fn is_empty(&self) -> bool { - self.0.is_empty() + fn deref(&self) -> &Self::Target { + &self.0 } } impl From> for DocLocator { fn from(value: Vec) -> Self { - DocLocator(value) + Self(value) } } @@ -49,6 +45,38 @@ impl Display for DocLocator { } } +impl FromStr for DocLocator { + type Err = DocRefError; + + fn from_str(s: &str) -> Result { + s.strip_prefix("0x") + .map(hex::decode) + .ok_or(DocRefError::HexDecode("missing 0x prefix".to_string()))? + .map(Self) + .map_err(|e| DocRefError::HexDecode(e.to_string())) + } +} + +impl<'de> serde::Deserialize<'de> for DocLocator { + fn deserialize(deserializer: D) -> Result + where D: serde::Deserializer<'de> { + let s = String::deserialize(deserializer)?; + s.parse::().map_err(serde::de::Error::custom) + } +} + +impl serde::Serialize for DocLocator { + fn serialize( + &self, + serializer: S, + ) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(&self.to_string()) + } +} + // document_locator = { "cid" => cid } impl Decode<'_, ()> for DocLocator { fn decode( diff --git a/rust/signed_doc/src/metadata/document_refs/doc_ref.rs b/rust/signed_doc/src/metadata/document_refs/doc_ref.rs index 1fd72f6338..f2c9365c92 100644 --- a/rust/signed_doc/src/metadata/document_refs/doc_ref.rs +++ b/rust/signed_doc/src/metadata/document_refs/doc_ref.rs @@ -12,13 +12,14 @@ use super::doc_locator::DocLocator; const DOC_REF_ARR_ITEM: u64 = 3; /// Reference to a Document. -#[derive(Clone, Debug, PartialEq, Hash, Eq)] +#[derive(Clone, Debug, PartialEq, Hash, Eq, serde::Serialize, serde::Deserialize)] pub struct DocumentRef { /// Reference to the Document Id id: UuidV7, /// Reference to the Document Ver ver: UuidV7, /// Document locator + #[serde(rename = "cid")] doc_locator: DocLocator, } diff --git a/rust/signed_doc/src/metadata/document_refs/mod.rs b/rust/signed_doc/src/metadata/document_refs/mod.rs index f13a1eac94..6922f7ee98 100644 --- a/rust/signed_doc/src/metadata/document_refs/mod.rs +++ b/rust/signed_doc/src/metadata/document_refs/mod.rs @@ -203,24 +203,13 @@ mod serde_impl { ver: String, } - /// New structure as deserialize as map {id, ver, cid} - #[derive(serde::Deserialize, serde::Serialize)] - struct NewRef { - /// "id": "uuidv7" - id: String, - /// "ver": "uuidv7" - ver: String, - /// "cid": "0x..." - cid: String, - } - #[derive(serde::Deserialize)] #[serde(untagged)] enum DocRefSerde { /// Old structure of document reference. Old(OldRef), /// New structure of document reference. - New(Vec), + New(Vec), } impl serde::Serialize for DocumentRefs { @@ -231,14 +220,7 @@ mod serde_impl { where S: serde::Serializer, { - let iter = self.0.iter().map(|v| { - NewRef { - id: v.id().to_string(), - ver: v.ver().to_string(), - cid: v.doc_locator().to_string(), - } - }); - serializer.collect_seq(iter) + self.0.serialize(serializer) } } @@ -261,23 +243,7 @@ mod serde_impl { DocLocator::default(), )])) }, - DocRefSerde::New(value) => { - let mut dr = vec![]; - for v in value { - let id = UuidV7::from_str(&v.id).map_err(|_| { - serde::de::Error::custom(DocRefError::StringConversion(v.id.clone())) - })?; - let ver = UuidV7::from_str(&v.ver).map_err(|_| { - serde::de::Error::custom(DocRefError::StringConversion(v.ver.clone())) - })?; - let cid = &v.cid.strip_prefix("0x").unwrap_or(&v.cid); - let locator = hex::decode(cid).map_err(|_| { - serde::de::Error::custom(DocRefError::HexDecode(v.cid.clone())) - })?; - dr.push(DocumentRef::new(id, ver, locator.into())); - } - Ok(DocumentRefs(dr)) - }, + DocRefSerde::New(v) => Ok(DocumentRefs(v)), } } } From 0c6c281512c8b49dacf0762ffb1d4e63486a02ea Mon Sep 17 00:00:00 2001 From: Apisit Ritruengroj Date: Tue, 23 Sep 2025 19:00:13 +0700 Subject: [PATCH 07/11] fix: deserializer --- .../src/metadata/document_refs/doc_ref.rs | 2 +- .../src/metadata/document_refs/mod.rs | 33 +++---------------- 2 files changed, 5 insertions(+), 30 deletions(-) diff --git a/rust/signed_doc/src/metadata/document_refs/doc_ref.rs b/rust/signed_doc/src/metadata/document_refs/doc_ref.rs index f2c9365c92..c775f9942b 100644 --- a/rust/signed_doc/src/metadata/document_refs/doc_ref.rs +++ b/rust/signed_doc/src/metadata/document_refs/doc_ref.rs @@ -19,7 +19,7 @@ pub struct DocumentRef { /// Reference to the Document Ver ver: UuidV7, /// Document locator - #[serde(rename = "cid")] + #[serde(rename = "cid", default)] doc_locator: DocLocator, } diff --git a/rust/signed_doc/src/metadata/document_refs/mod.rs b/rust/signed_doc/src/metadata/document_refs/mod.rs index 6922f7ee98..0afc321caf 100644 --- a/rust/signed_doc/src/metadata/document_refs/mod.rs +++ b/rust/signed_doc/src/metadata/document_refs/mod.rs @@ -190,24 +190,13 @@ impl Encode<()> for DocumentRefs { mod serde_impl { //! `serde::Deserialize` and `serde::Serialize` trait implementations - use std::str::FromStr; - - use super::{DocLocator, DocRefError, DocumentRef, DocumentRefs, UuidV7}; - - /// Old structure deserialize as map {id, ver} - #[derive(serde::Deserialize)] - struct OldRef { - /// "id": "uuidv7 - id: String, - /// "ver": "uuidv7" - ver: String, - } + use super::{DocumentRef, DocumentRefs}; #[derive(serde::Deserialize)] #[serde(untagged)] enum DocRefSerde { /// Old structure of document reference. - Old(OldRef), + Old(DocumentRef), /// New structure of document reference. New(Vec), } @@ -227,22 +216,8 @@ mod serde_impl { impl<'de> serde::Deserialize<'de> for DocumentRefs { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de> { - let input = DocRefSerde::deserialize(deserializer)?; - match input { - DocRefSerde::Old(v) => { - let id = UuidV7::from_str(&v.id).map_err(|_| { - serde::de::Error::custom(DocRefError::StringConversion(v.id.clone())) - })?; - let ver = UuidV7::from_str(&v.ver).map_err(|_| { - serde::de::Error::custom(DocRefError::StringConversion(v.ver.clone())) - })?; - - Ok(DocumentRefs(vec![DocumentRef::new( - id, - ver, - DocLocator::default(), - )])) - }, + match DocRefSerde::deserialize(deserializer)? { + DocRefSerde::Old(v) => Ok(DocumentRefs(vec![v])), DocRefSerde::New(v) => Ok(DocumentRefs(v)), } } From d2ec255f57bc98daaba43c7a1f3f9cf8cd469f89 Mon Sep 17 00:00:00 2001 From: Apisit Ritruengroj Date: Tue, 23 Sep 2025 19:07:55 +0700 Subject: [PATCH 08/11] chore: lintfix --- rust/cardano-chain-follower/examples/follow_chains.rs | 2 +- .../cardano-chain-follower/src/mithril_snapshot_config.rs | 2 +- rust/catalyst-types/src/catalyst_id/role_index.rs | 8 ++------ rust/hermes-ipfs/examples/pubsub.rs | 4 ++-- rust/signed_doc/src/metadata/chain.rs | 4 ++-- rust/signed_doc/src/metadata/document_refs/mod.rs | 1 + 6 files changed, 9 insertions(+), 12 deletions(-) diff --git a/rust/cardano-chain-follower/examples/follow_chains.rs b/rust/cardano-chain-follower/examples/follow_chains.rs index 6d304ec6d4..41fe8094e8 100644 --- a/rust/cardano-chain-follower/examples/follow_chains.rs +++ b/rust/cardano-chain-follower/examples/follow_chains.rs @@ -343,7 +343,7 @@ async fn follow_for( || (chain_update.immutable() != last_immutable) || reached_tip || follow_all - || (updates % RUNNING_UPDATE_INTERVAL == 0) + || updates.is_multiple_of(RUNNING_UPDATE_INTERVAL) || (last_fork != chain_update.data.fork()) { current_era = this_era; diff --git a/rust/cardano-chain-follower/src/mithril_snapshot_config.rs b/rust/cardano-chain-follower/src/mithril_snapshot_config.rs index ec2456e7b6..a660fe986c 100644 --- a/rust/cardano-chain-follower/src/mithril_snapshot_config.rs +++ b/rust/cardano-chain-follower/src/mithril_snapshot_config.rs @@ -494,7 +494,7 @@ fn remove_whitespace(s: &str) -> String { /// Check if a string is an even number of hex digits. fn is_hex(s: &str) -> bool { - s.chars().count() % 2 == 0 && s.chars().all(|c| c.is_ascii_hexdigit()) + s.chars().count().is_multiple_of(2) && s.chars().all(|c| c.is_ascii_hexdigit()) } #[cfg(test)] diff --git a/rust/catalyst-types/src/catalyst_id/role_index.rs b/rust/catalyst-types/src/catalyst_id/role_index.rs index 697c2a6543..b29f815342 100644 --- a/rust/catalyst-types/src/catalyst_id/role_index.rs +++ b/rust/catalyst-types/src/catalyst_id/role_index.rs @@ -28,8 +28,10 @@ pub enum RoleIdError { #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, EnumIs)] #[repr(u8)] #[non_exhaustive] +#[derive(Default)] pub enum RoleId { /// Primary required role use for voting and commenting. + #[default] Role0 = 0, /// Delegated representative (dRep) that vote on behalf of delegators. DelegatedRepresentative = 1, @@ -85,12 +87,6 @@ impl RoleId { } } -impl Default for RoleId { - fn default() -> Self { - Self::Role0 - } -} - impl From for RoleId { fn from(value: u8) -> Self { match value { diff --git a/rust/hermes-ipfs/examples/pubsub.rs b/rust/hermes-ipfs/examples/pubsub.rs index 29d9e67c91..78695b677b 100644 --- a/rust/hermes-ipfs/examples/pubsub.rs +++ b/rust/hermes-ipfs/examples/pubsub.rs @@ -66,8 +66,8 @@ async fn main() -> anyhow::Result<()> { let mut event_stream = hermes_a.pubsub_events(option_topic.clone()).await?; let mut event_stream_b = hermes_b.pubsub_events(option_topic).await?; - let stream = hermes_a.pubsub_subscribe(topic.to_string()).await?; - let stream_b = hermes_b.pubsub_subscribe(topic.to_string()).await?; + let stream = hermes_a.pubsub_subscribe(topic.clone()).await?; + let stream_b = hermes_b.pubsub_subscribe(topic.clone()).await?; pin_mut!(stream); pin_mut!(stream_b); diff --git a/rust/signed_doc/src/metadata/chain.rs b/rust/signed_doc/src/metadata/chain.rs index 8f160d7c9b..e4c9e06919 100644 --- a/rust/signed_doc/src/metadata/chain.rs +++ b/rust/signed_doc/src/metadata/chain.rs @@ -1,6 +1,6 @@ //! Document Payload Chain. //! -//! ref: https://input-output-hk.github.io/catalyst-libs/architecture/08_concepts/signed_doc/metadata/#chain-link +//! ref: use std::{fmt::Display, hash::Hash}; @@ -71,7 +71,7 @@ impl minicbor::Decode<'_, ()> for Chain { let arr = Array::decode(d, &mut DecodeCtx::Deterministic)?; - let Some(height_bytes) = arr.get(0) else { + let Some(height_bytes) = arr.first() else { return Err(minicbor::decode::Error::message(format!( "{CONTEXT}: expected [height, ? document_ref], found empty array" ))); diff --git a/rust/signed_doc/src/metadata/document_refs/mod.rs b/rust/signed_doc/src/metadata/document_refs/mod.rs index 0afc321caf..690fa05c27 100644 --- a/rust/signed_doc/src/metadata/document_refs/mod.rs +++ b/rust/signed_doc/src/metadata/document_refs/mod.rs @@ -192,6 +192,7 @@ mod serde_impl { use super::{DocumentRef, DocumentRefs}; + /// A struct to support deserializing for both the old and new version of `ref`. #[derive(serde::Deserialize)] #[serde(untagged)] enum DocRefSerde { From 37271ddbfa1844d6d49848803a53351ee082acf0 Mon Sep 17 00:00:00 2001 From: Apisit Ritruengroj Date: Tue, 23 Sep 2025 20:24:34 +0700 Subject: [PATCH 09/11] feat: accessor --- rust/signed_doc/src/metadata/chain.rs | 4 ++-- rust/signed_doc/src/metadata/mod.rs | 8 ++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/rust/signed_doc/src/metadata/chain.rs b/rust/signed_doc/src/metadata/chain.rs index e4c9e06919..42ccb2f558 100644 --- a/rust/signed_doc/src/metadata/chain.rs +++ b/rust/signed_doc/src/metadata/chain.rs @@ -55,8 +55,8 @@ impl minicbor::Encode<()> for Chain { ) -> Result<(), minicbor::encode::Error> { e.array(if self.document_ref.is_some() { 2 } else { 1 })?; self.height.encode(e, &mut ())?; - if let Some(document_ref) = &self.document_ref { - document_ref.encode(e, &mut ())?; + if let Some(doc_ref) = &self.document_ref { + doc_ref.encode(e, &mut ())?; } Ok(()) } diff --git a/rust/signed_doc/src/metadata/mod.rs b/rust/signed_doc/src/metadata/mod.rs index 6c65cbf43b..6be81ff836 100644 --- a/rust/signed_doc/src/metadata/mod.rs +++ b/rust/signed_doc/src/metadata/mod.rs @@ -134,6 +134,13 @@ impl Metadata { .and_then(SupportedField::try_as_parameters_ref) } + /// Return `chain` field. + pub fn chain(&self) -> Option<&Chain> { + self.0 + .get(&SupportedLabel::Chain) + .and_then(SupportedField::try_as_chain_ref) + } + /// Add `SupportedField` into the `Metadata`. /// /// # Warning @@ -228,6 +235,7 @@ impl Display for Metadata { writeln!(f, " section: {:?},", self.section())?; writeln!(f, " collaborators: {:?},", self.collaborators())?; writeln!(f, " parameters: {:?},", self.parameters())?; + writeln!(f, " chain: {:?},", self.chain())?; writeln!(f, " }},")?; writeln!(f, "}}") } From 82ff5b4c16d4421a4eb70696b992088180f51295 Mon Sep 17 00:00:00 2001 From: Apisit Ritruengroj Date: Tue, 23 Sep 2025 21:49:37 +0700 Subject: [PATCH 10/11] test: initial --- rust/signed_doc/src/metadata/chain.rs | 46 +++++++++++++++++++++++++++ rust/signed_doc/tests/decoding.rs | 5 +++ 2 files changed, 51 insertions(+) diff --git a/rust/signed_doc/src/metadata/chain.rs b/rust/signed_doc/src/metadata/chain.rs index 42ccb2f558..626f608b8b 100644 --- a/rust/signed_doc/src/metadata/chain.rs +++ b/rust/signed_doc/src/metadata/chain.rs @@ -94,3 +94,49 @@ impl minicbor::Decode<'_, ()> for Chain { }) } } + +#[cfg(test)] +mod tests { + use catalyst_types::uuid::UuidV7; + use minicbor::{Decode, Decoder, Encode, Encoder}; + + use super::*; + use crate::DocLocator; + + #[test] + fn test_chain_encode_decode_without_doc_ref() { + let chain = Chain { + height: 0, + document_ref: None, + }; + + let mut buf = Vec::new(); + let mut enc = Encoder::new(&mut buf); + chain.encode(&mut enc, &mut ()).unwrap(); + + let mut dec = Decoder::new(&buf); + let decoded = Chain::decode(&mut dec, &mut ()).unwrap(); + + assert_eq!(decoded, chain); + } + + #[test] + fn test_chain_encode_decode_with_doc_ref() { + let id = UuidV7::new(); + let ver = UuidV7::new(); + + let chain = Chain { + height: 3, + document_ref: Some(DocumentRef::new(id, ver, DocLocator::default())), + }; + + let mut buf = Vec::new(); + let mut enc = Encoder::new(&mut buf); + chain.encode(&mut enc, &mut ()).unwrap(); + + let mut dec = Decoder::new(&buf); + let decoded = Chain::decode(&mut dec, &mut ()).unwrap(); + + assert_eq!(decoded, chain); + } +} diff --git a/rust/signed_doc/tests/decoding.rs b/rust/signed_doc/tests/decoding.rs index 633f4a7472..ad2d40ac22 100644 --- a/rust/signed_doc/tests/decoding.rs +++ b/rust/signed_doc/tests/decoding.rs @@ -636,6 +636,10 @@ fn signed_doc_with_complete_metadata_fields_case() -> TestCase { p_headers.bytes(b"id.catalyst://preprod.cardano/FftxFnOrj2qmTuB2oZG2v0YEWJfKvQ9Gg8AgNAhDsKE/7/3")?; /* cspell:enable */ p_headers.str("parameters")?.encode_with(uuid_v7, &mut catalyst_types::uuid::CborContext::Tagged)?; + p_headers.str("chain")?; + p_headers.array(2)?; + p_headers.int(0.into())?; + p_headers.encode_with(doc_ref.clone(), &mut ())?; e.bytes(p_headers.into_writer().as_slice())?; // empty unprotected headers @@ -1367,6 +1371,7 @@ fn catalyst_signed_doc_decoding_test() { signed_doc_with_random_header_field_case("section"), signed_doc_with_random_header_field_case("collaborators"), signed_doc_with_random_header_field_case("parameters"), + signed_doc_with_random_header_field_case("chain"), signed_doc_with_random_header_field_case("content-encoding"), signed_doc_with_parameters_and_aliases_case(&["parameters", "category_id"]), signed_doc_with_parameters_and_aliases_case(&["parameters", "brand_id"]), From 9ae8e57c2bd15fb32c06b6a5ce18c7444eff0387 Mon Sep 17 00:00:00 2001 From: Apisit Ritruengroj Date: Wed, 24 Sep 2025 13:06:11 +0700 Subject: [PATCH 11/11] chore: minor --- rust/signed_doc/src/metadata/chain.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/rust/signed_doc/src/metadata/chain.rs b/rust/signed_doc/src/metadata/chain.rs index 626f608b8b..4d53514e7e 100644 --- a/rust/signed_doc/src/metadata/chain.rs +++ b/rust/signed_doc/src/metadata/chain.rs @@ -80,13 +80,13 @@ impl minicbor::Decode<'_, ()> for Chain { let height = minicbor::Decoder::new(height_bytes).int()?; let height = height.try_into().map_err(minicbor::decode::Error::custom)?; - let document_ref = match arr.get(1) { - Some(bytes) => { + let document_ref = arr + .get(1) + .map(|bytes| { let mut d = minicbor::Decoder::new(bytes); - Some(DocumentRef::decode(&mut d, &mut ())?) - }, - None => None, - }; + DocumentRef::decode(&mut d, &mut ()) + }) + .transpose()?; Ok(Self { height,