From 54997daf66ddfadb402c0da46f27afb9507c2873 Mon Sep 17 00:00:00 2001 From: Santiago Carmuega Date: Fri, 8 Mar 2024 19:32:03 -0300 Subject: [PATCH] fix(primitives): contemplate Conway's CBOR `set` tag (#421) Co-authored-by: jmhrpr <25673452+jmhrpr@users.noreply.github.com> --- pallas-codec/src/utils.rs | 85 ++++++++++++++++++++++++++- pallas-primitives/src/conway/model.rs | 59 +++++++++---------- test_data/conway1.block | 1 + 3 files changed, 114 insertions(+), 31 deletions(-) create mode 100644 test_data/conway1.block diff --git a/pallas-codec/src/utils.rs b/pallas-codec/src/utils.rs index c197f70..16d0f6f 100644 --- a/pallas-codec/src/utils.rs +++ b/pallas-codec/src/utils.rs @@ -1,7 +1,13 @@ -use minicbor::{data::Tag, Decode, Encode}; +use minicbor::{ + data::{Tag, Type}, + decode::Error, + Decode, Encode, +}; use serde::{Deserialize, Serialize}; use std::{fmt, hash::Hash as StdHash, ops::Deref}; +static TAG_SET: u64 = 258; + /// Utility for skipping parts of the CBOR payload, use only for debugging #[derive(Debug, PartialEq, PartialOrd, Eq, Ord)] pub struct SkipCbor {} @@ -474,6 +480,83 @@ where } } +/// Set +/// +/// Optional 258 tag (until era after Conway, at which point is it required) +/// with a vec of items which should contain no duplicates +#[derive(Debug, PartialEq, Eq, Clone, PartialOrd, Serialize, Deserialize)] +pub struct Set(Vec); + +impl Set { + pub fn to_vec(self) -> Vec { + self.0 + } +} + +impl Deref for Set { + type Target = Vec; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl From> for Set { + fn from(value: Vec) -> Self { + Set(value) + } +} + +impl From>> for Set { + fn from(value: Set>) -> Self { + let inner = value.0.into_iter().map(|x| x.unwrap()).collect(); + Self(inner) + } +} + +impl<'a, T> IntoIterator for &'a Set { + type Item = &'a T; + type IntoIter = std::slice::Iter<'a, T>; + + fn into_iter(self) -> Self::IntoIter { + self.0.iter() + } +} + +impl<'b, C, T> minicbor::decode::Decode<'b, C> for Set +where + T: Decode<'b, C>, +{ + fn decode(d: &mut minicbor::Decoder<'b>, ctx: &mut C) -> Result { + // decode optional set tag (this will be required in era following Conway) + if d.datatype()? == Type::Tag { + let found_tag = d.tag()?; + + if found_tag != Tag::Unassigned(TAG_SET) { + return Err(Error::message(format!("Unrecognised tag: {found_tag:?}"))); + } + } + + Ok(Self(d.decode_with(ctx)?)) + } +} + +impl minicbor::encode::Encode for Set +where + T: Encode, +{ + fn encode( + &self, + e: &mut minicbor::Encoder, + ctx: &mut C, + ) -> Result<(), minicbor::encode::Error> { + e.tag(Tag::Unassigned(TAG_SET))?; + e.encode_with(&self.0, ctx)?; + + Ok(()) + } +} + /// A uint structure that preserves original int length #[derive(Debug, PartialEq, Copy, Clone, PartialOrd, Eq, Ord, Hash)] pub enum AnyUInt { diff --git a/pallas-primitives/src/conway/model.rs b/pallas-primitives/src/conway/model.rs index 61b30b9..e04ba49 100644 --- a/pallas-primitives/src/conway/model.rs +++ b/pallas-primitives/src/conway/model.rs @@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize}; use pallas_codec::minicbor::{Decode, Encode}; use pallas_crypto::hash::Hash; -use pallas_codec::utils::{Bytes, KeepRaw, KeyValuePairs, MaybeIndefArray, Nullable}; +use pallas_codec::utils::{Bytes, KeepRaw, KeyValuePairs, MaybeIndefArray, Nullable, Set}; // required for derive attrs to work use pallas_codec::minicbor; @@ -66,7 +66,7 @@ pub use crate::alonzo::RewardAccount; pub type Withdrawals = KeyValuePairs; -pub type RequiredSigners = Vec; +pub type RequiredSigners = Set; // TODO: NON EMPTY SET pub use crate::alonzo::Port; @@ -106,7 +106,7 @@ pub enum Certificate { cost: Coin, margin: UnitInterval, reward_account: RewardAccount, - pool_owners: Vec, + pool_owners: Set, relays: Vec, pool_metadata: Option, }, @@ -645,7 +645,7 @@ impl minicbor::Encode for DRepVotingThresholds { #[cbor(map)] pub struct PseudoTransactionBody { #[n(0)] - pub inputs: Vec, + pub inputs: Set, #[n(1)] pub outputs: Vec, @@ -657,7 +657,7 @@ pub struct PseudoTransactionBody { pub ttl: Option, #[n(4)] - pub certificates: Option>, // TODO: NON EMPTY + pub certificates: Option>, // TODO: NON EMPTY ORDERED SET #[n(5)] pub withdrawals: Option>, // TODO: NON EMPTY @@ -677,7 +677,7 @@ pub struct PseudoTransactionBody { pub script_data_hash: Option>, #[n(13)] - pub collateral: Option>, // TODO: NON EMPTY SET + pub collateral: Option>, // TODO: NON EMPTY SET #[n(14)] pub required_signers: Option>, // TODO: NON EMPTY SET @@ -692,14 +692,14 @@ pub struct PseudoTransactionBody { pub total_collateral: Option, #[n(18)] - pub reference_inputs: Option>, // TODO: NON EMPTY SET + pub reference_inputs: Option>, // TODO: NON EMPTY SET // -- NEW IN CONWAY #[n(19)] pub voting_procedures: Option, #[n(20)] - pub proposal_procedures: Option>, // TODO: NON EMPTY MAP + pub proposal_procedures: Option>, // TODO: NON EMPTY ORDERED SET #[n(21)] pub treasury_value: Option, @@ -858,7 +858,7 @@ pub enum GovAction { NoConfidence(Option), UpdateCommittee( Option, - Vec, + Set, KeyValuePairs, UnitInterval, ), @@ -1229,70 +1229,66 @@ pub use crate::alonzo::BootstrapWitness; #[cbor(map)] pub struct WitnessSet { #[n(0)] - pub vkeywitness: Option>, + pub vkeywitness: Option>, // TODO: NON EMPTY SET #[n(1)] - pub native_script: Option>, + pub native_script: Option>, // TODO: NON EMPTY SET #[n(2)] - pub bootstrap_witness: Option>, + pub bootstrap_witness: Option>, // TODO: NON EMPTY SET #[n(3)] - pub plutus_v1_script: Option>, + pub plutus_v1_script: Option>, // TODO: NON EMPTY SET #[n(4)] - pub plutus_data: Option>, + pub plutus_data: Option>, // TODO: NON EMPTY SET #[n(5)] pub redeemer: Option>, #[n(6)] - pub plutus_v2_script: Option>, + pub plutus_v2_script: Option>, // TODO: NON EMPTY SET #[n(7)] - pub plutus_v3_script: Option>, + pub plutus_v3_script: Option>, // TODO: NON EMPTY SET } #[derive(Encode, Decode, Debug, PartialEq, Clone)] #[cbor(map)] pub struct MintedWitnessSet<'b> { #[n(0)] - pub vkeywitness: Option>, + pub vkeywitness: Option>, // TODO: NON EMPTY SET #[n(1)] - pub native_script: Option>>, + pub native_script: Option>>, // TODO: NON EMPTY SET #[n(2)] - pub bootstrap_witness: Option>, + pub bootstrap_witness: Option>, // TODO: NON EMPTY SET #[n(3)] - pub plutus_v1_script: Option>, + pub plutus_v1_script: Option>, // TODO: NON EMPTY SET #[b(4)] - pub plutus_data: Option>>, + pub plutus_data: Option>>, // TODO: NON EMPTY SET #[n(5)] pub redeemer: Option>, #[n(6)] - pub plutus_v2_script: Option>, + pub plutus_v2_script: Option>, // TODO: NON EMPTY SET #[n(7)] - pub plutus_v3_script: Option>, + pub plutus_v3_script: Option>, // TODO: NON EMPTY SET } impl<'b> From> for WitnessSet { fn from(x: MintedWitnessSet<'b>) -> Self { WitnessSet { vkeywitness: x.vkeywitness, - native_script: x - .native_script - .map(|x| x.into_iter().map(|x| x.unwrap()).collect()), + native_script: x.native_script.map(Into::into), bootstrap_witness: x.bootstrap_witness, plutus_v1_script: x.plutus_v1_script, - plutus_data: x - .plutus_data - .map(|x| x.into_iter().map(|x| x.unwrap()).collect()), + plutus_data: x.plutus_data.map(Into::into), redeemer: x.redeemer, plutus_v2_script: x.plutus_v2_script, plutus_v3_script: x.plutus_v3_script, @@ -1520,7 +1516,10 @@ mod tests { #[test] fn block_isomorphic_decoding_encoding() { - let test_blocks = [include_str!("../../../test_data/conway1.artificial.block")]; + let test_blocks = [ + include_str!("../../../test_data/conway1.block"), + include_str!("../../../test_data/conway1.artificial.block"), + ]; for (idx, block_str) in test_blocks.iter().enumerate() { println!("decoding test block {}", idx + 1); diff --git a/test_data/conway1.block b/test_data/conway1.block new file mode 100644 index 0000000..df783dd --- /dev/null +++ b/test_data/conway1.block @@ -0,0 +1 @@ +820785828a1a0010afaa1a0150d7925820a22f65265e7a71cfc3b637d6aefe8f8241d562f5b1b787ff36697ae4c3886f185820e856c84a3d90c8526891bd58d957afadc522de37b14ae04c395db8a7a1b08c4a582015587d5633be324f8de97168399ab59d7113f0a74bc7412b81f7cc1007491671825840af9ff8cb146880eba1b12beb72d86be46fbc98f6b88110cd009bd6746d255a14bb0637e3a29b7204bff28236c1b9f73e501fed1eb5634bd741be120332d25e5e5850a9f1de24d01ba43b025a3351b25de50cc77f931ed8cdd0be632ad1a437ec9cf327b24eb976f91dbf68526f15bacdf8f0c1ea4a2072df9412796b34836a816760f4909b98c0e76b160d9aec6b2da060071903705820b5858c659096fcc19f2f3baef5fdd6198641a623bd43e792157b5ea3a2ecc85c8458200ca1ec2c1c2af308bd9e7a86eb12d603a26157752f3f71c337781c456e6ed0c90018a558408e554b644a2b25cb5892d07a26c273893829f1650ec33bf6809d953451c519c32cfd48d044cd897a17cdef154d5f5c9b618d9b54f8c49e170082c08c236524098209005901c05a96b747789ef6678b2f4a2a7caca92e270f736e9b621686f95dd1332005102faee21ed50cf6fa6c67e38b33df686c79c91d55f30769f7c964d98aa84cbefe0a808ee6f45faaf9badcc3f746e6a51df1aa979195871fd5ffd91037ea216803be7e7fccbf4c13038c459c7a14906ab57f3306fe155af7877c88866eede7935f642f6a72f1368c33ed5cc7607c995754af787a5af486958edb531c0ae65ce9fdce423ad88925e13ef78700950093ae707bb1100299a66a5bb15137f7ba62132ba1c9b74495aac50e1106bacb5db2bed4592f66b610c2547f485d061c6c149322b0c92bdde644eb672267fdab5533157ff398b9e16dd6a06edfd67151e18a3ac93fc28a51f9a73f8b867f5f432b1d9b5ae454ef63dea7e1a78631cf3fee1ba82db61726701ac5db1c4fee4bb6316768c82c0cdc4ebd58ccc686be882f9608592b3c718e4b5d356982a6b83433fe76d37394eff9f3a8e4773e3bab9a8b93b4ea90fa33bfbcf0dc5a21bfe64be2eefaa82c0494ab729e50596110f60ae9ad64b3eb9ddb54001b03cc264b65634c071d3b24a44322f39a9eae239fd886db8d429969433cb2d0a82d7877f174b0e154262f1af44ce5bc053b62daadd2926f957440ff3981a600d9010281825820af09d312a642fecb47da719156517bec678469c15789bcf002ce2ef563edf54200018182581d6052e63f22c5107ed776b70f7b92248b02552fd08f3e747bc745099441821b00000001373049f4a1581c34250edd1e9836f5378702fbf9416b709bc140e04f668cc355208518a1494154414441636f696e1953a6021a000306b5031a01525e0209a1581c34250edd1e9836f5378702fbf9416b709bc140e04f668cc355208518a1494154414441636f696e010758206cf243cc513691d9edc092b1030c6d1e5f9a8621a4d4383032b3d292d4679d5c81a200d90102828258201287e9ce9e00a603d250b557146aa0581fc4edf277a244ce39d3b2f2ced5072f5840d40fbe736892d8dab09e864a25f2e59fb7bfe445d960bbace30996965dc12a34c59746febf9d32ade65b6a9e1a1a6efc53830a3acaab699972cd4f240c024c0f825820742d8af3543349b5b18f3cba28f23b2d6e465b9c136c42e1fae6b2390f565427584005637b5645784bd998bb8ed837021d520200211fdd958b9a4d4b3af128fa6e695fb86abad7a9ddad6f1db946f8b812113fa16cfb7025e2397277b14e8c9bed0a01d90102818200581c45d70e54f3b5e9c5a2b0cd417028197bd6f5fa5378c2f5eba896678da100d90103a100a11902a2a1636d73678f78264175746f2d4c6f6f702d5472616e73616374696f6e202336323733363820627920415441444160783c4c6976652045706f6368203235352c207765206861766520303131682035396d20323573206c65667420756e74696c20746865206e657874206f6e6578344974277320536f6e6e746167202d20323520466562727561722032303234202d2031333a33303a333520696e20417573747269616060607820412072616e646f6d205a656e2d51756f746520666f7220796f753a20f09f998f78344974206973206e6576657220746f6f206c61746520746f206265207768617420796f75206d696768742068617665206265656e2e6f202d2047656f72676520456c696f746078374e6f64652d5265766973696f6e3a203462623230343864623737643632336565366533363738363138633264386236633436373633333360782953616e63686f4e657420697320617765736f6d652c206861766520736f6d652066756e2120f09f988d7819204265737420726567617264732c204d617274696e203a2d2980 \ No newline at end of file