From 96df98143845aac109a647bc9bbdb9eec50b95d1 Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Wed, 6 Nov 2024 12:55:32 +0200 Subject: [PATCH 01/18] add vote-tx-v2 crate --- rust/Cargo.toml | 4 +++- rust/jormungandr-vote-tx/src/lib.rs | 4 ++-- rust/vote-tx-v2/Cargo.toml | 13 +++++++++++++ rust/vote-tx-v2/src/lib.rs | 2 ++ 4 files changed, 20 insertions(+), 3 deletions(-) create mode 100644 rust/vote-tx-v2/Cargo.toml create mode 100644 rust/vote-tx-v2/src/lib.rs diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 94ed9ac812e..8b73eeb20c9 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -7,7 +7,9 @@ members = [ "cbork", "cbork-abnf-parser", "cbork-cddl-parser", - "catalyst-voting", "jormungandr-vote-tx", + "catalyst-voting", + "jormungandr-vote-tx", + "vote-tx-v2", ] [workspace.package] diff --git a/rust/jormungandr-vote-tx/src/lib.rs b/rust/jormungandr-vote-tx/src/lib.rs index c8859180aa9..d63ec6d15da 100644 --- a/rust/jormungandr-vote-tx/src/lib.rs +++ b/rust/jormungandr-vote-tx/src/lib.rs @@ -1,5 +1,5 @@ -//! A Jörmungandr transaction object structured following this -//! [spec](https://input-output-hk.github.io/catalyst-libs/architecture/08_concepts/catalyst_voting/jorm/) +//! A Jörmungandr vote transaction object, structured following this +//! [spec](https://input-output-hk.github.io/catalyst-libs/architecture/08_concepts/catalyst_voting/v1/) //! //! ```rust //! use catalyst_voting::{ diff --git a/rust/vote-tx-v2/Cargo.toml b/rust/vote-tx-v2/Cargo.toml new file mode 100644 index 00000000000..64c53e40d1c --- /dev/null +++ b/rust/vote-tx-v2/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "vote-tx-v2" +version = "0.1.0" +edition.workspace = true +authors.workspace = true +homepage.workspace = true +repository.workspace = true +license.workspace = true + +[lints] +workspace = true + +[dependencies] diff --git a/rust/vote-tx-v2/src/lib.rs b/rust/vote-tx-v2/src/lib.rs new file mode 100644 index 00000000000..808e4a40b1f --- /dev/null +++ b/rust/vote-tx-v2/src/lib.rs @@ -0,0 +1,2 @@ +//! A Catalyst vote transaction v1 object, structured following this +//! [spec](https://input-output-hk.github.io/catalyst-libs/architecture/08_concepts/catalyst_voting/v2/) From 15d9ca966cd38d5a0bf54989153a5abf7d0943ec Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Wed, 6 Nov 2024 13:02:29 +0200 Subject: [PATCH 02/18] rename jormungandr-vote-tx to vote-tx-v1 --- rust/Cargo.toml | 2 +- rust/Earthfile | 4 ++-- rust/{jormungandr-vote-tx => vote-tx-v1}/Cargo.toml | 2 +- rust/{jormungandr-vote-tx => vote-tx-v1}/src/decoding.rs | 0 rust/{jormungandr-vote-tx => vote-tx-v1}/src/lib.rs | 4 ++-- rust/{jormungandr-vote-tx => vote-tx-v1}/src/utils.rs | 0 rust/vote-tx-v2/Cargo.toml | 2 +- rust/vote-tx-v2/src/lib.rs | 5 +++++ 8 files changed, 12 insertions(+), 7 deletions(-) rename rust/{jormungandr-vote-tx => vote-tx-v1}/Cargo.toml (94%) rename rust/{jormungandr-vote-tx => vote-tx-v1}/src/decoding.rs (100%) rename rust/{jormungandr-vote-tx => vote-tx-v1}/src/lib.rs (98%) rename rust/{jormungandr-vote-tx => vote-tx-v1}/src/utils.rs (100%) diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 8b73eeb20c9..4bf0c71f2bf 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -8,7 +8,7 @@ members = [ "cbork-abnf-parser", "cbork-cddl-parser", "catalyst-voting", - "jormungandr-vote-tx", + "vote-tx-v1", "vote-tx-v2", ] diff --git a/rust/Earthfile b/rust/Earthfile index e0747f2b27e..17b681eeaa9 100644 --- a/rust/Earthfile +++ b/rust/Earthfile @@ -10,7 +10,7 @@ COPY_SRC: .cargo .config \ c509-certificate \ cardano-chain-follower \ - catalyst-voting jormungandr-vote-tx \ + catalyst-voting vote-tx-v1 vote-tx-v2 \ cbork cbork-abnf-parser cbork-cddl-parser \ hermes-ipfs \ . @@ -53,7 +53,7 @@ build: --cmd="/scripts/std_build.py" \ --args1="--libs=c509-certificate --libs=cardano-chain-follower --libs=hermes-ipfs" \ --args2="--libs=cbork-cddl-parser --libs=cbork-abnf-parser" \ - --args3="--libs=catalyst-voting --libs=jormungandr-vote-tx" \ + --args3="--libs=catalyst-voting --libs=vote-tx-v1 --libs=vote-tx-v2" \ --args4="--bins=cbork/cbork" \ --args5="--cov_report=$HOME/build/coverage-report.info" \ --output="release/[^\./]+" \ diff --git a/rust/jormungandr-vote-tx/Cargo.toml b/rust/vote-tx-v1/Cargo.toml similarity index 94% rename from rust/jormungandr-vote-tx/Cargo.toml rename to rust/vote-tx-v1/Cargo.toml index 42a2088acba..e99a5acab00 100644 --- a/rust/jormungandr-vote-tx/Cargo.toml +++ b/rust/vote-tx-v1/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "jormungandr-vote-tx" +name = "vote-tx-v1" version = "0.0.1" edition.workspace = true authors.workspace = true diff --git a/rust/jormungandr-vote-tx/src/decoding.rs b/rust/vote-tx-v1/src/decoding.rs similarity index 100% rename from rust/jormungandr-vote-tx/src/decoding.rs rename to rust/vote-tx-v1/src/decoding.rs diff --git a/rust/jormungandr-vote-tx/src/lib.rs b/rust/vote-tx-v1/src/lib.rs similarity index 98% rename from rust/jormungandr-vote-tx/src/lib.rs rename to rust/vote-tx-v1/src/lib.rs index d63ec6d15da..648b4e2df30 100644 --- a/rust/jormungandr-vote-tx/src/lib.rs +++ b/rust/vote-tx-v1/src/lib.rs @@ -1,4 +1,4 @@ -//! A Jörmungandr vote transaction object, structured following this +//! A Catalyst v1 (Jörmungandr) vote transaction object, structured following this //! [spec](https://input-output-hk.github.io/catalyst-libs/architecture/08_concepts/catalyst_voting/v1/) //! //! ```rust @@ -65,7 +65,7 @@ use catalyst_voting::{ }, }; -/// A v1 (Jörmungandr) transaction struct +/// A v1 (Jörmungandr) vote transaction struct #[derive(Debug, Clone, PartialEq, Eq)] #[must_use] pub struct Tx { diff --git a/rust/jormungandr-vote-tx/src/utils.rs b/rust/vote-tx-v1/src/utils.rs similarity index 100% rename from rust/jormungandr-vote-tx/src/utils.rs rename to rust/vote-tx-v1/src/utils.rs diff --git a/rust/vote-tx-v2/Cargo.toml b/rust/vote-tx-v2/Cargo.toml index 64c53e40d1c..b9d792fb903 100644 --- a/rust/vote-tx-v2/Cargo.toml +++ b/rust/vote-tx-v2/Cargo.toml @@ -10,4 +10,4 @@ license.workspace = true [lints] workspace = true -[dependencies] +[dependencies] \ No newline at end of file diff --git a/rust/vote-tx-v2/src/lib.rs b/rust/vote-tx-v2/src/lib.rs index 808e4a40b1f..fc119bf3445 100644 --- a/rust/vote-tx-v2/src/lib.rs +++ b/rust/vote-tx-v2/src/lib.rs @@ -1,2 +1,7 @@ //! A Catalyst vote transaction v1 object, structured following this //! [spec](https://input-output-hk.github.io/catalyst-libs/architecture/08_concepts/catalyst_voting/v2/) + +/// A v2 vote transaction struct +#[derive(Debug, Clone, PartialEq, Eq)] +#[must_use] +pub struct GeneralisedTx {} From db9a83ee4e97512f7b98486b6086627231d6d48c Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Wed, 6 Nov 2024 15:27:44 +0200 Subject: [PATCH 03/18] add Vote CBOR encoding/decoding --- rust/vote-tx-v2/Cargo.toml | 9 +++- rust/vote-tx-v2/src/lib.rs | 104 +++++++++++++++++++++++++++++++++++-- 2 files changed, 109 insertions(+), 4 deletions(-) diff --git a/rust/vote-tx-v2/Cargo.toml b/rust/vote-tx-v2/Cargo.toml index b9d792fb903..ab51412cdfc 100644 --- a/rust/vote-tx-v2/Cargo.toml +++ b/rust/vote-tx-v2/Cargo.toml @@ -7,7 +7,14 @@ homepage.workspace = true repository.workspace = true license.workspace = true +[lib] +crate-type = ["lib", "cdylib"] + [lints] workspace = true -[dependencies] \ No newline at end of file +[dependencies] +anyhow = "1.0.89" +ciborium = { version = "0.2.2", default-features = false } +cddl = { version = "0.9.4" } +coset = { version = "0.3.8" } \ No newline at end of file diff --git a/rust/vote-tx-v2/src/lib.rs b/rust/vote-tx-v2/src/lib.rs index fc119bf3445..4b1443195ff 100644 --- a/rust/vote-tx-v2/src/lib.rs +++ b/rust/vote-tx-v2/src/lib.rs @@ -1,7 +1,105 @@ //! A Catalyst vote transaction v1 object, structured following this //! [spec](https://input-output-hk.github.io/catalyst-libs/architecture/08_concepts/catalyst_voting/v2/) -/// A v2 vote transaction struct -#[derive(Debug, Clone, PartialEq, Eq)] +use anyhow::{anyhow, bail, ensure}; +use coset::CborSerializable; + +/// `encoded-cbor` tag number +const ENCODED_CBOR_TAG: u64 = 24; + +/// A generalised vote transaction struct. +#[derive(Debug, Clone, PartialEq)] +#[must_use] +pub struct GeneralisedTx { + /// `tx-body` field + tx_body: GeneralisedTxBody, +} + +/// A generalised vote transaction body struct. +#[derive(Debug, Clone, PartialEq)] #[must_use] -pub struct GeneralisedTx {} +pub struct GeneralisedTxBody { + /// `vote-type` field + vote_type: Vec, + /// `event` field + event: Vec<(ciborium::Value, ciborium::Value)>, + /// `votes` field + votes: Vec, + /// `voters-data` field + voters_data: Vec, +} + +/// A vote struct. +#[derive(Debug, Clone, PartialEq)] +pub struct Vote { + /// `choice` field + choice: ciborium::Value, + /// `proof` field + proof: ciborium::Value, + /// `prop-id` field + prop_id: ciborium::Value, +} + +impl Vote { + /// Encodes `Vote` to CBOR encoded bytes. + /// + /// # Errors + /// - Cannot encode `Vote` to CBOR + pub fn to_bytes(&self) -> anyhow::Result> { + let cbor_array = ciborium::Value::Array(vec![ + ciborium::Value::Tag(ENCODED_CBOR_TAG, self.choice.clone().into()), + ciborium::Value::Tag(ENCODED_CBOR_TAG, self.proof.clone().into()), + ciborium::Value::Tag(ENCODED_CBOR_TAG, self.prop_id.clone().into()), + ]); + + cbor_array + .to_vec() + .map_err(|_| anyhow!("Cannot encode `Vote` to CBOR")) + } + + /// Decodes `Vote` from the CBOR encoded bytes. + /// + /// # Errors + /// - Invalid `Vote` CBOR encoded bytes. + pub fn from_bytes(bytes: &[u8]) -> anyhow::Result { + let val = ciborium::Value::from_slice(bytes) + .map_err(|_| anyhow!("Invalid `Vote` CBOR encoded bytes, not a CBOR encoded."))?; + let array = val + .into_array() + .map_err(|_| anyhow!("Invalid `Vote` CBOR encoded bytes, must be an array."))?; + + ensure!( + array.len() == 3, + "Invalid `Vote` CBOR encoded bytes, must be an array of length 3.", + ); + + let mut iter = array.into_iter(); + + let ciborium::Value::Tag(ENCODED_CBOR_TAG, choice) = iter.next().ok_or(anyhow!( + "Invalid `Vote` CBOR encoded bytes, missing `choice` field." + ))? + else { + bail!("Invalid `Vote` CBOR encoded bytes, must be a major type 6 with tag {ENCODED_CBOR_TAG}."); + }; + + let ciborium::Value::Tag(ENCODED_CBOR_TAG, proof) = iter.next().ok_or(anyhow!( + "Invalid `Vote` CBOR encoded bytes, missing `proof` field." + ))? + else { + bail!("Invalid `Vote` CBOR encoded bytes, must be a major type 6 with tag {ENCODED_CBOR_TAG}."); + }; + + let ciborium::Value::Tag(ENCODED_CBOR_TAG, prop_id) = iter.next().ok_or(anyhow!( + "Invalid `Vote` CBOR encoded bytes, missing `prod-id` field." + ))? + else { + bail!("Invalid `Vote` CBOR encoded bytes, must be a major type 6 with tag {ENCODED_CBOR_TAG}."); + }; + + Ok(Self { + choice: *choice, + proof: *proof, + prop_id: *prop_id, + }) + } +} From c1c1fc5f6f0f634d472bf8f425b5a54f3402feb7 Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Fri, 8 Nov 2024 10:09:14 +0200 Subject: [PATCH 04/18] wip --- rust/vote-tx-v2/src/lib.rs | 65 +++++++++++++++++++++++++------------- 1 file changed, 43 insertions(+), 22 deletions(-) diff --git a/rust/vote-tx-v2/src/lib.rs b/rust/vote-tx-v2/src/lib.rs index 4b1443195ff..edcd20d9ea0 100644 --- a/rust/vote-tx-v2/src/lib.rs +++ b/rust/vote-tx-v2/src/lib.rs @@ -33,11 +33,11 @@ pub struct GeneralisedTxBody { #[derive(Debug, Clone, PartialEq)] pub struct Vote { /// `choice` field - choice: ciborium::Value, + choice: Vec, /// `proof` field - proof: ciborium::Value, + proof: Vec, /// `prop-id` field - prop_id: ciborium::Value, + prop_id: Vec, } impl Vote { @@ -47,9 +47,18 @@ impl Vote { /// - Cannot encode `Vote` to CBOR pub fn to_bytes(&self) -> anyhow::Result> { let cbor_array = ciborium::Value::Array(vec![ - ciborium::Value::Tag(ENCODED_CBOR_TAG, self.choice.clone().into()), - ciborium::Value::Tag(ENCODED_CBOR_TAG, self.proof.clone().into()), - ciborium::Value::Tag(ENCODED_CBOR_TAG, self.prop_id.clone().into()), + ciborium::Value::Tag( + ENCODED_CBOR_TAG, + ciborium::Value::Bytes(self.choice.clone()).into(), + ), + ciborium::Value::Tag( + ENCODED_CBOR_TAG, + ciborium::Value::Bytes(self.proof.clone()).into(), + ), + ciborium::Value::Tag( + ENCODED_CBOR_TAG, + ciborium::Value::Bytes(self.prop_id.clone()).into(), + ), ]); cbor_array @@ -62,44 +71,56 @@ impl Vote { /// # Errors /// - Invalid `Vote` CBOR encoded bytes. pub fn from_bytes(bytes: &[u8]) -> anyhow::Result { + /// Invalid `Vote` CBOR encoded bytes error msg. + const INVALID_CBOR_BYTES_ERR: &str = "Invalid `Vote` CBOR encoded bytes"; + let val = ciborium::Value::from_slice(bytes) - .map_err(|_| anyhow!("Invalid `Vote` CBOR encoded bytes, not a CBOR encoded."))?; + .map_err(|_| anyhow!("{INVALID_CBOR_BYTES_ERR}, not a CBOR encoded."))?; let array = val .into_array() - .map_err(|_| anyhow!("Invalid `Vote` CBOR encoded bytes, must be an array."))?; + .map_err(|_| anyhow!("{INVALID_CBOR_BYTES_ERR}, must be array type."))?; ensure!( array.len() == 3, - "Invalid `Vote` CBOR encoded bytes, must be an array of length 3.", + "{INVALID_CBOR_BYTES_ERR}, must be an array of length 3.", ); let mut iter = array.into_iter(); - let ciborium::Value::Tag(ENCODED_CBOR_TAG, choice) = iter.next().ok_or(anyhow!( - "Invalid `Vote` CBOR encoded bytes, missing `choice` field." - ))? + let ciborium::Value::Tag(ENCODED_CBOR_TAG, choice) = iter + .next() + .ok_or(anyhow!("{INVALID_CBOR_BYTES_ERR}, missing `choice` field."))? else { - bail!("Invalid `Vote` CBOR encoded bytes, must be a major type 6 with tag {ENCODED_CBOR_TAG}."); + bail!("{INVALID_CBOR_BYTES_ERR}, must be a major type 6 with tag {ENCODED_CBOR_TAG}."); }; + let choice = choice + .into_bytes() + .map_err(|_| anyhow!("{INVALID_CBOR_BYTES_ERR}, `choice` must be bytes type."))?; - let ciborium::Value::Tag(ENCODED_CBOR_TAG, proof) = iter.next().ok_or(anyhow!( - "Invalid `Vote` CBOR encoded bytes, missing `proof` field." - ))? + let ciborium::Value::Tag(ENCODED_CBOR_TAG, proof) = iter + .next() + .ok_or(anyhow!("{INVALID_CBOR_BYTES_ERR}, missing `proof` field."))? else { - bail!("Invalid `Vote` CBOR encoded bytes, must be a major type 6 with tag {ENCODED_CBOR_TAG}."); + bail!("{INVALID_CBOR_BYTES_ERR}, must be a major type 6 with tag {ENCODED_CBOR_TAG}."); }; + let proof = proof + .into_bytes() + .map_err(|_| anyhow!("{INVALID_CBOR_BYTES_ERR}, `proof` must be bytes type."))?; let ciborium::Value::Tag(ENCODED_CBOR_TAG, prop_id) = iter.next().ok_or(anyhow!( - "Invalid `Vote` CBOR encoded bytes, missing `prod-id` field." + "{INVALID_CBOR_BYTES_ERR}, missing `prod-id` field." ))? else { - bail!("Invalid `Vote` CBOR encoded bytes, must be a major type 6 with tag {ENCODED_CBOR_TAG}."); + bail!("{INVALID_CBOR_BYTES_ERR}, must be a major type 6 with tag {ENCODED_CBOR_TAG}."); }; + let prop_id = prop_id + .into_bytes() + .map_err(|_| anyhow!("{INVALID_CBOR_BYTES_ERR}, `prop-id` must be bytes type."))?; Ok(Self { - choice: *choice, - proof: *proof, - prop_id: *prop_id, + choice, + proof, + prop_id, }) } } From 4c8a2cd9d0503b02c30b0307784fdfcb28251171 Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Fri, 8 Nov 2024 10:23:50 +0200 Subject: [PATCH 05/18] wip --- rust/vote-tx-v2/src/decoding.rs | 128 ++++++++++++++++++++++++++++++++ rust/vote-tx-v2/src/lib.rs | 95 +----------------------- 2 files changed, 131 insertions(+), 92 deletions(-) create mode 100644 rust/vote-tx-v2/src/decoding.rs diff --git a/rust/vote-tx-v2/src/decoding.rs b/rust/vote-tx-v2/src/decoding.rs new file mode 100644 index 00000000000..fa0a51af09d --- /dev/null +++ b/rust/vote-tx-v2/src/decoding.rs @@ -0,0 +1,128 @@ +//! CBOR encoding and decoding implementation. + +use anyhow::{anyhow, bail, ensure}; +use coset::CborSerializable; + +use crate::Vote; + +/// `encoded-cbor` tag number +const ENCODED_CBOR_TAG: u64 = 24; + +impl Vote { + /// Encodes `Vote` to CBOR encoded bytes. + /// + /// # Errors + /// - Cannot encode `Vote` to CBOR + pub fn to_bytes(&self) -> anyhow::Result> { + let cbor_array = ciborium::Value::Array(vec![ + ciborium::Value::Tag( + ENCODED_CBOR_TAG, + ciborium::Value::Array( + self.choices + .clone() + .into_iter() + .map(ciborium::Value::Bytes) + .collect(), + ) + .into(), + ), + ciborium::Value::Tag( + ENCODED_CBOR_TAG, + ciborium::Value::Bytes(self.proof.clone()).into(), + ), + ciborium::Value::Tag( + ENCODED_CBOR_TAG, + ciborium::Value::Bytes(self.prop_id.clone()).into(), + ), + ]); + + cbor_array + .to_vec() + .map_err(|_| anyhow!("Cannot encode `Vote` to CBOR")) + } + + /// Decodes `Vote` from the CBOR encoded bytes. + /// + /// # Errors + /// - Invalid `Vote` CBOR encoded bytes. + pub fn from_bytes(bytes: &[u8]) -> anyhow::Result { + /// Invalid `Vote` CBOR encoded bytes error msg. + const INVALID_CBOR_BYTES_ERR: &str = "Invalid `Vote` CBOR encoded bytes"; + + let val = ciborium::Value::from_slice(bytes) + .map_err(|_| anyhow!("{INVALID_CBOR_BYTES_ERR}, not a CBOR encoded."))?; + let array = val + .into_array() + .map_err(|_| anyhow!("{INVALID_CBOR_BYTES_ERR}, must be array type."))?; + + ensure!( + array.len() == 3, + "{INVALID_CBOR_BYTES_ERR}, must be an array of length 3.", + ); + + let mut iter = array.into_iter(); + + let choices = { + let ciborium::Value::Tag(ENCODED_CBOR_TAG, choices) = iter.next().ok_or(anyhow!( + "{INVALID_CBOR_BYTES_ERR}, missing `choices` field." + ))? + else { + bail!( + "{INVALID_CBOR_BYTES_ERR}, must be a major type 6 with tag {ENCODED_CBOR_TAG}." + ); + }; + let choices = choices + .into_array() + .map_err(|_| anyhow!("{INVALID_CBOR_BYTES_ERR}, `choices` must be array type."))?; + + ensure!( + !choices.is_empty(), + "{INVALID_CBOR_BYTES_ERR}, `choices` must have at least one element.", + ); + + choices + .into_iter() + .enumerate() + .map(|(i, choice)| { + choice + .into_bytes() + .map_err(|_| anyhow!("`choice` {i} must be bytes type.")) + }) + .collect::>()? + }; + + let proof = { + let ciborium::Value::Tag(ENCODED_CBOR_TAG, proof) = iter + .next() + .ok_or(anyhow!("{INVALID_CBOR_BYTES_ERR}, missing `proof` field."))? + else { + bail!( + "{INVALID_CBOR_BYTES_ERR}, must be a major type 6 with tag {ENCODED_CBOR_TAG}." + ); + }; + proof + .into_bytes() + .map_err(|_| anyhow!("{INVALID_CBOR_BYTES_ERR}, `proof` must be bytes type."))? + }; + + let prop_id = { + let ciborium::Value::Tag(ENCODED_CBOR_TAG, prop_id) = iter.next().ok_or(anyhow!( + "{INVALID_CBOR_BYTES_ERR}, missing `prod-id` field." + ))? + else { + bail!( + "{INVALID_CBOR_BYTES_ERR}, must be a major type 6 with tag {ENCODED_CBOR_TAG}." + ); + }; + prop_id + .into_bytes() + .map_err(|_| anyhow!("{INVALID_CBOR_BYTES_ERR}, `prop-id` must be bytes type."))? + }; + + Ok(Self { + choices, + proof, + prop_id, + }) + } +} diff --git a/rust/vote-tx-v2/src/lib.rs b/rust/vote-tx-v2/src/lib.rs index edcd20d9ea0..e7370433988 100644 --- a/rust/vote-tx-v2/src/lib.rs +++ b/rust/vote-tx-v2/src/lib.rs @@ -1,11 +1,7 @@ //! A Catalyst vote transaction v1 object, structured following this //! [spec](https://input-output-hk.github.io/catalyst-libs/architecture/08_concepts/catalyst_voting/v2/) -use anyhow::{anyhow, bail, ensure}; -use coset::CborSerializable; - -/// `encoded-cbor` tag number -const ENCODED_CBOR_TAG: u64 = 24; +mod decoding; /// A generalised vote transaction struct. #[derive(Debug, Clone, PartialEq)] @@ -32,95 +28,10 @@ pub struct GeneralisedTxBody { /// A vote struct. #[derive(Debug, Clone, PartialEq)] pub struct Vote { - /// `choice` field - choice: Vec, + /// `choices` field + choices: Vec>, /// `proof` field proof: Vec, /// `prop-id` field prop_id: Vec, } - -impl Vote { - /// Encodes `Vote` to CBOR encoded bytes. - /// - /// # Errors - /// - Cannot encode `Vote` to CBOR - pub fn to_bytes(&self) -> anyhow::Result> { - let cbor_array = ciborium::Value::Array(vec![ - ciborium::Value::Tag( - ENCODED_CBOR_TAG, - ciborium::Value::Bytes(self.choice.clone()).into(), - ), - ciborium::Value::Tag( - ENCODED_CBOR_TAG, - ciborium::Value::Bytes(self.proof.clone()).into(), - ), - ciborium::Value::Tag( - ENCODED_CBOR_TAG, - ciborium::Value::Bytes(self.prop_id.clone()).into(), - ), - ]); - - cbor_array - .to_vec() - .map_err(|_| anyhow!("Cannot encode `Vote` to CBOR")) - } - - /// Decodes `Vote` from the CBOR encoded bytes. - /// - /// # Errors - /// - Invalid `Vote` CBOR encoded bytes. - pub fn from_bytes(bytes: &[u8]) -> anyhow::Result { - /// Invalid `Vote` CBOR encoded bytes error msg. - const INVALID_CBOR_BYTES_ERR: &str = "Invalid `Vote` CBOR encoded bytes"; - - let val = ciborium::Value::from_slice(bytes) - .map_err(|_| anyhow!("{INVALID_CBOR_BYTES_ERR}, not a CBOR encoded."))?; - let array = val - .into_array() - .map_err(|_| anyhow!("{INVALID_CBOR_BYTES_ERR}, must be array type."))?; - - ensure!( - array.len() == 3, - "{INVALID_CBOR_BYTES_ERR}, must be an array of length 3.", - ); - - let mut iter = array.into_iter(); - - let ciborium::Value::Tag(ENCODED_CBOR_TAG, choice) = iter - .next() - .ok_or(anyhow!("{INVALID_CBOR_BYTES_ERR}, missing `choice` field."))? - else { - bail!("{INVALID_CBOR_BYTES_ERR}, must be a major type 6 with tag {ENCODED_CBOR_TAG}."); - }; - let choice = choice - .into_bytes() - .map_err(|_| anyhow!("{INVALID_CBOR_BYTES_ERR}, `choice` must be bytes type."))?; - - let ciborium::Value::Tag(ENCODED_CBOR_TAG, proof) = iter - .next() - .ok_or(anyhow!("{INVALID_CBOR_BYTES_ERR}, missing `proof` field."))? - else { - bail!("{INVALID_CBOR_BYTES_ERR}, must be a major type 6 with tag {ENCODED_CBOR_TAG}."); - }; - let proof = proof - .into_bytes() - .map_err(|_| anyhow!("{INVALID_CBOR_BYTES_ERR}, `proof` must be bytes type."))?; - - let ciborium::Value::Tag(ENCODED_CBOR_TAG, prop_id) = iter.next().ok_or(anyhow!( - "{INVALID_CBOR_BYTES_ERR}, missing `prod-id` field." - ))? - else { - bail!("{INVALID_CBOR_BYTES_ERR}, must be a major type 6 with tag {ENCODED_CBOR_TAG}."); - }; - let prop_id = prop_id - .into_bytes() - .map_err(|_| anyhow!("{INVALID_CBOR_BYTES_ERR}, `prop-id` must be bytes type."))?; - - Ok(Self { - choice, - proof, - prop_id, - }) - } -} From 731290ffba4b8719eab66e77d5ab4e08795b0d49 Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Fri, 8 Nov 2024 10:45:41 +0200 Subject: [PATCH 06/18] wip --- rust/vote-tx-v2/src/decoding.rs | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/rust/vote-tx-v2/src/decoding.rs b/rust/vote-tx-v2/src/decoding.rs index fa0a51af09d..796a640a130 100644 --- a/rust/vote-tx-v2/src/decoding.rs +++ b/rust/vote-tx-v2/src/decoding.rs @@ -1,7 +1,6 @@ //! CBOR encoding and decoding implementation. use anyhow::{anyhow, bail, ensure}; -use coset::CborSerializable; use crate::Vote; @@ -13,7 +12,7 @@ impl Vote { /// /// # Errors /// - Cannot encode `Vote` to CBOR - pub fn to_bytes(&self) -> anyhow::Result> { + pub fn write_to_bytes(&self, buf: &mut Vec) -> anyhow::Result<()> { let cbor_array = ciborium::Value::Array(vec![ ciborium::Value::Tag( ENCODED_CBOR_TAG, @@ -35,12 +34,20 @@ impl Vote { ciborium::Value::Bytes(self.prop_id.clone()).into(), ), ]); - - cbor_array - .to_vec() + ciborium::ser::into_writer(&cbor_array, buf) .map_err(|_| anyhow!("Cannot encode `Vote` to CBOR")) } + /// Encodes `Vote` to CBOR encoded bytes. + /// + /// # Errors + /// - Cannot encode `Vote` to CBOR + pub fn to_bytes(&self) -> anyhow::Result> { + let mut bytes = Vec::new(); + self.write_to_bytes(&mut bytes)?; + Ok(bytes) + } + /// Decodes `Vote` from the CBOR encoded bytes. /// /// # Errors @@ -49,8 +56,9 @@ impl Vote { /// Invalid `Vote` CBOR encoded bytes error msg. const INVALID_CBOR_BYTES_ERR: &str = "Invalid `Vote` CBOR encoded bytes"; - let val = ciborium::Value::from_slice(bytes) + let val: ciborium::Value = ciborium::de::from_reader(bytes) .map_err(|_| anyhow!("{INVALID_CBOR_BYTES_ERR}, not a CBOR encoded."))?; + let array = val .into_array() .map_err(|_| anyhow!("{INVALID_CBOR_BYTES_ERR}, must be array type."))?; From df0266f90923466cf3c914d0357799685bac96db Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Fri, 8 Nov 2024 13:15:07 +0200 Subject: [PATCH 07/18] wip --- rust/vote-tx-v2/src/decoding.rs | 106 ++++++++++++++++++++++---------- 1 file changed, 75 insertions(+), 31 deletions(-) diff --git a/rust/vote-tx-v2/src/decoding.rs b/rust/vote-tx-v2/src/decoding.rs index 796a640a130..6005c048673 100644 --- a/rust/vote-tx-v2/src/decoding.rs +++ b/rust/vote-tx-v2/src/decoding.rs @@ -1,12 +1,59 @@ //! CBOR encoding and decoding implementation. +use std::fmt::Display; + use anyhow::{anyhow, bail, ensure}; -use crate::Vote; +use crate::{GeneralisedTxBody, Vote}; /// `encoded-cbor` tag number const ENCODED_CBOR_TAG: u64 = 24; +/// CBOR decoding error. +fn cbor_decoding_err(reason: impl Display) -> anyhow::Error { + anyhow!("Cannot decode `{}`, {reason}.", std::any::type_name::()) +} + +/// CBOR encoding error. +fn cbor_encoding_err(reason: impl Display) -> anyhow::Error { + anyhow!("Cannot encode `{}`, {reason}.", std::any::type_name::()) +} + +impl GeneralisedTxBody { + /// Encodes `Vote` to CBOR encoded bytes. + /// + /// # Errors + /// - Cannot encode `Vote` to CBOR + pub fn write_to_bytes(&self, buf: &mut Vec) -> anyhow::Result<()> { + let cbor_array = ciborium::Value::Array(vec![]); + ciborium::ser::into_writer(&cbor_array, buf) + .map_err(|_| cbor_encoding_err::("interal `ciborioum` error")) + } + + /// Encodes `Vote` to CBOR encoded bytes. + /// + /// # Errors + /// - Cannot encode `Vote` to CBOR + pub fn to_bytes(&self) -> anyhow::Result> { + let mut bytes = Vec::new(); + self.write_to_bytes(&mut bytes)?; + Ok(bytes) + } + + // /// Decodes `Vote` from the CBOR encoded bytes. + // /// + // /// # Errors + // /// - Invalid `Vote` CBOR encoded bytes. + // pub fn from_bytes(bytes: &[u8]) -> anyhow::Result { + // /// Invalid `Vote` CBOR encoded bytes error msg. + // const INVALID_CBOR_BYTES_ERR: &str = "Invalid `GeneralisedTxBody` CBOR encoded + // bytes"; + + // let val: ciborium::Value = ciborium::de::from_reader(bytes) + // .map_err(|_| anyhow!("{INVALID_CBOR_BYTES_ERR}, not a CBOR encoded."))?; + // } +} + impl Vote { /// Encodes `Vote` to CBOR encoded bytes. /// @@ -35,7 +82,7 @@ impl Vote { ), ]); ciborium::ser::into_writer(&cbor_array, buf) - .map_err(|_| anyhow!("Cannot encode `Vote` to CBOR")) + .map_err(|_| cbor_encoding_err::("interal `ciborioum` error")) } /// Encodes `Vote` to CBOR encoded bytes. @@ -53,48 +100,45 @@ impl Vote { /// # Errors /// - Invalid `Vote` CBOR encoded bytes. pub fn from_bytes(bytes: &[u8]) -> anyhow::Result { - /// Invalid `Vote` CBOR encoded bytes error msg. - const INVALID_CBOR_BYTES_ERR: &str = "Invalid `Vote` CBOR encoded bytes"; - let val: ciborium::Value = ciborium::de::from_reader(bytes) - .map_err(|_| anyhow!("{INVALID_CBOR_BYTES_ERR}, not a CBOR encoded."))?; + .map_err(|_| cbor_decoding_err::("not a CBOR encoded"))?; let array = val .into_array() - .map_err(|_| anyhow!("{INVALID_CBOR_BYTES_ERR}, must be array type."))?; + .map_err(|_| cbor_decoding_err::("must be array type"))?; ensure!( array.len() == 3, - "{INVALID_CBOR_BYTES_ERR}, must be an array of length 3.", + cbor_decoding_err::("must be an array of length 3") ); let mut iter = array.into_iter(); let choices = { - let ciborium::Value::Tag(ENCODED_CBOR_TAG, choices) = iter.next().ok_or(anyhow!( - "{INVALID_CBOR_BYTES_ERR}, missing `choices` field." - ))? + let ciborium::Value::Tag(ENCODED_CBOR_TAG, choices) = iter + .next() + .ok_or(cbor_decoding_err::("missing `choices` field"))? else { - bail!( - "{INVALID_CBOR_BYTES_ERR}, must be a major type 6 with tag {ENCODED_CBOR_TAG}." - ); + bail!(cbor_decoding_err::(format!( + "must be a major type 6 with tag {ENCODED_CBOR_TAG}" + ))); }; let choices = choices .into_array() - .map_err(|_| anyhow!("{INVALID_CBOR_BYTES_ERR}, `choices` must be array type."))?; + .map_err(|_| cbor_decoding_err::("`choices` must be array type"))?; ensure!( !choices.is_empty(), - "{INVALID_CBOR_BYTES_ERR}, `choices` must have at least one element.", + cbor_decoding_err::("`choices` must have at least one element") ); choices .into_iter() .enumerate() .map(|(i, choice)| { - choice - .into_bytes() - .map_err(|_| anyhow!("`choice` {i} must be bytes type.")) + choice.into_bytes().map_err(|_| { + cbor_decoding_err::(format!("`choice` {i} must be bytes type")) + }) }) .collect::>()? }; @@ -102,29 +146,29 @@ impl Vote { let proof = { let ciborium::Value::Tag(ENCODED_CBOR_TAG, proof) = iter .next() - .ok_or(anyhow!("{INVALID_CBOR_BYTES_ERR}, missing `proof` field."))? + .ok_or(cbor_decoding_err::("missing `proof` field"))? else { - bail!( - "{INVALID_CBOR_BYTES_ERR}, must be a major type 6 with tag {ENCODED_CBOR_TAG}." - ); + bail!(cbor_decoding_err::(format!( + "must be a major type 6 with tag {ENCODED_CBOR_TAG}" + ))); }; proof .into_bytes() - .map_err(|_| anyhow!("{INVALID_CBOR_BYTES_ERR}, `proof` must be bytes type."))? + .map_err(|_| cbor_decoding_err::("`proof` must be bytes type"))? }; let prop_id = { - let ciborium::Value::Tag(ENCODED_CBOR_TAG, prop_id) = iter.next().ok_or(anyhow!( - "{INVALID_CBOR_BYTES_ERR}, missing `prod-id` field." - ))? + let ciborium::Value::Tag(ENCODED_CBOR_TAG, prop_id) = iter + .next() + .ok_or(cbor_decoding_err::("missing `prod-id` field"))? else { - bail!( - "{INVALID_CBOR_BYTES_ERR}, must be a major type 6 with tag {ENCODED_CBOR_TAG}." - ); + bail!(cbor_decoding_err::(format!( + "must be a major type 6 with tag {ENCODED_CBOR_TAG}" + ))); }; prop_id .into_bytes() - .map_err(|_| anyhow!("{INVALID_CBOR_BYTES_ERR}, `prop-id` must be bytes type."))? + .map_err(|_| cbor_decoding_err::("`prop-id` must be bytes type"))? }; Ok(Self { From 08bd6ab25428f51e177abdd8a22902bdda7210d4 Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Fri, 8 Nov 2024 20:05:22 +0200 Subject: [PATCH 08/18] add proptest for Vote type --- rust/vote-tx-v2/Cargo.toml | 7 +++- rust/vote-tx-v2/src/decoding.rs | 57 +++++++++++---------------------- rust/vote-tx-v2/src/lib.rs | 53 +++++++++++++++++------------- 3 files changed, 55 insertions(+), 62 deletions(-) diff --git a/rust/vote-tx-v2/Cargo.toml b/rust/vote-tx-v2/Cargo.toml index ab51412cdfc..4fa63798993 100644 --- a/rust/vote-tx-v2/Cargo.toml +++ b/rust/vote-tx-v2/Cargo.toml @@ -17,4 +17,9 @@ workspace = true anyhow = "1.0.89" ciborium = { version = "0.2.2", default-features = false } cddl = { version = "0.9.4" } -coset = { version = "0.3.8" } \ No newline at end of file +proptest = { version = "1.5.0" } + +[dev-dependencies] +# Potentially it could be replaced with using `proptest::property_test` attribute macro, +# after this PR will be merged https://github.com/proptest-rs/proptest/pull/523 +test-strategy = "0.4.0" \ No newline at end of file diff --git a/rust/vote-tx-v2/src/decoding.rs b/rust/vote-tx-v2/src/decoding.rs index 6005c048673..3c24c5c79f7 100644 --- a/rust/vote-tx-v2/src/decoding.rs +++ b/rust/vote-tx-v2/src/decoding.rs @@ -4,7 +4,7 @@ use std::fmt::Display; use anyhow::{anyhow, bail, ensure}; -use crate::{GeneralisedTxBody, Vote}; +use crate::Vote; /// `encoded-cbor` tag number const ENCODED_CBOR_TAG: u64 = 24; @@ -19,46 +19,11 @@ fn cbor_encoding_err(reason: impl Display) -> anyhow::Error { anyhow!("Cannot encode `{}`, {reason}.", std::any::type_name::()) } -impl GeneralisedTxBody { - /// Encodes `Vote` to CBOR encoded bytes. - /// - /// # Errors - /// - Cannot encode `Vote` to CBOR - pub fn write_to_bytes(&self, buf: &mut Vec) -> anyhow::Result<()> { - let cbor_array = ciborium::Value::Array(vec![]); - ciborium::ser::into_writer(&cbor_array, buf) - .map_err(|_| cbor_encoding_err::("interal `ciborioum` error")) - } - - /// Encodes `Vote` to CBOR encoded bytes. - /// - /// # Errors - /// - Cannot encode `Vote` to CBOR - pub fn to_bytes(&self) -> anyhow::Result> { - let mut bytes = Vec::new(); - self.write_to_bytes(&mut bytes)?; - Ok(bytes) - } - - // /// Decodes `Vote` from the CBOR encoded bytes. - // /// - // /// # Errors - // /// - Invalid `Vote` CBOR encoded bytes. - // pub fn from_bytes(bytes: &[u8]) -> anyhow::Result { - // /// Invalid `Vote` CBOR encoded bytes error msg. - // const INVALID_CBOR_BYTES_ERR: &str = "Invalid `GeneralisedTxBody` CBOR encoded - // bytes"; - - // let val: ciborium::Value = ciborium::de::from_reader(bytes) - // .map_err(|_| anyhow!("{INVALID_CBOR_BYTES_ERR}, not a CBOR encoded."))?; - // } -} - impl Vote { /// Encodes `Vote` to CBOR encoded bytes. /// /// # Errors - /// - Cannot encode `Vote` to CBOR + /// - Cannot encode `Vote` pub fn write_to_bytes(&self, buf: &mut Vec) -> anyhow::Result<()> { let cbor_array = ciborium::Value::Array(vec![ ciborium::Value::Tag( @@ -88,7 +53,7 @@ impl Vote { /// Encodes `Vote` to CBOR encoded bytes. /// /// # Errors - /// - Cannot encode `Vote` to CBOR + /// - Cannot encode `Vote` pub fn to_bytes(&self) -> anyhow::Result> { let mut bytes = Vec::new(); self.write_to_bytes(&mut bytes)?; @@ -98,7 +63,7 @@ impl Vote { /// Decodes `Vote` from the CBOR encoded bytes. /// /// # Errors - /// - Invalid `Vote` CBOR encoded bytes. + /// - Cannot decode `GeneralisedTxBody` pub fn from_bytes(bytes: &[u8]) -> anyhow::Result { let val: ciborium::Value = ciborium::de::from_reader(bytes) .map_err(|_| cbor_decoding_err::("not a CBOR encoded"))?; @@ -178,3 +143,17 @@ impl Vote { }) } } + +#[cfg(test)] +mod tests { + use test_strategy::proptest; + + use super::Vote; + + #[proptest] + fn vote_from_bytes_to_bytes_test(vote: Vote) { + let bytes = vote.to_bytes().unwrap(); + let decoded = Vote::from_bytes(&bytes).unwrap(); + assert_eq!(vote, decoded); + } +} diff --git a/rust/vote-tx-v2/src/lib.rs b/rust/vote-tx-v2/src/lib.rs index e7370433988..c0b51480db7 100644 --- a/rust/vote-tx-v2/src/lib.rs +++ b/rust/vote-tx-v2/src/lib.rs @@ -3,28 +3,6 @@ mod decoding; -/// A generalised vote transaction struct. -#[derive(Debug, Clone, PartialEq)] -#[must_use] -pub struct GeneralisedTx { - /// `tx-body` field - tx_body: GeneralisedTxBody, -} - -/// A generalised vote transaction body struct. -#[derive(Debug, Clone, PartialEq)] -#[must_use] -pub struct GeneralisedTxBody { - /// `vote-type` field - vote_type: Vec, - /// `event` field - event: Vec<(ciborium::Value, ciborium::Value)>, - /// `votes` field - votes: Vec, - /// `voters-data` field - voters_data: Vec, -} - /// A vote struct. #[derive(Debug, Clone, PartialEq)] pub struct Vote { @@ -35,3 +13,34 @@ pub struct Vote { /// `prop-id` field prop_id: Vec, } + +#[allow(missing_docs, clippy::missing_docs_in_private_items)] +mod arbitrary_impl { + use proptest::{ + prelude::{any_with, Arbitrary, BoxedStrategy, Strategy}, + sample::size_range, + }; + + use super::Vote; + + impl Arbitrary for Vote { + type Parameters = (); + type Strategy = BoxedStrategy; + + fn arbitrary_with((): Self::Parameters) -> Self::Strategy { + any_with::<(Vec>, Vec, Vec)>(( + (size_range(1..10usize), Default::default()), + Default::default(), + Default::default(), + )) + .prop_map(|(choices, proof, prop_id)| { + Self { + choices, + proof, + prop_id, + } + }) + .boxed() + } + } +} From ac4a472c598264aefa7219b4db6fa709fa0eb983 Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Mon, 11 Nov 2024 13:36:05 +0200 Subject: [PATCH 09/18] replace ciborium usage with minicbor --- rust/vote-tx-v2/Cargo.toml | 5 +- rust/vote-tx-v2/src/decoding.rs | 239 ++++++++++++++++---------------- rust/vote-tx-v2/src/lib.rs | 28 ++-- 3 files changed, 141 insertions(+), 131 deletions(-) diff --git a/rust/vote-tx-v2/Cargo.toml b/rust/vote-tx-v2/Cargo.toml index 4fa63798993..badce38a19b 100644 --- a/rust/vote-tx-v2/Cargo.toml +++ b/rust/vote-tx-v2/Cargo.toml @@ -15,11 +15,10 @@ workspace = true [dependencies] anyhow = "1.0.89" -ciborium = { version = "0.2.2", default-features = false } -cddl = { version = "0.9.4" } proptest = { version = "1.5.0" } +minicbor = { version = "0.25.1", features = ["alloc"] } [dev-dependencies] # Potentially it could be replaced with using `proptest::property_test` attribute macro, # after this PR will be merged https://github.com/proptest-rs/proptest/pull/523 -test-strategy = "0.4.0" \ No newline at end of file +test-strategy = "0.4.0" diff --git a/rust/vote-tx-v2/src/decoding.rs b/rust/vote-tx-v2/src/decoding.rs index 3c24c5c79f7..ba5e49d9ac5 100644 --- a/rust/vote-tx-v2/src/decoding.rs +++ b/rust/vote-tx-v2/src/decoding.rs @@ -1,140 +1,47 @@ //! CBOR encoding and decoding implementation. -use std::fmt::Display; +use anyhow::anyhow; +use minicbor::{data::IanaTag, Decode, Decoder, Encode, Encoder}; -use anyhow::{anyhow, bail, ensure}; - -use crate::Vote; - -/// `encoded-cbor` tag number -const ENCODED_CBOR_TAG: u64 = 24; - -/// CBOR decoding error. -fn cbor_decoding_err(reason: impl Display) -> anyhow::Error { - anyhow!("Cannot decode `{}`, {reason}.", std::any::type_name::()) -} - -/// CBOR encoding error. -fn cbor_encoding_err(reason: impl Display) -> anyhow::Error { - anyhow!("Cannot encode `{}`, {reason}.", std::any::type_name::()) -} +use crate::{Choice, Proof, PropId, Vote}; impl Vote { - /// Encodes `Vote` to CBOR encoded bytes. - /// - /// # Errors - /// - Cannot encode `Vote` - pub fn write_to_bytes(&self, buf: &mut Vec) -> anyhow::Result<()> { - let cbor_array = ciborium::Value::Array(vec![ - ciborium::Value::Tag( - ENCODED_CBOR_TAG, - ciborium::Value::Array( - self.choices - .clone() - .into_iter() - .map(ciborium::Value::Bytes) - .collect(), - ) - .into(), - ), - ciborium::Value::Tag( - ENCODED_CBOR_TAG, - ciborium::Value::Bytes(self.proof.clone()).into(), - ), - ciborium::Value::Tag( - ENCODED_CBOR_TAG, - ciborium::Value::Bytes(self.prop_id.clone()).into(), - ), - ]); - ciborium::ser::into_writer(&cbor_array, buf) - .map_err(|_| cbor_encoding_err::("interal `ciborioum` error")) - } - /// Encodes `Vote` to CBOR encoded bytes. /// /// # Errors /// - Cannot encode `Vote` pub fn to_bytes(&self) -> anyhow::Result> { let mut bytes = Vec::new(); - self.write_to_bytes(&mut bytes)?; + let mut e = Encoder::new(&mut bytes); + self.encode(&mut e, &mut ()) + .map_err(|e| anyhow!("Cannot encode `{}`, {e}.", std::any::type_name::()))?; Ok(bytes) } /// Decodes `Vote` from the CBOR encoded bytes. /// /// # Errors - /// - Cannot decode `GeneralisedTxBody` + /// - Cannot decode `Vote` pub fn from_bytes(bytes: &[u8]) -> anyhow::Result { - let val: ciborium::Value = ciborium::de::from_reader(bytes) - .map_err(|_| cbor_decoding_err::("not a CBOR encoded"))?; - - let array = val - .into_array() - .map_err(|_| cbor_decoding_err::("must be array type"))?; - - ensure!( - array.len() == 3, - cbor_decoding_err::("must be an array of length 3") - ); - - let mut iter = array.into_iter(); - - let choices = { - let ciborium::Value::Tag(ENCODED_CBOR_TAG, choices) = iter - .next() - .ok_or(cbor_decoding_err::("missing `choices` field"))? - else { - bail!(cbor_decoding_err::(format!( - "must be a major type 6 with tag {ENCODED_CBOR_TAG}" - ))); - }; - let choices = choices - .into_array() - .map_err(|_| cbor_decoding_err::("`choices` must be array type"))?; - - ensure!( - !choices.is_empty(), - cbor_decoding_err::("`choices` must have at least one element") - ); - - choices - .into_iter() - .enumerate() - .map(|(i, choice)| { - choice.into_bytes().map_err(|_| { - cbor_decoding_err::(format!("`choice` {i} must be bytes type")) - }) - }) - .collect::>()? - }; - - let proof = { - let ciborium::Value::Tag(ENCODED_CBOR_TAG, proof) = iter - .next() - .ok_or(cbor_decoding_err::("missing `proof` field"))? - else { - bail!(cbor_decoding_err::(format!( - "must be a major type 6 with tag {ENCODED_CBOR_TAG}" - ))); - }; - proof - .into_bytes() - .map_err(|_| cbor_decoding_err::("`proof` must be bytes type"))? - }; - - let prop_id = { - let ciborium::Value::Tag(ENCODED_CBOR_TAG, prop_id) = iter - .next() - .ok_or(cbor_decoding_err::("missing `prod-id` field"))? - else { - bail!(cbor_decoding_err::(format!( - "must be a major type 6 with tag {ENCODED_CBOR_TAG}" - ))); - }; - prop_id - .into_bytes() - .map_err(|_| cbor_decoding_err::("`prop-id` must be bytes type"))? - }; + let mut decoder = Decoder::new(bytes); + let res = Vote::decode(&mut decoder, &mut ()) + .map_err(|e| anyhow!("Cannot decode `{}`, {e}.", std::any::type_name::()))?; + Ok(res) + } +} + +impl Decode<'_, ()> for Vote { + fn decode(d: &mut Decoder<'_>, (): &mut ()) -> Result { + d.array()?; + + let choices = Vec::::decode(d, &mut ())?; + if choices.is_empty() { + return Err(minicbor::decode::Error::message( + "choices array must has at least one entry", + )); + } + let proof = Proof::decode(d, &mut ())?; + let prop_id = PropId::decode(d, &mut ())?; Ok(Self { choices, @@ -144,13 +51,105 @@ impl Vote { } } +impl Encode<()> for Vote { + fn encode( + &self, e: &mut minicbor::Encoder, (): &mut (), + ) -> Result<(), minicbor::encode::Error> { + e.array(3)?; + + self.choices.encode(e, &mut ())?; + self.proof.encode(e, &mut ())?; + self.prop_id.encode(e, &mut ())?; + Ok(()) + } +} + +impl Decode<'_, ()> for Choice { + fn decode(d: &mut Decoder<'_>, (): &mut ()) -> Result { + let tag = d.tag()?; + let expected_tag = minicbor::data::IanaTag::Cbor.tag(); + if expected_tag != tag { + return Err(minicbor::decode::Error::message(format!( + "tag value must be: {}, provided: {}", + expected_tag.as_u64(), + tag.as_u64(), + ))); + } + let choice = d.bytes()?.to_vec(); + Ok(Choice(choice)) + } +} + +impl Encode<()> for Choice { + fn encode( + &self, e: &mut minicbor::Encoder, (): &mut (), + ) -> Result<(), minicbor::encode::Error> { + e.tag(IanaTag::Cbor.tag())?; + e.bytes(&self.0)?; + Ok(()) + } +} + +impl Decode<'_, ()> for Proof { + fn decode(d: &mut Decoder<'_>, (): &mut ()) -> Result { + let tag = d.tag()?; + let expected_tag = minicbor::data::IanaTag::Cbor.tag(); + if expected_tag != tag { + return Err(minicbor::decode::Error::message(format!( + "tag value must be: {}, provided: {}", + expected_tag.as_u64(), + tag.as_u64(), + ))); + } + let choice = d.bytes()?.to_vec(); + Ok(Proof(choice)) + } +} + +impl Encode<()> for Proof { + fn encode( + &self, e: &mut minicbor::Encoder, (): &mut (), + ) -> Result<(), minicbor::encode::Error> { + e.tag(IanaTag::Cbor.tag())?; + e.bytes(&self.0)?; + Ok(()) + } +} + +impl Decode<'_, ()> for PropId { + fn decode(d: &mut Decoder<'_>, (): &mut ()) -> Result { + let tag = d.tag()?; + let expected_tag = IanaTag::Cbor.tag(); + if expected_tag != tag { + return Err(minicbor::decode::Error::message(format!( + "tag value must be: {}, provided: {}", + expected_tag.as_u64(), + tag.as_u64(), + ))); + } + let choice = d.bytes()?.to_vec(); + Ok(PropId(choice)) + } +} + +impl Encode<()> for PropId { + fn encode( + &self, e: &mut minicbor::Encoder, (): &mut (), + ) -> Result<(), minicbor::encode::Error> { + e.tag(IanaTag::Cbor.tag())?; + e.bytes(&self.0)?; + Ok(()) + } +} + #[cfg(test)] mod tests { + use proptest::prelude::ProptestConfig; use test_strategy::proptest; use super::Vote; - #[proptest] + #[proptest(ProptestConfig::with_cases(0))] fn vote_from_bytes_to_bytes_test(vote: Vote) { let bytes = vote.to_bytes().unwrap(); let decoded = Vote::from_bytes(&bytes).unwrap(); diff --git a/rust/vote-tx-v2/src/lib.rs b/rust/vote-tx-v2/src/lib.rs index c0b51480db7..c14c7b102f3 100644 --- a/rust/vote-tx-v2/src/lib.rs +++ b/rust/vote-tx-v2/src/lib.rs @@ -4,16 +4,28 @@ mod decoding; /// A vote struct. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct Vote { /// `choices` field - choices: Vec>, + choices: Vec, /// `proof` field - proof: Vec, + proof: Proof, /// `prop-id` field - prop_id: Vec, + prop_id: PropId, } +/// A choice struct. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Choice(Vec); + +/// A proof struct. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Proof(Vec); + +/// A prop id struct. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct PropId(Vec); + #[allow(missing_docs, clippy::missing_docs_in_private_items)] mod arbitrary_impl { use proptest::{ @@ -21,7 +33,7 @@ mod arbitrary_impl { sample::size_range, }; - use super::Vote; + use super::{Choice, Proof, PropId, Vote}; impl Arbitrary for Vote { type Parameters = (); @@ -35,9 +47,9 @@ mod arbitrary_impl { )) .prop_map(|(choices, proof, prop_id)| { Self { - choices, - proof, - prop_id, + choices: choices.into_iter().map(Choice).collect(), + proof: Proof(proof), + prop_id: PropId(prop_id), } }) .boxed() From 1f9855d535e5d88fa41353d94858617b6ffb3abb Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Mon, 11 Nov 2024 15:30:11 +0200 Subject: [PATCH 10/18] add new TxBody struct, cleanup gen_vote_tx.cddl --- .../catalyst_voting/cddl/gen_vote_tx.cddl | 4 +- .../catalyst_voting/gen_vote_tx.md | 2 +- rust/vote-tx-v2/src/lib.rs | 41 ++++++++++++++++++- 3 files changed, 42 insertions(+), 5 deletions(-) diff --git a/docs/src/architecture/08_concepts/catalyst_voting/cddl/gen_vote_tx.cddl b/docs/src/architecture/08_concepts/catalyst_voting/cddl/gen_vote_tx.cddl index 67635a0b206..693e151dabb 100644 --- a/docs/src/architecture/08_concepts/catalyst_voting/cddl/gen_vote_tx.cddl +++ b/docs/src/architecture/08_concepts/catalyst_voting/cddl/gen_vote_tx.cddl @@ -7,7 +7,7 @@ tx-body = [ vote-type event, votes, - voters-data, + voter-data, ] vote-type = UUID ; e.g. Public or Private vote @@ -25,7 +25,7 @@ choice = #6.24(bytes .cbor choice-t) ; encoded-cbor proof = #6.24(bytes .cbor proof-t) ; encoded-cbor prop-id = #6.24(bytes .cbor prop-id-t) ; encoded-cbor -voters-data = encoded-cbor +voter-data = encoded-cbor UUID = #6.37(bytes) ; UUID type signature = #6.98(cose.COSE_Sign) ; COSE signature diff --git a/docs/src/architecture/08_concepts/catalyst_voting/gen_vote_tx.md b/docs/src/architecture/08_concepts/catalyst_voting/gen_vote_tx.md index 1a2b70f6fdf..cc467ff19f3 100644 --- a/docs/src/architecture/08_concepts/catalyst_voting/gen_vote_tx.md +++ b/docs/src/architecture/08_concepts/catalyst_voting/gen_vote_tx.md @@ -42,7 +42,7 @@ Vote: so it's redundant to provide an additional identifier for the proposal, so it could be placed `null`. -`voters-data` - an any additional voter's specific data. +`voter-data` - an any additional voter's specific data. ### Transaction signing diff --git a/rust/vote-tx-v2/src/lib.rs b/rust/vote-tx-v2/src/lib.rs index c14c7b102f3..06e3960d06a 100644 --- a/rust/vote-tx-v2/src/lib.rs +++ b/rust/vote-tx-v2/src/lib.rs @@ -3,6 +3,17 @@ mod decoding; +/// A tx body struct. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct TxBody { + /// `vote-type` field + vote_type: Uuid, + /// `votes` field + votes: Vec, + /// `voter-data` field + voter_data: VoterData, +} + /// A vote struct. #[derive(Debug, Clone, PartialEq, Eq)] pub struct Vote { @@ -14,6 +25,14 @@ pub struct Vote { prop_id: PropId, } +/// A UUID struct. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Uuid(Vec); + +/// A voter's data struct. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct VoterData(Vec); + /// A choice struct. #[derive(Debug, Clone, PartialEq, Eq)] pub struct Choice(Vec); @@ -29,11 +48,29 @@ pub struct PropId(Vec); #[allow(missing_docs, clippy::missing_docs_in_private_items)] mod arbitrary_impl { use proptest::{ - prelude::{any_with, Arbitrary, BoxedStrategy, Strategy}, + prelude::{any, any_with, Arbitrary, BoxedStrategy, Strategy}, sample::size_range, }; - use super::{Choice, Proof, PropId, Vote}; + use super::{Choice, Proof, PropId, Uuid, Vote, VoterData}; + use crate::TxBody; + + impl Arbitrary for TxBody { + type Parameters = (); + type Strategy = BoxedStrategy; + + fn arbitrary_with((): Self::Parameters) -> Self::Strategy { + any::<(Vec, Vec, Vec)>() + .prop_map(|(vote_type, votes, voters_data)| { + Self { + vote_type: Uuid(vote_type), + votes, + voter_data: VoterData(voters_data), + } + }) + .boxed() + } + } impl Arbitrary for Vote { type Parameters = (); From 3fc98c850d812e7094c63a20a6d7cedd2e039594 Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Mon, 11 Nov 2024 15:45:22 +0200 Subject: [PATCH 11/18] add TxBody CBOR decoding/encoding impl --- rust/vote-tx-v2/src/decoding.rs | 129 +++++++++++++++++++++++++++----- 1 file changed, 111 insertions(+), 18 deletions(-) diff --git a/rust/vote-tx-v2/src/decoding.rs b/rust/vote-tx-v2/src/decoding.rs index ba5e49d9ac5..d4abdbd2441 100644 --- a/rust/vote-tx-v2/src/decoding.rs +++ b/rust/vote-tx-v2/src/decoding.rs @@ -3,13 +3,22 @@ use anyhow::anyhow; use minicbor::{data::IanaTag, Decode, Decoder, Encode, Encoder}; -use crate::{Choice, Proof, PropId, Vote}; +use crate::{Choice, Proof, PropId, TxBody, Uuid, Vote, VoterData}; -impl Vote { - /// Encodes `Vote` to CBOR encoded bytes. +/// UUID CBOR tag . +const CBOR_UUID_TAG: u64 = 37; + +/// `Vote` array struct length +const VOTE_LEN: u64 = 3; + +/// `TxBody` array struct length +const TX_BODY_LEN: u64 = 3; + +impl TxBody { + /// Encodes `TxBody` to CBOR encoded bytes. /// /// # Errors - /// - Cannot encode `Vote` + /// - Cannot encode `TxBody` pub fn to_bytes(&self) -> anyhow::Result> { let mut bytes = Vec::new(); let mut e = Encoder::new(&mut bytes); @@ -18,21 +27,106 @@ impl Vote { Ok(bytes) } - /// Decodes `Vote` from the CBOR encoded bytes. + /// Decodes `TxBody` from the CBOR encoded bytes. /// /// # Errors - /// - Cannot decode `Vote` + /// - Cannot decode `TxBody` pub fn from_bytes(bytes: &[u8]) -> anyhow::Result { let mut decoder = Decoder::new(bytes); - let res = Vote::decode(&mut decoder, &mut ()) + let res = Self::decode(&mut decoder, &mut ()) .map_err(|e| anyhow!("Cannot decode `{}`, {e}.", std::any::type_name::()))?; Ok(res) } } +impl Decode<'_, ()> for TxBody { + fn decode(d: &mut Decoder<'_>, (): &mut ()) -> Result { + let Some(TX_BODY_LEN) = d.array()? else { + return Err(minicbor::decode::Error::message(format!( + "must be a defined sized array with {TX_BODY_LEN} entries" + ))); + }; + + let vote_type = Uuid::decode(d, &mut ())?; + let votes = Vec::::decode(d, &mut ())?; + let voter_data = VoterData::decode(d, &mut ())?; + Ok(TxBody { + vote_type, + votes, + voter_data, + }) + } +} + +impl Encode<()> for TxBody { + fn encode( + &self, e: &mut Encoder, (): &mut (), + ) -> Result<(), minicbor::encode::Error> { + e.array(TX_BODY_LEN)?; + self.vote_type.encode(e, &mut ())?; + self.votes.encode(e, &mut ())?; + self.voter_data.encode(e, &mut ())?; + Ok(()) + } +} + +impl Decode<'_, ()> for VoterData { + fn decode(d: &mut Decoder<'_>, (): &mut ()) -> Result { + let tag = d.tag()?; + let expected_tag = minicbor::data::IanaTag::Cbor.tag(); + if expected_tag != tag { + return Err(minicbor::decode::Error::message(format!( + "tag value must be: {}, provided: {}", + expected_tag.as_u64(), + tag.as_u64(), + ))); + } + let choice = d.bytes()?.to_vec(); + Ok(Self(choice)) + } +} + +impl Encode<()> for VoterData { + fn encode( + &self, e: &mut minicbor::Encoder, (): &mut (), + ) -> Result<(), minicbor::encode::Error> { + e.tag(IanaTag::Cbor.tag())?; + e.bytes(&self.0)?; + Ok(()) + } +} + +impl Decode<'_, ()> for Uuid { + fn decode(d: &mut Decoder<'_>, (): &mut ()) -> Result { + let tag = d.tag()?; + if CBOR_UUID_TAG != tag.as_u64() { + return Err(minicbor::decode::Error::message(format!( + "tag value must be: {CBOR_UUID_TAG}, provided: {}", + tag.as_u64(), + ))); + } + let choice = d.bytes()?.to_vec(); + Ok(Self(choice)) + } +} + +impl Encode<()> for Uuid { + fn encode( + &self, e: &mut minicbor::Encoder, (): &mut (), + ) -> Result<(), minicbor::encode::Error> { + e.tag(IanaTag::Cbor.tag())?; + e.bytes(&self.0)?; + Ok(()) + } +} + impl Decode<'_, ()> for Vote { fn decode(d: &mut Decoder<'_>, (): &mut ()) -> Result { - d.array()?; + let Some(VOTE_LEN) = d.array()? else { + return Err(minicbor::decode::Error::message(format!( + "must be a defined sized array with {VOTE_LEN} entries" + ))); + }; let choices = Vec::::decode(d, &mut ())?; if choices.is_empty() { @@ -55,8 +149,7 @@ impl Encode<()> for Vote { fn encode( &self, e: &mut minicbor::Encoder, (): &mut (), ) -> Result<(), minicbor::encode::Error> { - e.array(3)?; - + e.array(VOTE_LEN)?; self.choices.encode(e, &mut ())?; self.proof.encode(e, &mut ())?; self.prop_id.encode(e, &mut ())?; @@ -76,7 +169,7 @@ impl Decode<'_, ()> for Choice { ))); } let choice = d.bytes()?.to_vec(); - Ok(Choice(choice)) + Ok(Self(choice)) } } @@ -102,7 +195,7 @@ impl Decode<'_, ()> for Proof { ))); } let choice = d.bytes()?.to_vec(); - Ok(Proof(choice)) + Ok(Self(choice)) } } @@ -128,7 +221,7 @@ impl Decode<'_, ()> for PropId { ))); } let choice = d.bytes()?.to_vec(); - Ok(PropId(choice)) + Ok(Self(choice)) } } @@ -147,12 +240,12 @@ mod tests { use proptest::prelude::ProptestConfig; use test_strategy::proptest; - use super::Vote; + use super::TxBody; #[proptest(ProptestConfig::with_cases(0))] - fn vote_from_bytes_to_bytes_test(vote: Vote) { - let bytes = vote.to_bytes().unwrap(); - let decoded = Vote::from_bytes(&bytes).unwrap(); - assert_eq!(vote, decoded); + fn vote_from_bytes_to_bytes_test(tx_body: TxBody) { + let bytes = tx_body.to_bytes().unwrap(); + let decoded = TxBody::from_bytes(&bytes).unwrap(); + assert_eq!(tx_body, decoded); } } From db7eaa97ff2c22d87f12464855b145009487f4df Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Mon, 11 Nov 2024 15:58:46 +0200 Subject: [PATCH 12/18] add Cbor trait --- rust/vote-tx-v2/src/decoding.rs | 48 +++++++++++---------------------- rust/vote-tx-v2/src/lib.rs | 31 +++++++++++++++++++++ 2 files changed, 47 insertions(+), 32 deletions(-) diff --git a/rust/vote-tx-v2/src/decoding.rs b/rust/vote-tx-v2/src/decoding.rs index d4abdbd2441..60a549223a0 100644 --- a/rust/vote-tx-v2/src/decoding.rs +++ b/rust/vote-tx-v2/src/decoding.rs @@ -1,7 +1,9 @@ //! CBOR encoding and decoding implementation. -use anyhow::anyhow; -use minicbor::{data::IanaTag, Decode, Decoder, Encode, Encoder}; +use minicbor::{ + data::{IanaTag, Tag}, + Decode, Decoder, Encode, Encoder, +}; use crate::{Choice, Proof, PropId, TxBody, Uuid, Vote, VoterData}; @@ -14,31 +16,6 @@ const VOTE_LEN: u64 = 3; /// `TxBody` array struct length const TX_BODY_LEN: u64 = 3; -impl TxBody { - /// Encodes `TxBody` to CBOR encoded bytes. - /// - /// # Errors - /// - Cannot encode `TxBody` - pub fn to_bytes(&self) -> anyhow::Result> { - let mut bytes = Vec::new(); - let mut e = Encoder::new(&mut bytes); - self.encode(&mut e, &mut ()) - .map_err(|e| anyhow!("Cannot encode `{}`, {e}.", std::any::type_name::()))?; - Ok(bytes) - } - - /// Decodes `TxBody` from the CBOR encoded bytes. - /// - /// # Errors - /// - Cannot decode `TxBody` - pub fn from_bytes(bytes: &[u8]) -> anyhow::Result { - let mut decoder = Decoder::new(bytes); - let res = Self::decode(&mut decoder, &mut ()) - .map_err(|e| anyhow!("Cannot decode `{}`, {e}.", std::any::type_name::()))?; - Ok(res) - } -} - impl Decode<'_, ()> for TxBody { fn decode(d: &mut Decoder<'_>, (): &mut ()) -> Result { let Some(TX_BODY_LEN) = d.array()? else { @@ -114,7 +91,7 @@ impl Encode<()> for Uuid { fn encode( &self, e: &mut minicbor::Encoder, (): &mut (), ) -> Result<(), minicbor::encode::Error> { - e.tag(IanaTag::Cbor.tag())?; + e.tag(Tag::new(CBOR_UUID_TAG))?; e.bytes(&self.0)?; Ok(()) } @@ -237,15 +214,22 @@ impl Encode<()> for PropId { #[cfg(test)] mod tests { - use proptest::prelude::ProptestConfig; use test_strategy::proptest; - use super::TxBody; + use super::*; + use crate::Cbor; - #[proptest(ProptestConfig::with_cases(0))] - fn vote_from_bytes_to_bytes_test(tx_body: TxBody) { + #[proptest] + fn tx_body_from_bytes_to_bytes_test(tx_body: TxBody) { let bytes = tx_body.to_bytes().unwrap(); let decoded = TxBody::from_bytes(&bytes).unwrap(); assert_eq!(tx_body, decoded); } + + #[proptest] + fn vote_from_bytes_to_bytes_test(vote: Vote) { + let bytes = vote.to_bytes().unwrap(); + let decoded = Vote::from_bytes(&bytes).unwrap(); + assert_eq!(vote, decoded); + } } diff --git a/rust/vote-tx-v2/src/lib.rs b/rust/vote-tx-v2/src/lib.rs index 06e3960d06a..80e115cd088 100644 --- a/rust/vote-tx-v2/src/lib.rs +++ b/rust/vote-tx-v2/src/lib.rs @@ -1,6 +1,9 @@ //! A Catalyst vote transaction v1 object, structured following this //! [spec](https://input-output-hk.github.io/catalyst-libs/architecture/08_concepts/catalyst_voting/v2/) +use anyhow::anyhow; +use minicbor::{Decode, Decoder, Encode, Encoder}; + mod decoding; /// A tx body struct. @@ -45,6 +48,34 @@ pub struct Proof(Vec); #[derive(Debug, Clone, PartialEq, Eq)] pub struct PropId(Vec); +/// Cbor encodable and decodable type trait. +pub trait Cbor<'a>: Encode<()> + Decode<'a, ()> { + /// Encodes to CBOR encoded bytes. + /// + /// # Errors + /// - Cannot encode + fn to_bytes(&self) -> anyhow::Result> { + let mut bytes = Vec::new(); + let mut e = Encoder::new(&mut bytes); + self.encode(&mut e, &mut ()) + .map_err(|e| anyhow!("Cannot encode `{}`, {e}.", std::any::type_name::()))?; + Ok(bytes) + } + + /// Decodes from the CBOR encoded bytes. + /// + /// # Errors + /// - Cannot decode + fn from_bytes(bytes: &'a [u8]) -> anyhow::Result { + let mut decoder = Decoder::new(bytes); + let res = Self::decode(&mut decoder, &mut ()) + .map_err(|e| anyhow!("Cannot decode `{}`, {e}.", std::any::type_name::()))?; + Ok(res) + } +} + +impl<'a, T> Cbor<'a> for T where T: Encode<()> + Decode<'a, ()> {} + #[allow(missing_docs, clippy::missing_docs_in_private_items)] mod arbitrary_impl { use proptest::{ From 2e2962154f0073fc2df917898d20b3fe90d98a95 Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Mon, 11 Nov 2024 16:53:28 +0200 Subject: [PATCH 13/18] add GeneralisedTx struct --- rust/vote-tx-v2/src/decoding.rs | 47 ++++++++++++++++++++++----------- rust/vote-tx-v2/src/lib.rs | 19 +++++++++++-- 2 files changed, 49 insertions(+), 17 deletions(-) diff --git a/rust/vote-tx-v2/src/decoding.rs b/rust/vote-tx-v2/src/decoding.rs index 60a549223a0..26a07a603ec 100644 --- a/rust/vote-tx-v2/src/decoding.rs +++ b/rust/vote-tx-v2/src/decoding.rs @@ -5,7 +5,7 @@ use minicbor::{ Decode, Decoder, Encode, Encoder, }; -use crate::{Choice, Proof, PropId, TxBody, Uuid, Vote, VoterData}; +use crate::{Choice, GeneralisedTx, Proof, PropId, TxBody, Uuid, Vote, VoterData}; /// UUID CBOR tag . const CBOR_UUID_TAG: u64 = 37; @@ -16,18 +16,34 @@ const VOTE_LEN: u64 = 3; /// `TxBody` array struct length const TX_BODY_LEN: u64 = 3; -impl Decode<'_, ()> for TxBody { +/// `GeneralisedTx` array struct length +const GENERALISED_TX_LEN: u64 = 1; + +impl Decode<'_, ()> for GeneralisedTx { fn decode(d: &mut Decoder<'_>, (): &mut ()) -> Result { - let Some(TX_BODY_LEN) = d.array()? else { - return Err(minicbor::decode::Error::message(format!( - "must be a defined sized array with {TX_BODY_LEN} entries" - ))); - }; + d.array()?; + let tx_body = TxBody::decode(d, &mut ())?; + Ok(Self { tx_body }) + } +} +impl Encode<()> for GeneralisedTx { + fn encode( + &self, e: &mut Encoder, (): &mut (), + ) -> Result<(), minicbor::encode::Error> { + e.array(GENERALISED_TX_LEN)?; + self.tx_body.encode(e, &mut ())?; + Ok(()) + } +} + +impl Decode<'_, ()> for TxBody { + fn decode(d: &mut Decoder<'_>, (): &mut ()) -> Result { + d.array()?; let vote_type = Uuid::decode(d, &mut ())?; let votes = Vec::::decode(d, &mut ())?; let voter_data = VoterData::decode(d, &mut ())?; - Ok(TxBody { + Ok(Self { vote_type, votes, voter_data, @@ -99,12 +115,7 @@ impl Encode<()> for Uuid { impl Decode<'_, ()> for Vote { fn decode(d: &mut Decoder<'_>, (): &mut ()) -> Result { - let Some(VOTE_LEN) = d.array()? else { - return Err(minicbor::decode::Error::message(format!( - "must be a defined sized array with {VOTE_LEN} entries" - ))); - }; - + d.array()?; let choices = Vec::::decode(d, &mut ())?; if choices.is_empty() { return Err(minicbor::decode::Error::message( @@ -113,7 +124,6 @@ impl Decode<'_, ()> for Vote { } let proof = Proof::decode(d, &mut ())?; let prop_id = PropId::decode(d, &mut ())?; - Ok(Self { choices, proof, @@ -219,6 +229,13 @@ mod tests { use super::*; use crate::Cbor; + #[proptest] + fn generalised_tx_from_bytes_to_bytes_test(generalised_tx: GeneralisedTx) { + let bytes = generalised_tx.to_bytes().unwrap(); + let decoded = GeneralisedTx::from_bytes(&bytes).unwrap(); + assert_eq!(generalised_tx, decoded); + } + #[proptest] fn tx_body_from_bytes_to_bytes_test(tx_body: TxBody) { let bytes = tx_body.to_bytes().unwrap(); diff --git a/rust/vote-tx-v2/src/lib.rs b/rust/vote-tx-v2/src/lib.rs index 80e115cd088..c5ac8fc9abb 100644 --- a/rust/vote-tx-v2/src/lib.rs +++ b/rust/vote-tx-v2/src/lib.rs @@ -6,6 +6,13 @@ use minicbor::{Decode, Decoder, Encode, Encoder}; mod decoding; +/// A geeneralised tx struct. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct GeneralisedTx { + /// `tx-body` + tx_body: TxBody, +} + /// A tx body struct. #[derive(Debug, Clone, PartialEq, Eq)] pub struct TxBody { @@ -83,8 +90,16 @@ mod arbitrary_impl { sample::size_range, }; - use super::{Choice, Proof, PropId, Uuid, Vote, VoterData}; - use crate::TxBody; + use super::{Choice, GeneralisedTx, Proof, PropId, TxBody, Uuid, Vote, VoterData}; + + impl Arbitrary for GeneralisedTx { + type Parameters = (); + type Strategy = BoxedStrategy; + + fn arbitrary_with((): Self::Parameters) -> Self::Strategy { + any::().prop_map(|tx_body| Self { tx_body }).boxed() + } + } impl Arbitrary for TxBody { type Parameters = (); From fa53ddd90b533688bc5fb268f5ba4dde5962599d Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Mon, 11 Nov 2024 18:22:37 +0200 Subject: [PATCH 14/18] wip --- rust/vote-tx-v2/src/decoding.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/rust/vote-tx-v2/src/decoding.rs b/rust/vote-tx-v2/src/decoding.rs index 26a07a603ec..a9289d7d231 100644 --- a/rust/vote-tx-v2/src/decoding.rs +++ b/rust/vote-tx-v2/src/decoding.rs @@ -1,4 +1,5 @@ //! CBOR encoding and decoding implementation. +//! use minicbor::{ data::{IanaTag, Tag}, From 6561cd62ad0b774dcb5f2d67db40d70e1d9b9299 Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Mon, 11 Nov 2024 18:26:45 +0200 Subject: [PATCH 15/18] wip --- .github/workflows/semantic_pull_request.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/semantic_pull_request.yml b/.github/workflows/semantic_pull_request.yml index b788d8a508f..bc8ae9585e6 100644 --- a/.github/workflows/semantic_pull_request.yml +++ b/.github/workflows/semantic_pull_request.yml @@ -19,6 +19,8 @@ jobs: rust/c509-certificate rust/cardano-chain-follower rust/catalyst-voting + rust/vote-tx-v1 + rust/vote-tx-v2 rust/cbork rust/hermes-ipfs dart From 9d0ca6687c2ff69b92e0421120b93fd7c9e6aaa0 Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Mon, 11 Nov 2024 18:34:22 +0200 Subject: [PATCH 16/18] fix spelling --- rust/vote-tx-v2/src/decoding.rs | 20 ++++++++++---------- rust/vote-tx-v2/src/lib.rs | 8 ++++---- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/rust/vote-tx-v2/src/decoding.rs b/rust/vote-tx-v2/src/decoding.rs index a9289d7d231..81564524420 100644 --- a/rust/vote-tx-v2/src/decoding.rs +++ b/rust/vote-tx-v2/src/decoding.rs @@ -6,7 +6,7 @@ use minicbor::{ Decode, Decoder, Encode, Encoder, }; -use crate::{Choice, GeneralisedTx, Proof, PropId, TxBody, Uuid, Vote, VoterData}; +use crate::{Choice, GeneralizedTx, Proof, PropId, TxBody, Uuid, Vote, VoterData}; /// UUID CBOR tag . const CBOR_UUID_TAG: u64 = 37; @@ -17,10 +17,10 @@ const VOTE_LEN: u64 = 3; /// `TxBody` array struct length const TX_BODY_LEN: u64 = 3; -/// `GeneralisedTx` array struct length -const GENERALISED_TX_LEN: u64 = 1; +/// `GeneralizedTx` array struct length +const GENERALIZED_TX_LEN: u64 = 1; -impl Decode<'_, ()> for GeneralisedTx { +impl Decode<'_, ()> for GeneralizedTx { fn decode(d: &mut Decoder<'_>, (): &mut ()) -> Result { d.array()?; let tx_body = TxBody::decode(d, &mut ())?; @@ -28,11 +28,11 @@ impl Decode<'_, ()> for GeneralisedTx { } } -impl Encode<()> for GeneralisedTx { +impl Encode<()> for GeneralizedTx { fn encode( &self, e: &mut Encoder, (): &mut (), ) -> Result<(), minicbor::encode::Error> { - e.array(GENERALISED_TX_LEN)?; + e.array(GENERALIZED_TX_LEN)?; self.tx_body.encode(e, &mut ())?; Ok(()) } @@ -231,10 +231,10 @@ mod tests { use crate::Cbor; #[proptest] - fn generalised_tx_from_bytes_to_bytes_test(generalised_tx: GeneralisedTx) { - let bytes = generalised_tx.to_bytes().unwrap(); - let decoded = GeneralisedTx::from_bytes(&bytes).unwrap(); - assert_eq!(generalised_tx, decoded); + fn generalized_tx_from_bytes_to_bytes_test(generalized_tx: GeneralizedTx) { + let bytes = generalized_tx.to_bytes().unwrap(); + let decoded = GeneralizedTx::from_bytes(&bytes).unwrap(); + assert_eq!(generalized_tx, decoded); } #[proptest] diff --git a/rust/vote-tx-v2/src/lib.rs b/rust/vote-tx-v2/src/lib.rs index c5ac8fc9abb..a169a329695 100644 --- a/rust/vote-tx-v2/src/lib.rs +++ b/rust/vote-tx-v2/src/lib.rs @@ -6,9 +6,9 @@ use minicbor::{Decode, Decoder, Encode, Encoder}; mod decoding; -/// A geeneralised tx struct. +/// A generalized tx struct. #[derive(Debug, Clone, PartialEq, Eq)] -pub struct GeneralisedTx { +pub struct GeneralizedTx { /// `tx-body` tx_body: TxBody, } @@ -90,9 +90,9 @@ mod arbitrary_impl { sample::size_range, }; - use super::{Choice, GeneralisedTx, Proof, PropId, TxBody, Uuid, Vote, VoterData}; + use super::{Choice, GeneralizedTx, Proof, PropId, TxBody, Uuid, Vote, VoterData}; - impl Arbitrary for GeneralisedTx { + impl Arbitrary for GeneralizedTx { type Parameters = (); type Strategy = BoxedStrategy; From 2af0e7f15344327460bbf87dd77c50d0e262f698 Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Mon, 11 Nov 2024 18:48:15 +0200 Subject: [PATCH 17/18] fix doctest --- rust/vote-tx-v1/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/vote-tx-v1/src/lib.rs b/rust/vote-tx-v1/src/lib.rs index 648b4e2df30..c7f46ac6750 100644 --- a/rust/vote-tx-v1/src/lib.rs +++ b/rust/vote-tx-v1/src/lib.rs @@ -6,7 +6,7 @@ //! crypto::{ed25519::PrivateKey, rng::default_rng}, //! vote_protocol::committee::ElectionSecretKey, //! }; -//! use jormungandr_vote_tx::Tx; +//! use vote_tx_v1::Tx; //! //! let vote_plan_id = [0u8; 32]; //! let proposal_index = 0u8; From ab9999e14e007beaf8eb50cc651f6127e862fa13 Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Tue, 12 Nov 2024 17:33:57 +0200 Subject: [PATCH 18/18] add array lenth validation --- rust/vote-tx-v2/src/decoding.rs | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/rust/vote-tx-v2/src/decoding.rs b/rust/vote-tx-v2/src/decoding.rs index 81564524420..775dd5db6d8 100644 --- a/rust/vote-tx-v2/src/decoding.rs +++ b/rust/vote-tx-v2/src/decoding.rs @@ -22,7 +22,12 @@ const GENERALIZED_TX_LEN: u64 = 1; impl Decode<'_, ()> for GeneralizedTx { fn decode(d: &mut Decoder<'_>, (): &mut ()) -> Result { - d.array()?; + let Some(GENERALIZED_TX_LEN) = d.array()? else { + return Err(minicbor::decode::Error::message(format!( + "must be a defined sized array with {GENERALIZED_TX_LEN} entries" + ))); + }; + let tx_body = TxBody::decode(d, &mut ())?; Ok(Self { tx_body }) } @@ -40,7 +45,12 @@ impl Encode<()> for GeneralizedTx { impl Decode<'_, ()> for TxBody { fn decode(d: &mut Decoder<'_>, (): &mut ()) -> Result { - d.array()?; + let Some(TX_BODY_LEN) = d.array()? else { + return Err(minicbor::decode::Error::message(format!( + "must be a defined sized array with {GENERALIZED_TX_LEN} entries" + ))); + }; + let vote_type = Uuid::decode(d, &mut ())?; let votes = Vec::::decode(d, &mut ())?; let voter_data = VoterData::decode(d, &mut ())?; @@ -116,7 +126,12 @@ impl Encode<()> for Uuid { impl Decode<'_, ()> for Vote { fn decode(d: &mut Decoder<'_>, (): &mut ()) -> Result { - d.array()?; + let Some(VOTE_LEN) = d.array()? else { + return Err(minicbor::decode::Error::message(format!( + "must be a defined sized array with {VOTE_LEN} entries" + ))); + }; + let choices = Vec::::decode(d, &mut ())?; if choices.is_empty() { return Err(minicbor::decode::Error::message(