diff --git a/Cargo.toml b/Cargo.toml index 53c1ad5..91d9bd1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ members = [ "pallas-localstate", "pallas-crypto", "pallas-alonzo", + "pallas-byron", "pallas", ] diff --git a/examples/byron-decoder/.gitignore b/examples/byron-decoder/.gitignore new file mode 100644 index 0000000..2e01a47 --- /dev/null +++ b/examples/byron-decoder/.gitignore @@ -0,0 +1,4 @@ +/target + +scratchpad +.DS_Store \ No newline at end of file diff --git a/examples/byron-decoder/Cargo.toml b/examples/byron-decoder/Cargo.toml new file mode 100644 index 0000000..3df1b64 --- /dev/null +++ b/examples/byron-decoder/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "byron-decoder" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +pallas = { path = "../../pallas" } +net2 = "0.2.37" +env_logger = "0.9.0" +hex = "0.4.3" + +[workspace] diff --git a/examples/byron-decoder/src/main.rs b/examples/byron-decoder/src/main.rs new file mode 100644 index 0000000..a41e8e9 --- /dev/null +++ b/examples/byron-decoder/src/main.rs @@ -0,0 +1,60 @@ +use net2::TcpStreamExt; + +use pallas::ledger::byron::*; +use pallas::ouroboros::network::blockfetch::{BatchClient, Observer}; +use pallas::ouroboros::network::handshake::{ + n2n::{Client, VersionTable}, + MAINNET_MAGIC, +}; +use pallas::ouroboros::network::machines::primitives::Point; +use pallas::ouroboros::network::machines::run_agent; +use pallas::ouroboros::network::multiplexer::Multiplexer; +use std::net::TcpStream; + +#[derive(Debug)] +struct BlockPrinter; + +impl Observer for BlockPrinter { + fn on_block_received(&self, body: Vec) -> Result<(), Box> { + println!("{}", hex::encode(&body)); + println!("----------"); + + let block = Block::decode_fragment(body.as_slice()).unwrap(); + println!("{:?}", block); + println!("===========\n\n"); + + Ok(()) + } +} + +fn main() { + env_logger::init(); + + let bearer = TcpStream::connect("relays-new.cardano-mainnet.iohk.io:3001").unwrap(); + bearer.set_nodelay(true).unwrap(); + bearer.set_keepalive_ms(Some(30_000u32)).unwrap(); + + let mut muxer = Multiplexer::setup(bearer, &vec![0, 3]).unwrap(); + + let mut hs_channel = muxer.use_channel(0); + let versions = VersionTable::v4_and_above(MAINNET_MAGIC); + let last = run_agent(Client::initial(versions), &mut hs_channel).unwrap(); + + let range = ( + Point( + 3240000, + hex::decode("b7096a881f77ced24bdd285758646c0e059545b54855bd3a2307ece518bd6317") + .unwrap(), + ), + Point( + 4492794, + hex::decode("5c196e7394ace0449ba5a51c919369699b13896e97432894b4f0354dce8670b6") + .unwrap(), + ), + ); + + let mut bf_channel = muxer.use_channel(3); + let bf = BatchClient::initial(range, BlockPrinter {}); + let bf_last = run_agent(bf, &mut bf_channel); + println!("{:?}", bf_last); +} diff --git a/pallas-byron/Cargo.toml b/pallas-byron/Cargo.toml new file mode 100644 index 0000000..abedf66 --- /dev/null +++ b/pallas-byron/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "pallas-byron" +description = "Ledger primitives and cbor codec for the Byron era" +version = "0.4.0" +edition = "2021" +repository = "https://github.com/txpipe/pallas" +homepage = "https://github.com/txpipe/pallas" +documentation = "https://docs.rs/pallas-byron" +license = "Apache-2.0" +readme = "README.md" +authors = [ + "Santiago Carmuega ", + "Lucas Rosa ", + +] + +[dependencies] +minicbor = { version = "0.13", features = ["std"] } +minicbor-derive = "0.8.0" +hex = "0.4.3" +log = "0.4.14" +pallas-crypto = { version = "0.4.0", path = "../pallas-crypto" } diff --git a/pallas-byron/README.md b/pallas-byron/README.md new file mode 100644 index 0000000..b3a50d3 --- /dev/null +++ b/pallas-byron/README.md @@ -0,0 +1,2 @@ +# Pallas Byron + diff --git a/pallas-byron/src/framework.rs b/pallas-byron/src/framework.rs new file mode 100644 index 0000000..497501b --- /dev/null +++ b/pallas-byron/src/framework.rs @@ -0,0 +1,22 @@ +pub type Error = Box; + +pub trait Fragment<'a> +where + Self: Sized, +{ + fn encode_fragment(&self) -> Result, Error>; + fn decode_fragment(bytes: &'a [u8]) -> Result; +} + +impl<'a, T> Fragment<'a> for T +where + T: minicbor::Encode + minicbor::Decode<'a> + Sized, +{ + fn encode_fragment(&self) -> Result, Error> { + minicbor::to_vec(self).map_err(|e| e.into()) + } + + fn decode_fragment(bytes: &'a [u8]) -> Result { + minicbor::decode(bytes).map_err(|e| e.into()) + } +} diff --git a/pallas-byron/src/lib.rs b/pallas-byron/src/lib.rs new file mode 100644 index 0000000..a5acf0f --- /dev/null +++ b/pallas-byron/src/lib.rs @@ -0,0 +1,10 @@ +//! Ledger primitives and cbor codec for the Byron era + +mod framework; +mod model; +mod utils; + +pub use framework::*; +pub use model::*; + +// pub mod crypto; diff --git a/pallas-byron/src/model.rs b/pallas-byron/src/model.rs new file mode 100644 index 0000000..468e836 --- /dev/null +++ b/pallas-byron/src/model.rs @@ -0,0 +1,954 @@ +//! Ledger primitives and cbor codec for the Byron era +//! +//! Handcrafted, idiomatic rust artifacts based on based on the [Byron CDDL](https://github.com/input-output-hk/cardano-ledger/blob/master/eras/byron/cddl-spec/byron.cddl) file in IOHK repo. + +use log::warn; +use minicbor::bytes::ByteVec; +use minicbor_derive::{Decode, Encode}; +use pallas_crypto::hash::Hash; + +use crate::utils::{ + CborWrap, EmptyMap, KeyValuePairs, MaybeIndefArray, OrderPreservingProperties, TagWrap, +}; + +#[derive(Debug, PartialEq, PartialOrd, Eq, Ord)] +pub struct SkipCbor {} + +impl<'b, const N: usize> minicbor::Decode<'b> for SkipCbor { + fn decode(d: &mut minicbor::Decoder<'b>) -> Result { + { + let probe = d.probe(); + warn!("skipped cbor value {}: {:?}", N, probe.datatype()?); + println!("skipped cbor value {}: {:?}", N, probe.datatype()?); + } + + d.skip()?; + Ok(SkipCbor {}) + } +} + +impl minicbor::Encode for SkipCbor { + fn encode( + &self, + _e: &mut minicbor::Encoder, + ) -> Result<(), minicbor::encode::Error> { + todo!() + } +} + +// Basic Cardano Types + +pub type Blake2b256 = Hash<32>; + +pub type TxId = Blake2b256; +pub type BlockId = Blake2b256; +pub type UpdId = Blake2b256; +pub type ByronHash = Blake2b256; + +pub type Blake2b224 = Hash<28>; + +pub type AddressId = Blake2b224; +pub type StakeholderId = Blake2b224; + +pub type EpochId = u64; + +#[derive(Encode, Decode, Debug)] +pub struct SlotId { + #[n(0)] + epoch: EpochId, + + #[n(1)] + slot: u64, +} + +pub type PubKey = ByteVec; +pub type Signature = ByteVec; + +// Attributes + +// quote from the CDDL file: at the moment we do not bother deserialising these, +// since they don't contain anything + +// attributes = {* any => any} +pub type Attributes = EmptyMap; + +// Addresses + +#[derive(Debug)] +pub enum AddrDistr { + Variant0(StakeholderId), + Variant1, +} + +impl<'b> minicbor::Decode<'b> for AddrDistr { + fn decode(d: &mut minicbor::Decoder<'b>) -> Result { + d.array()?; + let variant = d.u32()?; + + match variant { + 0 => Ok(AddrDistr::Variant0(d.decode()?)), + 1 => Ok(AddrDistr::Variant1), + _ => Err(minicbor::decode::Error::Message( + "invalid variant for addrdstr", + )), + } + } +} + +impl minicbor::Encode for AddrDistr { + fn encode( + &self, + e: &mut minicbor::Encoder, + ) -> Result<(), minicbor::encode::Error> { + match self { + AddrDistr::Variant0(x) => { + e.array(2)?; + e.u32(0)?; + e.encode(x)?; + + Ok(()) + } + AddrDistr::Variant1 => { + e.array(1)?; + e.u32(1)?; + + Ok(()) + } + } + } +} + +#[derive(Debug)] +pub enum AddrType { + PubKey, + Script, + Redeem, + Other(u64), +} + +impl<'b> minicbor::Decode<'b> for AddrType { + fn decode(d: &mut minicbor::Decoder<'b>) -> Result { + let variant = d.u64()?; + + match variant { + 0 => Ok(AddrType::PubKey), + 1 => Ok(AddrType::Script), + 2 => Ok(AddrType::Redeem), + x => Ok(AddrType::Other(x)), + } + } +} + +impl minicbor::Encode for AddrType { + fn encode( + &self, + e: &mut minicbor::Encoder, + ) -> Result<(), minicbor::encode::Error> { + match self { + AddrType::PubKey => e.u64(0)?, + AddrType::Script => e.u64(1)?, + AddrType::Redeem => e.u64(2)?, + AddrType::Other(x) => e.u64(*x)?, + }; + + Ok(()) + } +} + +#[derive(Debug)] +pub enum AddrAttrProperty { + AddrDistr(AddrDistr), + Bytes(ByteVec), +} + +impl<'b> minicbor::Decode<'b> for AddrAttrProperty { + fn decode(d: &mut minicbor::Decoder<'b>) -> Result { + let key = d.u8()?; + + match key { + 0 => Ok(AddrAttrProperty::AddrDistr(d.decode()?)), + 1 => Ok(AddrAttrProperty::Bytes(d.decode()?)), + _ => Err(minicbor::decode::Error::Message( + "unknown variant for addrattr property", + )), + } + } +} + +impl minicbor::Encode for AddrAttrProperty { + fn encode( + &self, + e: &mut minicbor::Encoder, + ) -> Result<(), minicbor::encode::Error> { + match self { + AddrAttrProperty::AddrDistr(x) => { + e.u32(0)?; + e.encode(x)?; + + Ok(()) + } + AddrAttrProperty::Bytes(x) => { + e.u32(1)?; + e.encode(x)?; + + Ok(()) + } + } + } +} + +pub type AddrAttr = OrderPreservingProperties; + +// address = [ #6.24(bytes .cbor ([addressid, addrattr, addrtype])), u64 ] +pub type Address = (CborWrap<(AddressId, AddrAttr, AddrType)>, u64); + +// Transactions + +// txout = [address, u64] +pub type TxOut = (Address, u64); + +#[derive(Debug)] +pub enum TxIn { + // [0, #6.24(bytes .cbor ([txid, u32]))] + Variant0(CborWrap<(TxId, u32)>), + + // [u8 .ne 0, encoded-cbor] + Other(u8, ByteVec), +} + +impl<'b> minicbor::Decode<'b> for TxIn { + fn decode(d: &mut minicbor::Decoder<'b>) -> Result { + d.array()?; + + let variant = d.u8()?; + + match variant { + 0 => Ok(TxIn::Variant0(d.decode()?)), + x => Ok(TxIn::Other(x, d.decode()?)), + } + } +} + +impl minicbor::Encode for TxIn { + fn encode( + &self, + e: &mut minicbor::Encoder, + ) -> Result<(), minicbor::encode::Error> { + match self { + TxIn::Variant0(x) => { + e.array(2)?; + e.u8(0)?; + e.encode(x)?; + + Ok(()) + } + TxIn::Other(a, b) => { + e.array(2)?; + e.u8(*a)?; + e.encode(b)?; + + Ok(()) + } + } + } +} + +// tx = [[+ txin], [+ txout], attributes] +pub type Tx = (Vec, Vec, Attributes); + +// txproof = [u32, hash, hash] +pub type TxProof = (u32, ByronHash, ByronHash); + +#[derive(Debug)] +pub enum Twit { + // [0, #6.24(bytes .cbor ([pubkey, signature]))] + Variant0(CborWrap<(PubKey, Signature)>), + + // [1, #6.24(bytes .cbor ([[u16, bytes], [u16, bytes]]))] + Variant1(CborWrap<((u16, ByteVec), (u16, ByteVec))>), + + // [2, #6.24(bytes .cbor ([pubkey, signature]))] + Variant2(CborWrap<(PubKey, Signature)>), + + // [u8 .gt 2, encoded-cbor] + Other(u8, ByteVec), +} + +impl<'b> minicbor::Decode<'b> for Twit { + fn decode(d: &mut minicbor::Decoder<'b>) -> Result { + d.array()?; + + let variant = d.u8()?; + + match variant { + 0 => Ok(Twit::Variant0(d.decode()?)), + 1 => Ok(Twit::Variant1(d.decode()?)), + 2 => Ok(Twit::Variant2(d.decode()?)), + x => Ok(Twit::Other(x, d.decode()?)), + } + } +} + +impl minicbor::Encode for Twit { + fn encode( + &self, + e: &mut minicbor::Encoder, + ) -> Result<(), minicbor::encode::Error> { + match self { + Twit::Variant0(x) => { + e.array(2)?; + e.u8(0)?; + e.encode(x)?; + + Ok(()) + } + Twit::Variant1(x) => { + e.array(2)?; + e.u8(1)?; + e.encode(x)?; + + Ok(()) + } + Twit::Variant2(x) => { + e.array(2)?; + e.u8(2)?; + e.encode(x)?; + + Ok(()) + } + Twit::Other(a, b) => { + e.array(2)?; + e.u8(*a)?; + e.encode(b)?; + + Ok(()) + } + } + } +} + +// Shared Seed Computation + +// cddl note: +// This is encoded using the 'Binary' instance +// for Scrape.PublicKey +pub type VssPubKey = ByteVec; + +// cddl note: +// This is encoded using the 'Binary' instance +// for Scrape.Secret. +pub type VssSec = ByteVec; + +// cddl note: +// This is encoded using the 'Binary' instance +// for Scrape.EncryptedSi. +// TODO work out why this seems to be in a length 1 array +pub type VssEnc = MaybeIndefArray; + +// cddl note: +// This is encoded using the 'Binary' instance +// for Scrape.DecryptedShare +pub type VssDec = ByteVec; + +// cddl note: +// This is encoded using the +// 'Binary' instance for Scrape.Proof +pub type VssProof = (ByteVec, ByteVec, ByteVec, Vec); + +//ssccomm = [pubkey, [{vsspubkey => vssenc},vssproof], signature] +pub type SscComm = ( + PubKey, + (KeyValuePairs, VssProof), + Signature, +); + +//ssccomms = #6.258([* ssccomm]) +pub type SscComms = TagWrap, 258>; + +// sscopens = {stakeholderid => vsssec} +pub type SscOpens = KeyValuePairs; + +// sscshares = {addressid => [addressid, [* vssdec]]} +pub type SscShares = KeyValuePairs)>; + +// CDDL says: ssccert = [vsspubkey, pubkey, epochid, signature] +// this is what seems to work: ssccert = [vsspubkey, epochid, pubkey, signature] +pub type SscCert = (VssPubKey, EpochId, PubKey, Signature); + +// ssccerts = #6.258([* ssccert]) +pub type SscCerts = TagWrap, 258>; + +#[derive(Debug)] +pub enum Ssc { + Variant0(SscComms, SscCerts), + Variant1(SscOpens, SscCerts), + Variant2(SscShares, SscCerts), + Variant3(SscCerts), +} + +impl<'b> minicbor::Decode<'b> for Ssc { + fn decode(d: &mut minicbor::Decoder<'b>) -> Result { + d.array()?; + + let variant = d.u8()?; + + match variant { + 0 => Ok(Ssc::Variant0(d.decode()?, d.decode()?)), + 1 => Ok(Ssc::Variant1(d.decode()?, d.decode()?)), + 2 => Ok(Ssc::Variant2(d.decode()?, d.decode()?)), + 3 => Ok(Ssc::Variant3(d.decode()?)), + _ => Err(minicbor::decode::Error::Message("invalid variant for ssc")), + } + } +} + +impl minicbor::Encode for Ssc { + fn encode( + &self, + e: &mut minicbor::Encoder, + ) -> Result<(), minicbor::encode::Error> { + match self { + Ssc::Variant0(a, b) => { + e.array(3)?; + e.u8(0)?; + e.encode(a)?; + e.encode(b)?; + + Ok(()) + } + Ssc::Variant1(a, b) => { + e.array(3)?; + e.u8(1)?; + e.encode(a)?; + e.encode(b)?; + + Ok(()) + } + Ssc::Variant2(a, b) => { + e.array(3)?; + e.u8(2)?; + e.encode(a)?; + e.encode(b)?; + + Ok(()) + } + Ssc::Variant3(x) => { + e.array(2)?; + e.u8(3)?; + e.encode(x)?; + + Ok(()) + } + } + } +} + +#[derive(Debug)] +pub enum SscProof { + Variant0(ByronHash, ByronHash), + Variant1(ByronHash, ByronHash), + Variant2(ByronHash, ByronHash), + Variant3(ByronHash), +} + +impl<'b> minicbor::Decode<'b> for SscProof { + fn decode(d: &mut minicbor::Decoder<'b>) -> Result { + d.array()?; + + let variant = d.u8()?; + + match variant { + 0 => Ok(SscProof::Variant0(d.decode()?, d.decode()?)), + 1 => Ok(SscProof::Variant1(d.decode()?, d.decode()?)), + 2 => Ok(SscProof::Variant2(d.decode()?, d.decode()?)), + 3 => Ok(SscProof::Variant3(d.decode()?)), + _ => Err(minicbor::decode::Error::Message( + "invalid variant for sscproof", + )), + } + } +} + +impl minicbor::Encode for SscProof { + fn encode( + &self, + e: &mut minicbor::Encoder, + ) -> Result<(), minicbor::encode::Error> { + match self { + SscProof::Variant0(a, b) => { + e.array(3)?; + e.u8(0)?; + e.encode(a)?; + e.encode(b)?; + + Ok(()) + } + SscProof::Variant1(a, b) => { + e.array(3)?; + e.u8(1)?; + e.encode(a)?; + e.encode(b)?; + + Ok(()) + } + SscProof::Variant2(a, b) => { + e.array(3)?; + e.u8(2)?; + e.encode(a)?; + e.encode(b)?; + + Ok(()) + } + SscProof::Variant3(x) => { + e.array(2)?; + e.u8(3)?; + e.encode(x)?; + + Ok(()) + } + } + } +} + +// Delegation + +#[derive(Debug, Encode, Decode)] +pub struct Dlg { + #[n(0)] + epoch: EpochId, + + #[n(1)] + issuer: PubKey, + + #[n(2)] + delegate: PubKey, + + #[n(3)] + certificate: Signature, +} + +pub type DlgSig = (Dlg, Signature); + +#[derive(Debug, Encode, Decode)] +pub struct Lwdlg { + #[n(0)] + epoch_range: (EpochId, EpochId), + + #[n(1)] + issuer: PubKey, + + #[n(2)] + delegate: PubKey, + + #[n(3)] + certificate: Signature, +} + +pub type LwdlgSig = (Lwdlg, Signature); + +// Updates + +pub type BVer = (u16, u16, u8); + +#[derive(Debug)] +pub enum TxFeePol { + //[0, #6.24(bytes .cbor ([bigint, bigint]))] + Variant0(CborWrap<(i64, i64)>), + + // [u8 .gt 0, encoded-cbor] + Other(u8, ByteVec), +} + +impl<'b> minicbor::Decode<'b> for TxFeePol { + fn decode(d: &mut minicbor::Decoder<'b>) -> Result { + d.array()?; + + let variant = d.u8()?; + + match variant { + 0 => Ok(TxFeePol::Variant0(d.decode()?)), + x => Ok(TxFeePol::Other(x, d.decode()?)), + } + } +} + +impl minicbor::Encode for TxFeePol { + fn encode( + &self, + e: &mut minicbor::Encoder, + ) -> Result<(), minicbor::encode::Error> { + match self { + TxFeePol::Variant0(x) => { + e.array(2)?; + e.u8(0)?; + e.encode(x)?; + + Ok(()) + } + TxFeePol::Other(a, b) => { + e.array(2)?; + e.u8(*a)?; + e.encode(b)?; + + Ok(()) + } + } + } +} + +#[derive(Debug, Encode, Decode)] +pub struct BVerMod { + #[n(0)] + script_version: (Option,), + + #[n(1)] + slot_duration: (Option,), + + #[n(2)] + max_block_size: (Option,), + + #[n(3)] + max_header_size: (Option,), + + #[n(4)] + max_tx_size: (Option,), + + #[n(5)] + max_proposal_size: (Option,), + + #[n(6)] + mpc_thd: (Option,), + + #[n(7)] + heavy_del_thd: (Option,), + + #[n(8)] + update_vote_thd: (Option,), + + #[n(9)] + update_proposal_thd: (Option,), + + #[n(10)] + update_implicit: (Option,), + + #[n(11)] + soft_fork_rule: (Option<(u64, u64, u64)>,), + + #[n(12)] + tx_fee_policy: (Option,), + + #[n(13)] + unlock_stake_epoch: (Option,), +} + +pub type UpData = (ByronHash, ByronHash, ByronHash, ByronHash); + +#[derive(Debug, Encode, Decode)] +pub struct UpProp { + #[n(0)] + block_version: Option, + + #[n(1)] + block_version_mod: Option, + + #[n(2)] + software_version: Option<(String, u32)>, + + #[n(3)] + data: Option>, + + #[n(4)] + attributes: Option, + + #[n(5)] + from: Option, + + #[n(6)] + signature: Option, +} + +#[derive(Debug, Encode, Decode)] +pub struct UpVote { + #[n(0)] + voter: PubKey, + + #[n(1)] + proposal_id: UpdId, + + #[n(2)] + vote: bool, + + #[n(3)] + signature: Signature, +} + +#[derive(Debug, Encode, Decode)] +pub struct Up { + #[n(0)] + proposal: Option, + + #[n(1)] + votes: MaybeIndefArray, +} + +// Blocks + +pub type Difficulty = MaybeIndefArray; + +#[derive(Debug)] +pub enum BlockSig { + Signature(Signature), + LwdlgSig(LwdlgSig), + DlgSig(DlgSig), +} + +impl<'b> minicbor::Decode<'b> for BlockSig { + fn decode(d: &mut minicbor::Decoder<'b>) -> Result { + d.array()?; + + let variant = d.u8()?; + + match variant { + 0 => Ok(BlockSig::Signature(d.decode()?)), + 1 => Ok(BlockSig::LwdlgSig(d.decode()?)), + 2 => Ok(BlockSig::DlgSig(d.decode()?)), + _ => Err(minicbor::decode::Error::Message( + "unknown variant for blocksig", + )), + } + } +} + +impl minicbor::Encode for BlockSig { + fn encode( + &self, + e: &mut minicbor::Encoder, + ) -> Result<(), minicbor::encode::Error> { + match self { + BlockSig::Signature(x) => { + e.array(2)?; + e.u8(0)?; + e.encode(x)?; + + Ok(()) + } + BlockSig::LwdlgSig(x) => { + e.array(2)?; + e.u8(1)?; + e.encode(x)?; + + Ok(()) + } + BlockSig::DlgSig(x) => { + e.array(2)?; + e.u8(2)?; + e.encode(x)?; + + Ok(()) + } + } + } +} + +#[derive(Encode, Decode, Debug)] +pub struct BlockCons( + #[n(0)] SlotId, + #[n(1)] PubKey, + #[n(2)] Difficulty, + #[n(3)] BlockSig, +); + +#[derive(Encode, Decode, Debug)] +pub struct BlockHeadEx { + #[n(0)] + block_version: BVer, + + #[n(1)] + software_version: (String, u32), + + #[n(2)] + attributes: Option, + + #[n(3)] + extra_proof: ByronHash, +} + +#[derive(Encode, Decode, Debug)] +pub struct BlockProof { + #[n(0)] + tx_proof: TxProof, + + #[n(1)] + ssc_proof: SscProof, + + #[n(2)] + dlg_proof: ByronHash, + + #[n(3)] + upd_proof: ByronHash, +} + +#[derive(Encode, Decode, Debug)] +pub struct BlockHead { + #[n(0)] + protocol_magic: u32, + + #[n(1)] + prev_block: BlockId, + + #[n(2)] + body_proof: BlockProof, + + #[n(3)] + consensus_data: BlockCons, + + #[n(4)] + extra_data: BlockHeadEx, +} + +// [tx, [* twit]] +pub type TxPayload = (Tx, Vec); + +#[derive(Encode, Decode, Debug)] +pub struct BlockBody { + #[n(0)] + tx_payload: MaybeIndefArray, + + #[n(1)] + ssc_payload: Ssc, + + #[n(2)] + dlg_payload: MaybeIndefArray, + + #[n(3)] + upd_payload: Up, +} + +// Epoch Boundary Blocks + +#[derive(Encode, Decode, Debug)] +pub struct EbbCons { + #[n(0)] + epoch_id: EpochId, + + #[n(1)] + difficulty: Difficulty, +} + +#[derive(Encode, Decode, Debug)] +pub struct EbbHead { + #[n(0)] + protocol_magic: u32, + + #[n(1)] + prev_block: BlockId, + + #[n(2)] + body_proof: ByronHash, + + #[n(3)] + consensus_data: EbbCons, + + #[n(4)] + extra_data: (Attributes,), +} + +#[derive(Encode, Decode, Debug)] +pub struct MainBlock { + #[n(0)] + header: BlockHead, + + #[n(1)] + body: BlockBody, + + #[n(2)] + extra: MaybeIndefArray, +} + +#[derive(Encode, Decode, Debug)] +pub struct EbBlock { + #[n(0)] + header: EbbHead, + + #[n(1)] + body: MaybeIndefArray, + + #[n(2)] + extra: Option, +} + +#[derive(Debug)] +pub enum Block { + MainBlock(MainBlock), + EbBlock(EbBlock), +} + +impl<'b> minicbor::Decode<'b> for Block { + fn decode(d: &mut minicbor::Decoder<'b>) -> Result { + d.array()?; + + let variant = d.u32()?; + + match variant { + 0 => Ok(Block::EbBlock(d.decode()?)), + 1 => Ok(Block::MainBlock(d.decode()?)), + _ => Err(minicbor::decode::Error::Message( + "unknown variant for block", + )), + } + } +} + +impl minicbor::Encode for Block { + fn encode( + &self, + e: &mut minicbor::Encoder, + ) -> Result<(), minicbor::encode::Error> { + match self { + Block::EbBlock(x) => { + e.array(2)?; + e.encode(0)?; + e.encode(x)?; + + Ok(()) + } + Block::MainBlock(x) => { + e.array(2)?; + e.encode(1)?; + e.encode(x)?; + + Ok(()) + } + } + } +} + +#[cfg(test)] +mod tests { + use crate::{Block, Fragment}; + use minicbor::{self, to_vec}; + + #[test] + fn block_isomorphic_decoding_encoding() { + let test_blocks = vec![ + include_str!("test_data/test1.block"), + include_str!("test_data/test2.block"), + include_str!("test_data/test3.block"), + ]; + + for (idx, block_str) in test_blocks.iter().enumerate() { + println!("decoding test block {}", idx + 1); + let bytes = hex::decode(block_str).expect(&format!("bad block file {}", idx)); + + let block = Block::decode_fragment(&bytes[..]) + .expect(&format!("error decoding cbor for file {}", idx)); + + println!("{:?}", block); + + let bytes2 = + to_vec(block).expect(&format!("error encoding block cbor for file {}", idx)); + + assert_eq!(bytes, bytes2); + } + } +} diff --git a/pallas-byron/src/test_data/test1.block b/pallas-byron/src/test_data/test1.block new file mode 100644 index 0000000..8a82df7 --- /dev/null +++ b/pallas-byron/src/test_data/test1.block @@ -0,0 +1 @@ +820183851a2d964a095820a5a5c235200bbe91f16cd3e5dcb67369c25dcfb1279c83d22021f5d960c485e684830058200e5751c026e543b2e8ab2eb06099daa1d1e5df47778f7787faab45cdf12fe3a85820afc0da64183bf2664f3d4eec7238d524ba607faeeab24fc100eb861dba69971b82035820d36a2619a672494604e11bb447cbcf5231e9f2ba25c2169177edc941bd50ad6c5820afc0da64183bf2664f3d4eec7238d524ba607faeeab24fc100eb861dba69971b58204e66280cd94d591072349bec0a3090a53aa945562efb6d08d56e53654b0e4098848218cf19545a584026566e86fc6b9b177c8480e275b2b112b573f6d073f9deea53b8d99c4ed976b335b2b3842f0e380001f090bc923caa9691ed9115e286da9421e2745c7acc87f1811a004485098202828400584026566e86fc6b9b177c8480e275b2b112b573f6d073f9deea53b8d99c4ed976b335b2b3842f0e380001f090bc923caa9691ed9115e286da9421e2745c7acc87f15840f14f712dc600d793052d4842d50cefa4e65884ea6cf83707079eb8ce302efc85dae922d5eb3838d2b91784f04824d26767bfb65bd36a36e74fec46d09d98858d58408ab43e904b06e799c1817c5ced4f3a7bbe15cdbf422dea9d2d5dc2c6105ce2f4d4c71e5d4779f6c44b770a133636109949e1f7786acb5a732bcdea0470fea4065840c15d273e3e0735777e2b38359224803aa4a0fcd1cde37ed1f2f399d7e095b18bdc698a2aff59b2118cbbe45498c7bdc6ccf3a88a806cbd3e73bcf2fc6d7364028483020000826a63617264616e6f2d736c01a058204ba92aa320c60acc9ad7b9a64f2eda55c4d2ec28e604faf186708b4f0c4e8edf849fff8203d90102809fff82809fff81a0 \ No newline at end of file diff --git a/pallas-byron/src/test_data/test2.block b/pallas-byron/src/test_data/test2.block new file mode 100644 index 0000000..2c26a9a --- /dev/null +++ b/pallas-byron/src/test_data/test2.block @@ -0,0 +1 @@ +820183851a2d964a0958208b5add21497fa1749292318f1ac96a252ee58ac9bf60d321d1273f5135e72f2f8483025820eba8b3720ab760171e4900b88cc0379bf6c78c4dac55d1dc3d646b00c13515405820187817e2ba9ff807dd905e3a4e0d061630979da49071495b2b38201f7ffbc97b8300582025777aca9e4a73d48fc73b4f961d345b06d4a6f349cb7916570d35537d53479f5820d36a2619a672494604e11bb447cbcf5231e9f2ba25c2169177edc941bd50ad6c5820afc0da64183bf2664f3d4eec7238d524ba607faeeab24fc100eb861dba69971b58204e66280cd94d591072349bec0a3090a53aa945562efb6d08d56e53654b0e40988482189619056558400bdb1f5ef3d994037593f2266255f134a564658bb2df814b3b9cefb96da34fa9c888591c85b770fd36726d5f3d991c668828affc7bbe0872fd699136e664d9d8811a00316fa2820282840058400bdb1f5ef3d994037593f2266255f134a564658bb2df814b3b9cefb96da34fa9c888591c85b770fd36726d5f3d991c668828affc7bbe0872fd699136e664d9d858405fddeedade2714d6db2f9e1104743d2d8d818ecddc306e176108db14caadd441b457d5840c60f8840b99c8f78c290ae229d4f8431e678ba7a545c35607b94ddb5840552741f728196e62f218047b944b24ce4d374300d04b9b281426f55aa000d53ded66989ad5ea0908e6ff6492001ff18ece6c7040a934060759e9ae09863bf20358400e663202ff860e5a1cc84f32ad8ceffb0adb6cf476b07829e922312e038ba23573728e02f3775f6714b3b731f8b8084d92b3f38f51a41ba859e7e700feeeab038483000200826a63617264616e6f2d736c01a058204ba92aa320c60acc9ad7b9a64f2eda55c4d2ec28e604faf186708b4f0c4e8edf849f82839f8200d8185824825820da832fb5ef57df5b91817e9a7448d26e92552afb34f8ee5adb491b24bbe990d50eff9f8282d818584283581cdac5d9464c2140aeb0e3b6d69f0657e61f51e0c259fe19681ed268e8a101581e581c2b5a44277e3543c08eae5d9d9d1146f43ba009fea6e285334f2549be001ae69c4d201b0000000172a84e408282d818584283581c2b8e5e0cb6495ec275872d1340b0581613b04a49a3c6f2f760ecaf95a101581e581cca3e553c9c63c5b66689e943ce7dad7d560ae84d7c2eaf21611c024c001ad27c159a1b00000003355d95efffa0818200d8185885825840888cdf85991d85f2023423ba4c80d41570ebf1fc878c9f5731df1d20c64aecf3e8aa2bbafc9beba8ef33acb4d7e199b445229085718fba83b7f86ab6a3bcf782584063e34cf5fa6d8c0288630437fa5e151d93907e826e66ba273145e3ee712930b6f446ff81cb91d7f0cb4ceccd0466ba9ab14448d7eab9fc480a122324bd80170e82839f8200d8185824825820e059de2179400cd7e81ddb6683c0136c9d68119ff3a27a472ad2d98e2f1fbc9c038200d8185824825820adeb5745e6dba2c05a98f0ad9162b947f1484e998b8b3335f98213e0c67f426e008200d8185824825820f0fb258a6e741a02ae91b8dc7fe340b9e5b601a6048bf2a0c205f9cc6f51768d018200d8185824825820c2e4e1f1d8217724b76d979166b16cb0cf5cd6506f70f48c618a085b10460c44028200d8185824825820aaca2f41f4a17fe464481c69f1220a7bfd93b1a6854f52006094271204e7df7c008200d818582482582089185f2daf9ea3bdfdb5d1fef7eced7e890cb89b8821275c0bf0973be08c4ee901ff9f8282d818582183581cb8340f839cc48449e2ee6085bf1ab6f152fb20d2e071f429085bbe13a0001a5bf6bee11b00000011972734888282d818584283581c7ed455da1e6e026b35204699d4fa39b3bc9dc47ed27d36d2b47ec3eca101581e581c082dcccc3e51655ddc77c01953f7b64c7cf6aca758c686d9c51a32fe001ab116b6251b00000005bd6bbf308282d818582183581cc2a3e4bb135f14d44b5659fd3cac5f950e7515f8ce243f264dfe1befa0001ad2e633181a84dbd7408282d818584283581ca492c5ad6316f8e7e84177673acf32f53ec25b2baee5a52ff50964f9a101581e581c1e9a0361bdc37dd4b96893a363943b3fa8e2e89b8761c3f0ff7568bc001a600064881b0000008cc4df8c118282d818584283581cf73d2b9c66ae7c8d479151c7e43e2fae59fba02e7108ac4d643a4909a101581e581c1e9a0361bdc37de543987ea3cadcec1c18fa6cf784794fec37146f34001a7103195d1b00000004f92e889b8282d818584283581cbd765f3097754bda870653533855012a82a22cedf508afc97a05f912a101581e581c1e9a0361bdc37d9d5470dca3256d2b36da565a1be6fd6c319321d8c9001a743764aa1b00000011b52f1056ffa0868200d8185885825840b43ac73cca84eb7baa30f0ad3c4d427ea43e8d8831eb8de34ae9994bc6f00313d81f35d51d695e30f88b267572f96f056ddff1b39acf011b8227c14df833371258408a0c1d59791fd04e6329adcf532fe2749a8da282ca4bdba9942a6acea07b3e4d9d30cc0dcd9a602e151694a417d069fdaa8a3bbbb2e1724b9c9671850498430a8200d81858858258408a3c3f38a7a598bd4b62fa4b56f92b96be3d1b7f1df1a713a3cb4978f835396f935c09b245ab312efda634d3ac4af1f843fb0577725a27579c9fd665d14c24fa5840a27f622daaf26f1047845723f20a13247ce866005f5263397082657a80d60095d81856e127e27e2fe36391c1631170ac630d3e340aed71c9cb8c323eec148a058200d81858858258409322bcf07526e852e5a0830d7c24d4a2a0dcf8a894f176e0a8fe3f742fa485906206283cdab4fdba8280fdc51e818ccdd1e9e6cd530381b18152f38dc598b6cc5840d0edd562447c433cc7870913fb8d90a2169d4a1f9aae8695eedf07e0eff67213fd7c339f03860ecd83ceed1a324aa10067671e4ee1d3f762f355678dd4d0ff008200d8185885825840ac4a10ea86a183dfc8ab8f9ccf7a61eb9b11e471b53321725bafe403bd101abdfac85680f85d72f5462b4abf234759f3d833f7fe5372884f8a84475f4989edbd5840181183c6c147ba34c9f39269ab36949d0b1ec0a7dd950afa1ea2dd27b2d691412948de68f93471935905edf07934a7965926f52f8162841299a55246a5a97d0b8200d818588582584004bff1f0caaf06b7942f52218c1b8a1583f425014ec78ff2c4030f90dfe191ec861f537833d47898185d34746877e9d9d31cb827c145729d1200c1463625b22058401ffcb034120b9b20d84af9baec86bb0b2593d804e8b544d7dcde662b0e2a3da4b4afda77007a4133a9e0ea5306a92edff4fd82a14db371407e6a67bbc644250b8200d81858858258408a3c3f38a7a598bd4b62fa4b56f92b96be3d1b7f1df1a713a3cb4978f835396f935c09b245ab312efda634d3ac4af1f843fb0577725a27579c9fd665d14c24fa5840a27f622daaf26f1047845723f20a13247ce866005f5263397082657a80d60095d81856e127e27e2fe36391c1631170ac630d3e340aed71c9cb8c323eec148a05ff8300d9010280d90102809fff82809fff81a0 \ No newline at end of file diff --git a/pallas-byron/src/test_data/test3.block b/pallas-byron/src/test_data/test3.block new file mode 100644 index 0000000..cf14fdc --- /dev/null +++ b/pallas-byron/src/test_data/test3.block @@ -0,0 +1 @@ +820183851a2d964a095820b7096a881f77ced24bdd285758646c0e059545b54855bd3a2307ece518bd631784830058200e5751c026e543b2e8ab2eb06099daa1d1e5df47778f7787faab45cdf12fe3a85820afc0da64183bf2664f3d4eec7238d524ba607faeeab24fc100eb861dba69971b83005820b94cc078f6935a13e0977cdc8404c30b4b4d1d72d469dd344cc60d23253331a95820dfffce156e909a4ec6a71e7424ea80054053fa7146af31c8f8c09345810056025820afc0da64183bf2664f3d4eec7238d524ba607faeeab24fc100eb861dba69971b58204e66280cd94d591072349bec0a3090a53aa945562efb6d08d56e53654b0e409884821896015840993a8f056d2d3e50b0ac60139f10df8f8123d5f7c4817b40dac2b5dd8aa94a82e8536832e6312ddfc0787d7b5310c815655ada4fdbcf6b12297d4458eccc2dfb811a00316a3e82028284005840993a8f056d2d3e50b0ac60139f10df8f8123d5f7c4817b40dac2b5dd8aa94a82e8536832e6312ddfc0787d7b5310c815655ada4fdbcf6b12297d4458eccc2dfb584089c29f8c4af27b7accbe589747820134ebbaa1caf3ce949270a3d0c7dcfd541b1def326d2ef0db780341c9e261f04890cdeef1f9c99f6d90b8edca7d3cfc09885840496b29b5c57e8ac7cffc6e8b5e40b3d260e407ad4d09792decb0a22d54da7f8828265688a18aa1a5c76d9e7477a5f4a650501409fdcd3855b300fd2e2bc3c60558405ea655c50be3b49e1b3009aba182743d99357b18980de86d27eeb59ca67b9223abb6fc7c9554c6f9e5b87668ea661aad91c657c7ef50d0a54c5ef48e0779c9058483000200826a63617264616e6f2d736c01a058204ba92aa320c60acc9ad7b9a64f2eda55c4d2ec28e604faf186708b4f0c4e8edf849fff8300d901028183584089c29f8c4af27b7accbe589747820134ebbaa1caf3ce949270a3d0c7dcfd541b1def326d2ef0db780341c9e261f04890cdeef1f9c99f6d90b8edca7d3cfc098882a7582358210235779046a8936523c30f2791277825fd0cd830b2ed347d3237e5ce975ee9bbc69f582358210246007f6cb263d1dfb9094f1c7cf05a974bf77a9641cc15105c7c7a97d77cb90bff58235821024286635894aeffa47be020b4b9e87b717991d2c6ac993b82a9ff17d01b40438a9f5823582102b2247c6cb89bda0e241a591c742ef759f8e13f2ccc263113d3537ca8b207383eff58235821024b5d4f5b1c9b507298da11e60609bfcd8691d98b2ab07f9fdee3cea76c9dcff19f5823582102a13c04fa6c9b167e546ce69328a0eb5360db3e094f6e84386b9e82e42c2f4b9fff5823582102828c3e8492be0718cdc7480862b6a9fa89d4b11a1c39b404573bc9b1d8faf2fc9f5823582102b7981ab85a61f2f3cdef254338ef83f3260cc2809246d45ac94c13862064f46bff5823582102b35fa583c2ec9b5e45c5f63c6e11b246ec1ef3239a487d30c126a32d161e5bfb9f5823582102e26b1ad6bf7791f5607b82e85dfd785eebe7b15117da4a4a08b62cba5638ce9dff582358210371adbb07189b563428035409696c788001363140749477dbea32778a8ad70fbb9f582358210363f3a6153861e13c2e65789f3a25be57481909b1ed7e57a0d3cf83ea489889f4ff5823582103ea4e80aea3a7850b5f3518341520b15a602da1c65cc95df701d8cca2d8c6c3129f58235821038542dd83395d88b9a76ffb18b9ebd21e44cbf650b23414ba56fe5e7b91d9ff03ff8458210207b1ebf9e9141a93f99ab404a2361ad97edecacf2a675cea0ec8f485aeee3c235840a624341380963a5a368b25d51325a4f4901c135d9c6887607b41f5de865e1b0d178e628dabea21f2e6031c8266334ce10da5abd0df2563932677e2bf99795e3f5901085347fe75a2debb3f4a2735b588c5752bcbd1d0950f8fe8c88f448545c92613e40000000000000007ec51138d37a0e2f8ab021e2717605597a8c48c760d4bcee04068480e7958f5c362189caccb74b0559a373586f4407bb58ac75969fffc29a94d4d894cdc65ee7fa8192e8844f994275cb5eedb99315fb50d77a349d6dcb383cb765ff8913acd205393f808beb8163b56008bad21f837a7515c0ebb70519e6a73013a87413f8222b46a6ed246c8f866bfeb90bdb41a6e5b7f99186195de024da7562c5558445ab2fd16bef23dc77173dfc0bc1fa63567eafb4650885da34ab898cf2cb83425a97e8955e36512c006f706970f13e127eb23b044c3a76f177c05bddd4a91f56e155a9f5821022a1da8f0db01c80c7e6463318c7c3ff5739fc09b0875377e8cea1ed251f424de5821035b082b299cab33f3b69a32d06006d398f47a7355147e6a1425e58274438fa3f0582103b7c5550c9eba4e53c013e3b625579d524612c6962a88a521eeaff7c08e1a1fe85821025383f0bb890bee8b2a025bbf73d6e4f33c6e0b2b9dc421241dfdefda525ff9d158210364c8d3de39e378483426dce80d33e8a332f1a314027672183017f840554c99e1582103e1d0b4bd3187c9285d347fb662d497481f4a131ac2af0e5bb01c374254d80ec05821036939fa54b398683207f63e7d99fbdf2d93a503d8d9f3ded68b72bde6d7860fb5ff584063859acf38f050d6b525b690174c9e115944513b2a926e143f2f7d4990690bb690434ed9a8d050e4e3f3d7dfff898fd37bbfb84fb7bbfb82335bfea2315b4109d901028784582358210235779046a8936523c30f2791277825fd0cd830b2ed347d3237e5ce975ee9bbc6189b5840166612548d234cd54181b0fde40a879638df776c260b112149e90e569734d5101a7721e386f3c65c32a7f5bbcc2a983ec29ebe97db241b840c1ed384e2d0b107584061261a95b7613ee6bf2067dad77b70349729b0c50d57bc1cf30de0db4a1e73a885d0054af7c23fc6c37919dba41c602a57e2d0f9329a7954b867338d6fb2c9458458235821024286635894aeffa47be020b4b9e87b717991d2c6ac993b82a9ff17d01b40438a189b584078c3bbf09c16b079f16704e4fbd4aeffa3bf28acc35a3ec033b58bac7ef5374538623a4243b737dff9ca12b45627c1df4961ed3bd7eedcc5c28a63b4e039f309584089c29f8c4af27b7accbe589747820134ebbaa1caf3ce949270a3d0c7dcfd541b1def326d2ef0db780341c9e261f04890cdeef1f9c99f6d90b8edca7d3cfc09888458235821024b5d4f5b1c9b507298da11e60609bfcd8691d98b2ab07f9fdee3cea76c9dcff1189b5840cbf9f783d07e3eb95b46f7dfcfc48bade6e796dda2d6cd0f74f7dc16238bee15d6d7fc9302983b866dc9a499984f252dd5e248a57aaee18e5a1e337a1a56cd0258405fddeedade2714d6db2f9e1104743d2d8d818ecddc306e176108db14caadd441b457d5840c60f8840b99c8f78c290ae229d4f8431e678ba7a545c35607b94ddb845823582102828c3e8492be0718cdc7480862b6a9fa89d4b11a1c39b404573bc9b1d8faf2fc189b5840cf643b8b77960cb9b9c4c001aea9935f465348938e5efc8642c22950ae7478cfdb126b34d80473558477a177e9bb3edf3ed956eef8446a9ea5e5ec40e2e67b0258409180d818e69cd997e34663c418a648c076f2e19cd4194e486e159d8580bc6cda81344440c6ad0e5306fd035bef9281da5d8fbd38f59f588f7081016ee61113d2845823582102b35fa583c2ec9b5e45c5f63c6e11b246ec1ef3239a487d30c126a32d161e5bfb189b58405c2234afdf3053cadf5a6f450791ad99fffe665b85b2213ad365f5335f504a592f345f9cda0e817a7a14b792569803a696d0217499b0ef0117eed6a591f8e80a5840f14f712dc600d793052d4842d50cefa4e65884ea6cf83707079eb8ce302efc85dae922d5eb3838d2b91784f04824d26767bfb65bd36a36e74fec46d09d98858d84582358210371adbb07189b563428035409696c788001363140749477dbea32778a8ad70fbb189b5840a588ef9590e91380aa849bd5ded38a92153f1322628ae8b5b7a51bd2a02bb7c97254e57f035d7f769a13ebb35f5ec502529aaf770b974e5355a4ccb6bfbdf30958408b53207629f9a30e4b2015044f337c01735abe67243c19470c9dae8c7b73279809887b9f420d6f70ee1e9d50c16052878e544b65cb5d44bd691756b284cd829b845823582103ea4e80aea3a7850b5f3518341520b15a602da1c65cc95df701d8cca2d8c6c312189b58403c12b609eefc18d7c171083db46bfb54d9238ceb69bbb0e8ccf4ff21e88a20642e95e536efdd7b181e8e197cb0f434afc7bb3b76f6244a07b830ff5cf4aaf1035840e8c03a03c0b2ddbea4195caf39f41e669f7d251ecf221fbb2f275c0a5d7e05d190dcc246f56c8e33ac0037066e2f664ddaa985ea5284082643308dde4f5bfedf9fff82809fff81a0 \ No newline at end of file diff --git a/pallas-byron/src/utils.rs b/pallas-byron/src/utils.rs new file mode 100644 index 0000000..40e6f9c --- /dev/null +++ b/pallas-byron/src/utils.rs @@ -0,0 +1,285 @@ +use std::ops::Deref; + +use minicbor::{data::Tag, Decode, Encode}; + +/// Custom collection to ensure ordered pairs of values +/// +/// Since the ordering of the entries requires a particular order to maintain +/// canonicalization for isomorphic decoding / encoding operators, we use a Vec +/// as the underlaying struct for storage of the items (as opposed to a BTreeMap +/// or HashMap). +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub enum KeyValuePairs { + Def(Vec<(K, V)>), + Indef(Vec<(K, V)>), +} + +impl Deref for KeyValuePairs { + type Target = Vec<(K, V)>; + + fn deref(&self) -> &Self::Target { + match self { + KeyValuePairs::Def(x) => x, + KeyValuePairs::Indef(x) => x, + } + } +} + +impl<'b, K, V> minicbor::decode::Decode<'b> for KeyValuePairs +where + K: Encode + Decode<'b>, + V: Encode + Decode<'b>, +{ + fn decode(d: &mut minicbor::Decoder<'b>) -> Result { + let datatype = d.datatype()?; + + let items: Result, _> = d.map_iter::()?.collect(); + let items = items?; + + match datatype { + minicbor::data::Type::Map => Ok(KeyValuePairs::Def(items)), + minicbor::data::Type::MapIndef => Ok(KeyValuePairs::Indef(items)), + _ => Err(minicbor::decode::Error::Message( + "invalid data type for keyvaluepairs", + )), + } + } +} + +impl minicbor::encode::Encode for KeyValuePairs +where + K: Encode, + V: Encode, +{ + fn encode( + &self, + e: &mut minicbor::Encoder, + ) -> Result<(), minicbor::encode::Error> { + match self { + KeyValuePairs::Def(x) => { + e.map(x.len() as u64)?; + + for (k, v) in x.iter() { + k.encode(e)?; + v.encode(e)?; + } + } + KeyValuePairs::Indef(x) => { + e.begin_map()?; + + for (k, v) in x.iter() { + k.encode(e)?; + v.encode(e)?; + } + + e.end()?; + } + } + + Ok(()) + } +} + +/// A struct that maintains a reference to whether a cbor array was indef or not +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] +pub enum MaybeIndefArray { + Def(Vec), + Indef(Vec), +} + +impl Deref for MaybeIndefArray { + type Target = Vec; + + fn deref(&self) -> &Self::Target { + match self { + MaybeIndefArray::Def(x) => x, + MaybeIndefArray::Indef(x) => x, + } + } +} + +impl<'b, A> minicbor::decode::Decode<'b> for MaybeIndefArray +where + A: minicbor::decode::Decode<'b>, +{ + fn decode(d: &mut minicbor::Decoder<'b>) -> Result { + let datatype = d.datatype()?; + + match datatype { + minicbor::data::Type::Array => Ok(Self::Def(d.decode()?)), + minicbor::data::Type::ArrayIndef => Ok(Self::Indef(d.decode()?)), + _ => Err(minicbor::decode::Error::Message( + "unknown data type of maybe indef array", + )), + } + } +} + +impl minicbor::encode::Encode for MaybeIndefArray +where + A: minicbor::encode::Encode, +{ + fn encode( + &self, + e: &mut minicbor::Encoder, + ) -> Result<(), minicbor::encode::Error> { + match self { + MaybeIndefArray::Def(x) => { + e.encode(x)?; + } + MaybeIndefArray::Indef(x) if x.is_empty() => { + e.encode(x)?; + } + MaybeIndefArray::Indef(x) => { + e.begin_array()?; + + for v in x.iter() { + e.encode(v)?; + } + + e.end()?; + } + }; + + Ok(()) + } +} + +/// Order-preserving set of attributes +/// +/// There's no guarantee that the entries on a Cardano cbor entity that uses +/// maps for its representation will follow the canonical order specified by the +/// standard. To implement an isomorphic codec, we need a way of preserving the +/// original order in which the entries were encoded. To acomplish this, we +/// transform key-value structures into an orderer vec of `properties`, where +/// each entry represents a a cbor-encodable variant of an attribute of the +/// struct. +#[derive(Debug, PartialEq)] +pub struct OrderPreservingProperties

