diff --git a/examples/block-decode/src/main.rs b/examples/block-decode/src/main.rs index a14fecd..7f84664 100644 --- a/examples/block-decode/src/main.rs +++ b/examples/block-decode/src/main.rs @@ -16,7 +16,7 @@ fn main() { println!("{} {}", block.slot(), block.hash()); - for tx in block.tx_iter() { + for tx in &block.txs() { println!("{:?}", tx); } } diff --git a/pallas-codec/src/utils.rs b/pallas-codec/src/utils.rs index e63d588..c487bb7 100644 --- a/pallas-codec/src/utils.rs +++ b/pallas-codec/src/utils.rs @@ -273,7 +273,7 @@ impl Deref for CborWrap { } } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct TagWrap(I); impl TagWrap { @@ -342,7 +342,7 @@ impl minicbor::encode::Encode for EmptyMap { /// A common pattern seen in the CDDL is to represent optional values as an /// array containing zero or more items. This structure reflects that pattern /// while providing semantic meaning. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct ZeroOrOneArray(Option); impl Deref for ZeroOrOneArray { diff --git a/pallas-primitives/src/alonzo/address.rs b/pallas-primitives/src/alonzo/address.rs index 1ffbdce..65ab89e 100644 --- a/pallas-primitives/src/alonzo/address.rs +++ b/pallas-primitives/src/alonzo/address.rs @@ -17,7 +17,7 @@ impl TransactionOutput { mod tests { use pallas_codec::minicbor; - use crate::alonzo::{Block, TransactionBodyComponent}; + use crate::alonzo::Block; type BlockWrapper = (u16, Block); @@ -49,18 +49,14 @@ mod tests { assert!(block.transaction_bodies.len() > 0); for tx in block.transaction_bodies.iter() { - for component in tx.iter() { - if let TransactionBodyComponent::Outputs(outputs) = component { - for output in outputs.iter() { - let addr_str = output.to_bech32_address("addr_test").unwrap(); + for output in tx.outputs.iter() { + let addr_str = output.to_bech32_address("addr_test").unwrap(); - assert!( - KNOWN_ADDRESSES.contains(&addr_str.as_str()), - "address {} not in known list", - addr_str - ); - } - } + assert!( + KNOWN_ADDRESSES.contains(&addr_str.as_str()), + "address {} not in known list", + addr_str + ); } } } diff --git a/pallas-primitives/src/alonzo/model.rs b/pallas-primitives/src/alonzo/model.rs index 8bc36f0..dfd4e99 100644 --- a/pallas-primitives/src/alonzo/model.rs +++ b/pallas-primitives/src/alonzo/model.rs @@ -4,7 +4,6 @@ use pallas_codec::minicbor::{bytes::ByteVec, data::Int, data::Tag, Decode, Encode}; use pallas_crypto::hash::Hash; -use std::ops::Deref; use pallas_codec::utils::{AnyUInt, KeepRaw, KeyValuePairs, MaybeIndefArray}; @@ -699,155 +698,52 @@ pub struct Update { pub epoch: Epoch, } -#[derive(Debug, PartialEq, Clone)] -pub enum TransactionBodyComponent { - Inputs(MaybeIndefArray), - Outputs(MaybeIndefArray), - Fee(u64), - Ttl(u64), - Certificates(MaybeIndefArray), - Withdrawals(KeyValuePairs), - Update(Update), - AuxiliaryDataHash(ByteVec), - ValidityIntervalStart(u64), - Mint(Multiasset), - ScriptDataHash(Hash<32>), - Collateral(MaybeIndefArray), - RequiredSigners(MaybeIndefArray), - NetworkId(NetworkId), -} - -impl<'b, C> minicbor::decode::Decode<'b, C> for TransactionBodyComponent { - fn decode(d: &mut minicbor::Decoder<'b>, ctx: &mut C) -> Result { - let key: u32 = d.decode_with(ctx)?; - - match key { - 0 => Ok(Self::Inputs(d.decode_with(ctx)?)), - 1 => Ok(Self::Outputs(d.decode_with(ctx)?)), - 2 => Ok(Self::Fee(d.decode_with(ctx)?)), - 3 => Ok(Self::Ttl(d.decode_with(ctx)?)), - 4 => Ok(Self::Certificates(d.decode_with(ctx)?)), - 5 => Ok(Self::Withdrawals(d.decode_with(ctx)?)), - 6 => Ok(Self::Update(d.decode_with(ctx)?)), - 7 => Ok(Self::AuxiliaryDataHash(d.decode_with(ctx)?)), - 8 => Ok(Self::ValidityIntervalStart(d.decode_with(ctx)?)), - 9 => Ok(Self::Mint(d.decode_with(ctx)?)), - 11 => Ok(Self::ScriptDataHash(d.decode_with(ctx)?)), - 13 => Ok(Self::Collateral(d.decode_with(ctx)?)), - 14 => Ok(Self::RequiredSigners(d.decode_with(ctx)?)), - 15 => Ok(Self::NetworkId(d.decode_with(ctx)?)), - _ => Err(minicbor::decode::Error::message( - "invalid map key for transaction body component", - )), - } - } -} - -impl minicbor::encode::Encode for TransactionBodyComponent { - fn encode( - &self, - e: &mut minicbor::Encoder, - ctx: &mut C, - ) -> Result<(), minicbor::encode::Error> { - match self { - TransactionBodyComponent::Inputs(x) => { - e.encode_with(0, ctx)?; - e.encode_with(x, ctx)?; - } - TransactionBodyComponent::Outputs(x) => { - e.encode_with(1, ctx)?; - e.encode_with(x, ctx)?; - } - TransactionBodyComponent::Fee(x) => { - e.encode_with(2, ctx)?; - e.encode_with(x, ctx)?; - } - TransactionBodyComponent::Ttl(x) => { - e.encode_with(3, ctx)?; - e.encode_with(x, ctx)?; - } - TransactionBodyComponent::Certificates(x) => { - e.encode_with(4, ctx)?; - e.encode_with(x, ctx)?; - } - TransactionBodyComponent::Withdrawals(x) => { - e.encode_with(5, ctx)?; - e.encode_with(x, ctx)?; - } - TransactionBodyComponent::Update(x) => { - e.encode_with(6, ctx)?; - e.encode_with(x, ctx)?; - } - TransactionBodyComponent::AuxiliaryDataHash(x) => { - e.encode_with(7, ctx)?; - e.encode_with(x, ctx)?; - } - TransactionBodyComponent::ValidityIntervalStart(x) => { - e.encode_with(8, ctx)?; - e.encode_with(x, ctx)?; - } - TransactionBodyComponent::Mint(x) => { - e.encode_with(9, ctx)?; - e.encode_with(x, ctx)?; - } - TransactionBodyComponent::ScriptDataHash(x) => { - e.encode_with(11, ctx)?; - e.encode_with(x, ctx)?; - } - TransactionBodyComponent::Collateral(x) => { - e.encode_with(13, ctx)?; - e.encode_with(x, ctx)?; - } - TransactionBodyComponent::RequiredSigners(x) => { - e.encode_with(14, ctx)?; - e.encode_with(x, ctx)?; - } - TransactionBodyComponent::NetworkId(x) => { - e.encode_with(15, ctx)?; - e.encode_with(x, ctx)?; - } - } - - Ok(()) - } -} - // Can't derive encode for TransactionBody because it seems to require a very // particular order for each key in the map -#[derive(Debug, PartialEq, Clone)] -pub struct TransactionBody(Vec); +#[derive(Encode, Decode, Debug, PartialEq, Clone)] +#[cbor(map)] +pub struct TransactionBody { + #[n(0)] + pub inputs: MaybeIndefArray, -impl Deref for TransactionBody { - type Target = Vec; + #[n(1)] + pub outputs: MaybeIndefArray, - fn deref(&self) -> &Self::Target { - &self.0 - } -} + #[n(2)] + pub fee: u64, -impl<'b, C> minicbor::decode::Decode<'b, C> for TransactionBody { - fn decode(d: &mut minicbor::Decoder<'b>, ctx: &mut C) -> Result { - let len = d.map()?.unwrap_or_default(); + #[n(3)] + pub ttl: Option, - let components: Result<_, _> = (0..len).map(|_| d.decode_with(ctx)).collect(); + #[n(4)] + pub certificates: Option>, - Ok(Self(components?)) - } -} + #[n(5)] + pub withdrawals: Option>, -impl minicbor::encode::Encode for TransactionBody { - fn encode( - &self, - e: &mut minicbor::Encoder, - ctx: &mut C, - ) -> Result<(), minicbor::encode::Error> { - e.map(self.0.len() as u64)?; - for component in &self.0 { - e.encode_with(component, ctx)?; - } + #[n(6)] + pub update: Option, - Ok(()) - } + #[n(7)] + pub auxiliary_data_hash: Option, + + #[n(8)] + pub validity_interval_start: Option, + + #[n(9)] + pub mint: Option>, + + #[n(11)] + pub script_data_hash: Option>, + + #[n(13)] + pub collateral: Option>, + + #[n(14)] + pub required_signers: Option>, + + #[n(15)] + pub network_id: Option, } #[derive(Encode, Decode, Debug, PartialEq, Clone)] @@ -1440,7 +1336,7 @@ pub struct Block { /// This structure is analogous to [Block], but it allows to retrieve the /// original CBOR bytes for each structure that might require hashing. In this /// way, we make sure that the resulting hash matches what exists on-chain. -#[derive(Encode, Decode, Debug, PartialEq)] +#[derive(Encode, Decode, Debug, PartialEq, Clone)] pub struct MintedBlock<'b> { #[n(0)] pub header: KeepRaw<'b, Header>, @@ -1473,7 +1369,7 @@ pub struct Tx { pub auxiliary_data: Option, } -#[derive(Encode, Decode, Debug)] +#[derive(Encode, Decode, Debug, Clone)] pub struct MintedTx<'b> { #[b(0)] pub transaction_body: KeepRaw<'b, TransactionBody>, diff --git a/pallas-primitives/src/byron/model.rs b/pallas-primitives/src/byron/model.rs index d99ddf1..15c7b0b 100644 --- a/pallas-primitives/src/byron/model.rs +++ b/pallas-primitives/src/byron/model.rs @@ -29,7 +29,7 @@ pub type StakeholderId = Blake2b224; pub type EpochId = u64; -#[derive(Encode, Decode, Debug)] +#[derive(Encode, Decode, Debug, Clone)] pub struct SlotId { #[n(0)] pub epoch: EpochId, @@ -407,7 +407,7 @@ pub type SscCert = (VssPubKey, EpochId, PubKey, Signature); // ssccerts = #6.258([* ssccert]) pub type SscCerts = TagWrap, 258>; -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum Ssc { Variant0(SscComms, SscCerts), Variant1(SscOpens, SscCerts), @@ -473,7 +473,7 @@ impl minicbor::Encode for Ssc { } } -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum SscProof { Variant0(ByronHash, ByronHash), Variant1(ByronHash, ByronHash), @@ -543,7 +543,7 @@ impl minicbor::Encode for SscProof { // Delegation -#[derive(Debug, Encode, Decode)] +#[derive(Debug, Encode, Decode, Clone)] pub struct Dlg { #[n(0)] pub epoch: EpochId, @@ -560,7 +560,7 @@ pub struct Dlg { pub type DlgSig = (Dlg, Signature); -#[derive(Debug, Encode, Decode)] +#[derive(Debug, Encode, Decode, Clone)] pub struct Lwdlg { #[n(0)] pub epoch_range: (EpochId, EpochId), @@ -581,7 +581,7 @@ pub type LwdlgSig = (Lwdlg, Signature); pub type BVer = (u16, u16, u8); -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum TxFeePol { //[0, #6.24(bytes .cbor ([bigint, bigint]))] Variant0(CborWrap<(i64, i64)>), @@ -628,7 +628,7 @@ impl minicbor::Encode for TxFeePol { } } -#[derive(Debug, Encode, Decode)] +#[derive(Debug, Encode, Decode, Clone)] pub struct BVerMod { #[n(0)] pub script_version: ZeroOrOneArray, @@ -675,7 +675,7 @@ pub struct BVerMod { pub type UpData = (ByronHash, ByronHash, ByronHash, ByronHash); -#[derive(Debug, Encode, Decode)] +#[derive(Debug, Encode, Decode, Clone)] pub struct UpProp { #[n(0)] pub block_version: Option, @@ -701,7 +701,7 @@ pub struct UpProp { pub signature: Option, } -#[derive(Debug, Encode, Decode)] +#[derive(Debug, Encode, Decode, Clone)] pub struct UpVote { #[n(0)] pub voter: PubKey, @@ -716,7 +716,7 @@ pub struct UpVote { pub signature: Signature, } -#[derive(Debug, Encode, Decode)] +#[derive(Debug, Encode, Decode, Clone)] pub struct Up { #[n(0)] pub proposal: ZeroOrOneArray, @@ -729,7 +729,7 @@ pub struct Up { pub type Difficulty = MaybeIndefArray; -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum BlockSig { Signature(Signature), LwdlgSig(LwdlgSig), @@ -785,7 +785,7 @@ impl minicbor::Encode for BlockSig { } } -#[derive(Encode, Decode, Debug)] +#[derive(Encode, Decode, Debug, Clone)] pub struct BlockCons( #[n(0)] pub SlotId, #[n(1)] pub PubKey, @@ -793,7 +793,7 @@ pub struct BlockCons( #[n(3)] pub BlockSig, ); -#[derive(Encode, Decode, Debug)] +#[derive(Encode, Decode, Debug, Clone)] pub struct BlockHeadEx { #[n(0)] pub block_version: BVer, @@ -808,7 +808,7 @@ pub struct BlockHeadEx { pub extra_proof: ByronHash, } -#[derive(Encode, Decode, Debug)] +#[derive(Encode, Decode, Debug, Clone)] pub struct BlockProof { #[n(0)] pub tx_proof: TxProof, @@ -823,7 +823,7 @@ pub struct BlockProof { pub upd_proof: ByronHash, } -#[derive(Encode, Decode, Debug)] +#[derive(Encode, Decode, Debug, Clone)] pub struct BlockHead { #[n(0)] pub protocol_magic: u32, @@ -874,7 +874,7 @@ pub struct BlockBody { pub upd_payload: Up, } -#[derive(Encode, Decode, Debug)] +#[derive(Encode, Decode, Debug, Clone)] pub struct MintedBlockBody<'b> { #[b(0)] pub tx_payload: MaybeIndefArray>, @@ -891,7 +891,7 @@ pub struct MintedBlockBody<'b> { // Epoch Boundary Blocks -#[derive(Encode, Decode, Debug)] +#[derive(Encode, Decode, Debug, Clone)] pub struct EbbCons { #[n(0)] pub epoch_id: EpochId, @@ -900,7 +900,7 @@ pub struct EbbCons { pub difficulty: Difficulty, } -#[derive(Encode, Decode, Debug)] +#[derive(Encode, Decode, Debug, Clone)] pub struct EbbHead { #[n(0)] pub protocol_magic: u32, @@ -930,7 +930,7 @@ pub struct Block { pub extra: MaybeIndefArray, } -#[derive(Encode, Decode, Debug)] +#[derive(Encode, Decode, Debug, Clone)] pub struct MintedBlock<'b> { #[b(0)] pub header: KeepRaw<'b, BlockHead>, @@ -942,7 +942,7 @@ pub struct MintedBlock<'b> { pub extra: MaybeIndefArray, } -#[derive(Encode, Decode, Debug)] +#[derive(Encode, Decode, Debug, Clone)] pub struct EbBlock { #[n(0)] pub header: EbbHead, diff --git a/pallas-traverse/src/block.rs b/pallas-traverse/src/block.rs index c8b7cd3..a7f4f2e 100644 --- a/pallas-traverse/src/block.rs +++ b/pallas-traverse/src/block.rs @@ -1,54 +1,94 @@ +use std::borrow::Cow; + use pallas_codec::minicbor; use pallas_crypto::hash::Hash; use pallas_primitives::{alonzo, byron, ToHash}; -use crate::{probe, Era, Error, MultiEraBlock}; +use crate::{probe, support, Era, Error, MultiEraBlock, MultiEraTx}; type BlockWrapper = (u16, T); impl<'b> MultiEraBlock<'b> { - pub fn from_epoch_boundary(block: byron::EbBlock) -> Self { - Self::EpochBoundary(Box::new(block)) + pub fn decode_epoch_boundary(cbor: &'b [u8]) -> Result { + let (_, block): BlockWrapper = + minicbor::decode(cbor).map_err(Error::invalid_cbor)?; + + Ok(Self::EpochBoundary(Box::new(Cow::Owned(block)))) } - pub fn from_byron(block: byron::MintedBlock<'b>) -> Self { - Self::Byron(Box::new(block)) + pub fn decode_byron(cbor: &'b [u8]) -> Result { + let (_, block): BlockWrapper = + minicbor::decode(cbor).map_err(Error::invalid_cbor)?; + + Ok(Self::Byron(Box::new(Cow::Owned(block)))) } - pub fn from_alonzo_compatible(block: alonzo::MintedBlock<'b>) -> Self { - Self::AlonzoCompatible(Box::new(block)) + pub fn decode_shelley(cbor: &'b [u8]) -> Result { + let (_, block): BlockWrapper = + minicbor::decode(cbor).map_err(Error::invalid_cbor)?; + + Ok(Self::AlonzoCompatible( + Box::new(Cow::Owned(block)), + Era::Shelley, + )) + } + + pub fn decode_allegra(cbor: &'b [u8]) -> Result { + let (_, block): BlockWrapper = + minicbor::decode(cbor).map_err(Error::invalid_cbor)?; + + Ok(Self::AlonzoCompatible( + Box::new(Cow::Owned(block)), + Era::Allegra, + )) + } + + pub fn decode_mary(cbor: &'b [u8]) -> Result { + let (_, block): BlockWrapper = + minicbor::decode(cbor).map_err(Error::invalid_cbor)?; + + Ok(Self::AlonzoCompatible( + Box::new(Cow::Owned(block)), + Era::Mary, + )) + } + + pub fn decode_alonzo(cbor: &'b [u8]) -> Result { + let (_, block): BlockWrapper = + minicbor::decode(cbor).map_err(Error::invalid_cbor)?; + + Ok(Self::AlonzoCompatible( + Box::new(Cow::Owned(block)), + Era::Alonzo, + )) } pub fn decode(cbor: &'b [u8]) -> Result, Error> { match probe::block_era(cbor) { - probe::Outcome::EpochBoundary => { - let (_, block): BlockWrapper = - minicbor::decode(cbor).map_err(Error::invalid_cbor)?; - - Ok(MultiEraBlock::from_epoch_boundary(block)) - } + probe::Outcome::EpochBoundary => Self::decode_epoch_boundary(cbor), probe::Outcome::Matched(era) => match era { - Era::Byron => { - let (_, block): BlockWrapper = - minicbor::decode(cbor).map_err(Error::invalid_cbor)?; - - Ok(Self::from_byron(block)) - } - Era::Shelley | Era::Allegra | Era::Mary | Era::Alonzo => { - let (_, block): BlockWrapper = - minicbor::decode(cbor).map_err(Error::invalid_cbor)?; - - Ok(Self::from_alonzo_compatible(block)) - } + Era::Byron => Self::decode_byron(cbor), + Era::Shelley => Self::decode_shelley(cbor), + Era::Allegra => Self::decode_allegra(cbor), + Era::Mary => Self::decode_mary(cbor), + Era::Alonzo => Self::decode_alonzo(cbor), }, probe::Outcome::Inconclusive => Err(Error::unknown_cbor(cbor)), } } + pub fn era(&self) -> Era { + match self { + MultiEraBlock::EpochBoundary(_) => Era::Byron, + MultiEraBlock::AlonzoCompatible(_, x) => *x, + MultiEraBlock::Byron(_) => Era::Byron, + } + } + pub fn hash(&self) -> Hash<32> { match self { MultiEraBlock::EpochBoundary(x) => x.header.to_hash(), - MultiEraBlock::AlonzoCompatible(x) => x.header.to_hash(), + MultiEraBlock::AlonzoCompatible(x, _) => x.header.to_hash(), MultiEraBlock::Byron(x) => x.header.to_hash(), } } @@ -56,8 +96,60 @@ impl<'b> MultiEraBlock<'b> { pub fn slot(&self) -> u64 { match self { MultiEraBlock::EpochBoundary(x) => x.header.to_abs_slot(), - MultiEraBlock::AlonzoCompatible(x) => x.header.header_body.slot, + MultiEraBlock::AlonzoCompatible(x, _) => x.header.header_body.slot, MultiEraBlock::Byron(x) => x.header.consensus_data.0.to_abs_slot(), } } + + pub fn txs(&self) -> Vec { + match self { + MultiEraBlock::AlonzoCompatible(x, _) => support::clone_alonzo_txs(x) + .into_iter() + .map(|x| MultiEraTx::AlonzoCompatible(Box::new(Cow::Owned(x)))) + .collect(), + MultiEraBlock::Byron(x) => support::clone_byron_txs(x) + .into_iter() + .map(|x| MultiEraTx::Byron(Box::new(Cow::Owned(x)))) + .collect(), + MultiEraBlock::EpochBoundary(_) => vec![], + } + } + + pub fn as_alonzo(&self) -> Option<&alonzo::MintedBlock> { + match self { + MultiEraBlock::EpochBoundary(_) => None, + MultiEraBlock::AlonzoCompatible(x, _) => Some(x), + MultiEraBlock::Byron(_) => None, + } + } + + pub fn as_byron(&self) -> Option<&byron::MintedBlock> { + match self { + MultiEraBlock::EpochBoundary(_) => None, + MultiEraBlock::AlonzoCompatible(_, _) => None, + MultiEraBlock::Byron(x) => Some(x), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_iteration() { + let blocks = vec![ + (include_str!("../../test_data/byron2.block"), 2usize), + (include_str!("../../test_data/shelley1.block"), 0), + (include_str!("../../test_data/mary1.block"), 0), + (include_str!("../../test_data/allegra1.block"), 0), + (include_str!("../../test_data/alonzo1.block"), 5), + ]; + + for (block_str, tx_count) in blocks.into_iter() { + let cbor = hex::decode(block_str).expect("invalid hex"); + let block = MultiEraBlock::decode(&cbor).expect("invalid cbor"); + assert_eq!(block.txs().len(), tx_count); + } + } } diff --git a/pallas-traverse/src/cert.rs b/pallas-traverse/src/cert.rs new file mode 100644 index 0000000..555e3ca --- /dev/null +++ b/pallas-traverse/src/cert.rs @@ -0,0 +1,12 @@ +use pallas_primitives::alonzo; + +use crate::MultiEraCert; + +impl<'b> MultiEraCert<'b> { + pub fn as_alonzo(&self) -> Option<&alonzo::Certificate> { + match self { + MultiEraCert::NotApplicable => None, + MultiEraCert::AlonzoCompatible(x) => Some(x), + } + } +} diff --git a/pallas-traverse/src/iter.rs b/pallas-traverse/src/iter.rs deleted file mode 100644 index ef602a6..0000000 --- a/pallas-traverse/src/iter.rs +++ /dev/null @@ -1,96 +0,0 @@ -//! Iterate over block data - -use pallas_primitives::{alonzo, byron}; - -use crate::{MultiEraBlock, MultiEraTx}; - -fn clone_alonzo_tx_at<'b>( - block: &'b alonzo::MintedBlock, - index: usize, -) -> Option> { - let transaction_body = block.transaction_bodies.get(index).cloned()?; - let transaction_witness_set = block.transaction_witness_sets.get(index).cloned()?; - let success = block - .invalid_transactions - .as_ref()? - .contains(&(index as u32)); - - let auxiliary_data = block - .auxiliary_data_set - .iter() - .find_map(|(idx, val)| { - if idx.eq(&(index as u32)) { - Some(val) - } else { - None - } - }) - .cloned(); - - Some(alonzo::MintedTx { - transaction_body, - transaction_witness_set, - success, - auxiliary_data, - }) -} - -fn clone_byron_tx_at<'b>( - block: &'b byron::MintedBlock, - index: usize, -) -> Option> { - block.body.tx_payload.get(index).cloned() -} - -pub struct TxIter<'b> { - block: &'b MultiEraBlock<'b>, - index: usize, -} - -impl<'b> Iterator for TxIter<'b> { - type Item = MultiEraTx<'b>; - - fn next(&mut self) -> Option { - let tx = match self.block { - MultiEraBlock::EpochBoundary(_) => None, - MultiEraBlock::AlonzoCompatible(x) => { - clone_alonzo_tx_at(x, self.index).map(MultiEraTx::from_alonzo_compatible) - } - MultiEraBlock::Byron(x) => clone_byron_tx_at(x, self.index).map(MultiEraTx::from_byron), - }?; - - self.index += 1; - Some(tx) - } -} - -impl<'b> MultiEraBlock<'b> { - pub fn tx_iter(&'b self) -> TxIter<'b> { - TxIter { - index: 0, - block: self, - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_iteration() { - let blocks = vec![ - (include_str!("../../test_data/byron2.block"), 2usize), - (include_str!("../../test_data/shelley1.block"), 0), - (include_str!("../../test_data/mary1.block"), 0), - (include_str!("../../test_data/allegra1.block"), 0), - (include_str!("../../test_data/alonzo1.block"), 5), - ]; - - for (block_str, tx_count) in blocks.into_iter() { - let cbor = hex::decode(block_str).expect("invalid hex"); - let block = MultiEraBlock::decode(&cbor).expect("invalid cbor"); - assert_eq!(block.tx_iter().count(), tx_count); - } - } -} diff --git a/pallas-traverse/src/lib.rs b/pallas-traverse/src/lib.rs index 65b9a2f..5257ffd 100644 --- a/pallas-traverse/src/lib.rs +++ b/pallas-traverse/src/lib.rs @@ -1,12 +1,16 @@ //! Utilities to traverse over multi-era block data + +use std::borrow::Cow; use std::fmt::Display; use pallas_primitives::{alonzo, byron}; use thiserror::Error; pub mod block; -pub mod iter; +pub mod cert; +pub mod output; pub mod probe; +mod support; pub mod tx; #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] @@ -19,19 +23,31 @@ pub enum Era { Alonzo, // smart-contracts } -#[derive(Debug)] +#[derive(Debug, Clone)] #[non_exhaustive] -pub enum MultiEraTx<'b> { - AlonzoCompatible(Box>), - Byron(Box>), +pub enum MultiEraBlock<'b> { + EpochBoundary(Box>), + AlonzoCompatible(Box>>, Era), + Byron(Box>>), } #[derive(Debug)] #[non_exhaustive] -pub enum MultiEraBlock<'b> { - EpochBoundary(Box), - AlonzoCompatible(Box>), - Byron(Box>), +pub enum MultiEraTx<'b> { + AlonzoCompatible(Box>>), + Byron(Box>>), +} + +#[derive(Debug)] +#[non_exhaustive] +pub enum MultiEraOutput<'b> { + Byron(Box>), + AlonzoCompatible(Box>), +} + +pub enum MultiEraCert<'b> { + NotApplicable, + AlonzoCompatible(Box>), } #[derive(Debug, Error)] diff --git a/pallas-traverse/src/output.rs b/pallas-traverse/src/output.rs new file mode 100644 index 0000000..66be887 --- /dev/null +++ b/pallas-traverse/src/output.rs @@ -0,0 +1,38 @@ +use std::borrow::Cow; + +use pallas_primitives::{alonzo, byron}; + +use crate::MultiEraOutput; + +impl<'b> MultiEraOutput<'b> { + pub fn from_byron(output: &'b byron::TxOut) -> Self { + Self::Byron(Box::new(Cow::Borrowed(output))) + } + + pub fn from_alonzo_compatible(output: &'b alonzo::TransactionOutput) -> Self { + Self::AlonzoCompatible(Box::new(Cow::Borrowed(output))) + } + + pub fn address(&self, hrp: &str) -> String { + match self { + MultiEraOutput::Byron(x) => x.address.to_addr_string().expect("invalid address value"), + MultiEraOutput::AlonzoCompatible(x) => { + x.to_bech32_address(hrp).expect("invalid address value") + } + } + } + + pub fn as_alonzo(&self) -> Option<&alonzo::TransactionOutput> { + match self { + MultiEraOutput::Byron(_) => None, + MultiEraOutput::AlonzoCompatible(x) => Some(x), + } + } + + pub fn as_byron(&self) -> Option<&byron::TxOut> { + match self { + MultiEraOutput::Byron(x) => Some(x), + MultiEraOutput::AlonzoCompatible(_) => None, + } + } +} diff --git a/pallas-traverse/src/support.rs b/pallas-traverse/src/support.rs new file mode 100644 index 0000000..836bd98 --- /dev/null +++ b/pallas-traverse/src/support.rs @@ -0,0 +1,47 @@ +//! Internal supporting utilities + +use pallas_primitives::{alonzo, byron}; + +pub fn clone_alonzo_tx_at<'b>( + block: &'b alonzo::MintedBlock, + index: usize, +) -> Option> { + let transaction_body = block.transaction_bodies.get(index).cloned()?; + + let transaction_witness_set = block.transaction_witness_sets.get(index).cloned()?; + + let success = !block + .invalid_transactions + .as_ref()? + .contains(&(index as u32)); + + let auxiliary_data = block + .auxiliary_data_set + .iter() + .find_map(|(idx, val)| { + if idx.eq(&(index as u32)) { + Some(val) + } else { + None + } + }) + .cloned(); + + Some(alonzo::MintedTx { + transaction_body, + transaction_witness_set, + success, + auxiliary_data, + }) +} + +pub fn clone_alonzo_txs<'b>(block: &'b alonzo::MintedBlock) -> Vec> { + (0..block.transaction_bodies.len()) + .step_by(1) + .filter_map(|idx| clone_alonzo_tx_at(block, idx)) + .collect() +} + +pub fn clone_byron_txs<'b>(block: &'b byron::MintedBlock) -> Vec> { + block.body.tx_payload.iter().cloned().collect() +} diff --git a/pallas-traverse/src/tx.rs b/pallas-traverse/src/tx.rs index 15b6336..cd93763 100644 --- a/pallas-traverse/src/tx.rs +++ b/pallas-traverse/src/tx.rs @@ -1,13 +1,74 @@ -use pallas_primitives::{alonzo, byron}; +use pallas_codec::minicbor; +use pallas_crypto::hash::Hash; +use pallas_primitives::{alonzo, byron, ToHash}; +use std::borrow::Cow; -use crate::MultiEraTx; +use crate::{MultiEraCert, MultiEraOutput, MultiEraTx}; impl<'b> MultiEraTx<'b> { - pub fn from_byron(tx: byron::MintedTxPayload<'b>) -> Self { - Self::Byron(Box::new(tx)) + pub fn from_byron(tx: &'b byron::MintedTxPayload<'b>) -> Self { + Self::Byron(Box::new(Cow::Borrowed(tx))) } - pub fn from_alonzo_compatible(tx: alonzo::MintedTx<'b>) -> Self { - Self::AlonzoCompatible(Box::new(tx)) + pub fn from_alonzo_compatible(tx: &'b alonzo::MintedTx<'b>) -> Self { + Self::AlonzoCompatible(Box::new(Cow::Borrowed(tx))) + } + + pub fn encode(&self) -> Result, minicbor::encode::Error> { + match self { + MultiEraTx::AlonzoCompatible(x) => minicbor::to_vec(x), + MultiEraTx::Byron(x) => minicbor::to_vec(x), + } + } + + pub fn hash(&self) -> Hash<32> { + match self { + MultiEraTx::AlonzoCompatible(x) => x.transaction_body.to_hash(), + MultiEraTx::Byron(x) => x.transaction.to_hash(), + } + } + + pub fn outputs(&self) -> Vec { + match self { + MultiEraTx::AlonzoCompatible(x) => x + .transaction_body + .outputs + .iter() + .map(MultiEraOutput::from_alonzo_compatible) + .collect(), + MultiEraTx::Byron(x) => x + .transaction + .outputs + .iter() + .map(MultiEraOutput::from_byron) + .collect(), + } + } + + pub fn certs(&self) -> Vec { + match self { + MultiEraTx::AlonzoCompatible(x) => x + .transaction_body + .certificates + .iter() + .flat_map(|c| c.iter()) + .map(|c| MultiEraCert::AlonzoCompatible(Box::new(Cow::Borrowed(c)))) + .collect(), + MultiEraTx::Byron(_) => vec![], + } + } + + pub fn as_alonzo(&self) -> Option<&alonzo::MintedTx> { + match self { + MultiEraTx::AlonzoCompatible(x) => Some(x), + MultiEraTx::Byron(_) => None, + } + } + + pub fn as_byron(&self) -> Option<&byron::MintedTxPayload> { + match self { + MultiEraTx::AlonzoCompatible(_) => None, + MultiEraTx::Byron(x) => Some(x), + } } }