(Vec

); + +impl

Deref for OrderPreservingProperties

{ + type Target = Vec

; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'b, P> minicbor::decode::Decode<'b> for OrderPreservingProperties

+where + P: Decode<'b>, +{ + fn decode(d: &mut minicbor::Decoder<'b>) -> Result { + let len = d.map()?.unwrap_or_default(); + + let components: Result<_, _> = (0..len).map(|_| d.decode()).collect(); + + Ok(Self(components?)) + } +} + +impl

minicbor::encode::Encode for OrderPreservingProperties

+where + P: Encode, +{ + fn encode( + &self, + e: &mut minicbor::Encoder, + ) -> Result<(), minicbor::encode::Error> { + e.map(self.0.len() as u64)?; + for component in &self.0 { + e.encode(component)?; + } + + Ok(()) + } +} + +/// Wraps a struct so that it is encoded/decoded as a cbor bytes +#[derive(Debug)] +pub struct CborWrap(T); + +impl<'b, T> minicbor::Decode<'b> for CborWrap +where + T: minicbor::Decode<'b>, +{ + fn decode(d: &mut minicbor::Decoder<'b>) -> Result { + d.tag()?; + let cbor = d.bytes()?; + let wrapped = minicbor::decode(cbor)?; + + Ok(CborWrap(wrapped)) + } +} + +impl minicbor::Encode for CborWrap +where + T: minicbor::Encode, +{ + fn encode( + &self, + e: &mut minicbor::Encoder, + ) -> Result<(), minicbor::encode::Error> { + let buf = minicbor::to_vec(&self.0).map_err(|_| { + minicbor::encode::Error::Message("error encoding cbor-wrapped structure") + })?; + + e.tag(Tag::Cbor)?; + e.bytes(&buf)?; + + Ok(()) + } +} + +#[derive(Debug)] +pub struct TagWrap(I); + +impl<'b, I, const T: u64> minicbor::Decode<'b> for TagWrap +where + I: minicbor::Decode<'b>, +{ + fn decode(d: &mut minicbor::Decoder<'b>) -> Result { + d.tag()?; + + Ok(TagWrap(d.decode()?)) + } +} + +impl minicbor::Encode for TagWrap +where + I: minicbor::Encode, +{ + fn encode( + &self, + e: &mut minicbor::Encoder, + ) -> Result<(), minicbor::encode::Error> { + e.tag(Tag::Unassigned(T))?; + e.encode(&self.0)?; + + Ok(()) + } +} + +/// An empty map +/// +/// don't ask me why, that's what the CDDL asks for. +#[derive(Debug)] +pub struct EmptyMap; + +impl<'b> minicbor::decode::Decode<'b> for EmptyMap { + fn decode(d: &mut minicbor::Decoder<'b>) -> Result { + d.skip()?; + Ok(EmptyMap) + } +} + +impl minicbor::encode::Encode for EmptyMap { + fn encode( + &self, + e: &mut minicbor::Encoder, + ) -> Result<(), minicbor::encode::Error> { + e.map(0)?; + + Ok(()) + } +} diff --git a/pallas/Cargo.toml b/pallas/Cargo.toml index cf70cb2..9839222 100644 --- a/pallas/Cargo.toml +++ b/pallas/Cargo.toml @@ -20,5 +20,6 @@ pallas-chainsync = { version = "0.4.0", path = "../pallas-chainsync/" } pallas-blockfetch = { version = "0.4.0", path = "../pallas-blockfetch/" } pallas-localstate = { version = "0.4.0", path = "../pallas-localstate/" } pallas-txsubmission = { version = "0.4.0", path = "../pallas-txsubmission/" } +pallas-byron = { version = "0.4.0", path = "../pallas-byron/" } pallas-alonzo = { version = "0.4.0", path = "../pallas-alonzo/" } pallas-crypto = { version = "0.4.0", path = "../pallas-crypto/" } diff --git a/pallas/src/ledger/mod.rs b/pallas/src/ledger/mod.rs index 99d4c1b..a18a550 100644 --- a/pallas/src/ledger/mod.rs +++ b/pallas/src/ledger/mod.rs @@ -1,4 +1,7 @@ //! Ledger primitives and cbor codecs for different Cardano eras +#[doc(inline)] +pub use pallas_byron as byron; + #[doc(inline)] pub use pallas_alonzo as alonzo;