diff --git a/Cargo.toml b/Cargo.toml index 3dcc34a..28ab97e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ members = [ "pallas-miniprotocols", "pallas-crypto", "pallas-primitives", + "pallas-traverse", "pallas", "examples/block-download", "examples/block-decode", diff --git a/README.md b/README.md index 4493b94..915ba55 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,7 @@ As already explained, _Pallas_ aims at being an expanding set of components. The | Crates | Description | | --------------------------------------- | ----------------------------------------------------------------------- | | [pallas-primitives](/pallas-primitives) | Ledger primitives and cbor codec for the different Cardano eras | +| [pallas-traverse](/pallas-traverse) | Utilities to traverse over multi-era block data | | pallas-ticking | Time passage implementation for consensus algorithm | | pallas-applying | Logic for validating and applying new blocks and txs to the chain state | | pallas-forecasting | Ledger forecasting algorithm to be used by the consensus layer | diff --git a/examples/block-decode/src/main.rs b/examples/block-decode/src/main.rs index 6c6d4e8..a14fecd 100644 --- a/examples/block-decode/src/main.rs +++ b/examples/block-decode/src/main.rs @@ -1,4 +1,4 @@ -use pallas::ledger::primitives::{alonzo, byron, probing, Era}; +use pallas::ledger::traverse::MultiEraBlock; fn main() { let blocks = vec![ @@ -10,23 +10,14 @@ fn main() { ]; for block_str in blocks.iter() { - let bytes = hex::decode(block_str).expect("invalid hex"); + let cbor = hex::decode(block_str).expect("invalid hex"); - match probing::probe_block_cbor_era(&bytes) { - probing::Outcome::Matched(era) => match era { - Era::Byron => { - let (_, block): (u16, byron::MainBlock) = - pallas::codec::minicbor::decode(&bytes).expect("invalid cbor"); - println!("{:?}", block) - } - // we use alonzo for everything post-shelly since it's backward compatible - Era::Shelley | Era::Allegra | Era::Mary | Era::Alonzo => { - let (_, block): (u16, alonzo::Block) = - pallas::codec::minicbor::decode(&bytes).expect("invalid cbor"); - println!("{:?}", block) - } - }, - _ => println!("couldn't infer block era"), - }; + let block = MultiEraBlock::decode(&cbor).expect("invalid cbor"); + + println!("{} {}", block.slot(), block.hash()); + + for tx in block.tx_iter() { + println!("{:?}", tx); + } } } diff --git a/pallas-codec/src/utils.rs b/pallas-codec/src/utils.rs index 089c3f5..e63d588 100644 --- a/pallas-codec/src/utils.rs +++ b/pallas-codec/src/utils.rs @@ -111,7 +111,7 @@ where } /// A struct that maintains a reference to whether a cbor array was indef or not -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)] pub enum MaybeIndefArray { Def(Vec), Indef(Vec), @@ -186,7 +186,7 @@ where /// 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)] +#[derive(Debug, PartialEq, Clone)] pub struct OrderPreservingProperties

(Vec

); impl

Deref for OrderPreservingProperties

{ @@ -229,7 +229,7 @@ where } /// Wraps a struct so that it is encoded/decoded as a cbor bytes -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct CborWrap(pub T); impl<'b, C, T> minicbor::Decode<'b, C> for CborWrap @@ -312,7 +312,7 @@ where /// An empty map /// /// don't ask me why, that's what the CDDL asks for. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct EmptyMap; impl<'b, C> minicbor::decode::Decode<'b, C> for EmptyMap { @@ -518,7 +518,7 @@ impl From<&AnyUInt> for u64 { /// let confirm: (u16, u16) = minicbor::decode(keeper.raw_cbor()).unwrap(); /// assert_eq!(confirm, (456u16, 789u16)); /// ``` -#[derive(Debug, PartialEq, PartialOrd)] +#[derive(Debug, PartialEq, PartialOrd, Clone)] pub struct KeepRaw<'b, T> { raw: &'b [u8], inner: T, diff --git a/pallas-primitives/src/alonzo/model.rs b/pallas-primitives/src/alonzo/model.rs index 49cb017..8bc36f0 100644 --- a/pallas-primitives/src/alonzo/model.rs +++ b/pallas-primitives/src/alonzo/model.rs @@ -74,7 +74,7 @@ pub struct Header { pub body_signature: ByteVec, } -#[derive(Encode, Decode, Debug, PartialEq)] +#[derive(Encode, Decode, Debug, PartialEq, Clone)] pub struct TransactionInput { #[n(0)] pub transaction_id: Hash<32>, @@ -85,7 +85,7 @@ pub struct TransactionInput { // $nonce /= [ 0 // 1, bytes .size 32 ] -#[derive(Encode, Decode, Debug, PartialEq)] +#[derive(Encode, Decode, Debug, PartialEq, Clone)] #[cbor(index_only)] pub enum NonceVariant { #[n(0)] @@ -95,7 +95,7 @@ pub enum NonceVariant { Nonce, } -#[derive(Encode, Decode, Debug, PartialEq)] +#[derive(Encode, Decode, Debug, PartialEq, Clone)] pub struct Nonce { #[n(0)] pub variant: NonceVariant, @@ -162,7 +162,7 @@ impl minicbor::encode::Encode for Value { } } -#[derive(Encode, Decode, Debug, PartialEq)] +#[derive(Encode, Decode, Debug, PartialEq, Clone)] pub struct TransactionOutput { #[n(0)] pub address: ByteVec, @@ -187,7 +187,7 @@ pub type VrfKeyhash = Hash<32>; ; otherwise the funds are given to the other accounting pot. */ -#[derive(Debug, PartialEq, PartialOrd)] +#[derive(Debug, PartialEq, PartialOrd, Clone)] pub enum InstantaneousRewardSource { Reserves, Treasury, @@ -225,7 +225,7 @@ impl minicbor::encode::Encode for InstantaneousRewardSource { } } -#[derive(Debug, PartialEq, PartialOrd)] +#[derive(Debug, PartialEq, PartialOrd, Clone)] pub enum InstantaneousRewardTarget { StakeCredentials(KeyValuePairs), OtherAccountingPot(Coin), @@ -267,7 +267,7 @@ impl minicbor::encode::Encode for InstantaneousRewardTarget { } } -#[derive(Encode, Decode, Debug, PartialEq, PartialOrd)] +#[derive(Encode, Decode, Debug, PartialEq, PartialOrd, Clone)] #[cbor] pub struct MoveInstantaneousReward { #[n(0)] @@ -284,7 +284,7 @@ pub type IPv4 = ByteVec; pub type IPv6 = ByteVec; pub type DnsName = String; -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone)] pub enum Relay { SingleHostAddr(Option, Option, Option), SingleHostName(Option, DnsName), @@ -351,7 +351,7 @@ impl minicbor::encode::Encode for Relay { pub type PoolMetadataHash = Hash<32>; -#[derive(Encode, Decode, Debug, PartialEq)] +#[derive(Encode, Decode, Debug, PartialEq, Clone)] pub struct PoolMetadata { #[n(0)] pub url: String, @@ -363,7 +363,7 @@ pub struct PoolMetadata { pub type AddrKeyhash = Hash<28>; pub type Scripthash = Hash<28>; -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone)] pub struct RationalNumber { pub numerator: i64, pub denominator: u64, @@ -401,7 +401,7 @@ pub type UnitInterval = RationalNumber; pub type PositiveInterval = RationalNumber; -#[derive(Debug, PartialEq, PartialOrd, Eq, Ord)] +#[derive(Debug, PartialEq, PartialOrd, Eq, Ord, Clone)] pub enum StakeCredential { AddrKeyhash(AddrKeyhash), Scripthash(Scripthash), @@ -447,7 +447,7 @@ impl minicbor::encode::Encode for StakeCredential { } } -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone)] pub enum Certificate { StakeRegistration(StakeCredential), StakeDeregistration(StakeCredential), @@ -615,7 +615,7 @@ impl minicbor::encode::Encode for Certificate { } } -#[derive(Encode, Decode, Debug, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Encode, Decode, Debug, PartialEq, Eq, PartialOrd, Ord, Clone)] #[cbor(index_only)] pub enum NetworkId { #[n(0)] @@ -624,7 +624,7 @@ pub enum NetworkId { Two, } -#[derive(Encode, Decode, Debug, PartialEq)] +#[derive(Encode, Decode, Debug, PartialEq, Clone)] #[cbor(index_only)] pub enum Language { #[n(0)] @@ -637,7 +637,7 @@ pub type CostMdls = KeyValuePairs; pub type ProtocolVersion = (u32, u32); -#[derive(Encode, Decode, Debug, PartialEq)] +#[derive(Encode, Decode, Debug, PartialEq, Clone)] #[cbor(map)] pub struct ProtocolParamUpdate { #[n(0)] @@ -690,7 +690,7 @@ pub struct ProtocolParamUpdate { pub max_collateral_inputs: Option, } -#[derive(Encode, Decode, Debug, PartialEq)] +#[derive(Encode, Decode, Debug, PartialEq, Clone)] pub struct Update { #[n(0)] pub proposed_protocol_parameter_updates: KeyValuePairs, @@ -699,7 +699,7 @@ pub struct Update { pub epoch: Epoch, } -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone)] pub enum TransactionBodyComponent { Inputs(MaybeIndefArray), Outputs(MaybeIndefArray), @@ -814,7 +814,7 @@ impl minicbor::encode::Encode for TransactionBodyComponent { // Can't derive encode for TransactionBody because it seems to require a very // particular order for each key in the map -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone)] pub struct TransactionBody(Vec); impl Deref for TransactionBody { @@ -850,7 +850,7 @@ impl minicbor::encode::Encode for TransactionBody { } } -#[derive(Encode, Decode, Debug, PartialEq)] +#[derive(Encode, Decode, Debug, PartialEq, Clone)] pub struct VKeyWitness { #[n(0)] pub vkey: ByteVec, @@ -859,7 +859,7 @@ pub struct VKeyWitness { pub signature: ByteVec, } -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone)] pub enum NativeScript { ScriptPubkey(AddrKeyhash), ScriptAll(MaybeIndefArray), @@ -931,7 +931,7 @@ impl minicbor::encode::Encode for NativeScript { } } -#[derive(Encode, Decode, Debug, PartialEq)] +#[derive(Encode, Decode, Debug, PartialEq, Clone)] #[cbor(transparent)] pub struct PlutusScript(#[n(0)] pub ByteVec); @@ -947,7 +947,7 @@ big_uint = #6.2(bounded_bytes) ; New big_nint = #6.3(bounded_bytes) ; New */ -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)] pub enum BigInt { Int(Int), BigUInt(ByteVec), @@ -1009,7 +1009,7 @@ impl minicbor::encode::Encode for BigInt { } } -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)] pub enum PlutusData { Constr(Constr), Map(KeyValuePairs), @@ -1090,7 +1090,7 @@ impl minicbor::encode::Encode for PlutusData { } } -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)] pub struct Constr { pub tag: u64, pub any_constructor: Option, @@ -1159,7 +1159,7 @@ where } } -#[derive(Encode, Decode, Debug, PartialEq)] +#[derive(Encode, Decode, Debug, PartialEq, Clone)] pub struct ExUnits { #[n(0)] pub mem: u32, @@ -1167,7 +1167,7 @@ pub struct ExUnits { pub steps: u64, } -#[derive(Encode, Decode, Debug, PartialEq)] +#[derive(Encode, Decode, Debug, PartialEq, Clone)] pub struct ExUnitPrices { #[n(0)] mem_price: PositiveInterval, @@ -1176,7 +1176,7 @@ pub struct ExUnitPrices { step_price: PositiveInterval, } -#[derive(Encode, Decode, Debug, PartialEq)] +#[derive(Encode, Decode, Debug, PartialEq, Clone)] #[cbor(index_only)] pub enum RedeemerTag { #[n(0)] @@ -1189,7 +1189,7 @@ pub enum RedeemerTag { Reward, } -#[derive(Encode, Decode, Debug, PartialEq)] +#[derive(Encode, Decode, Debug, PartialEq, Clone)] pub struct Redeemer { #[n(0)] pub tag: RedeemerTag, @@ -1211,7 +1211,7 @@ pub struct Redeemer { , attributes : bytes ] */ -#[derive(Encode, Decode, Debug, PartialEq)] +#[derive(Encode, Decode, Debug, PartialEq, Clone)] pub struct BootstrapWitness { #[n(0)] pub public_key: ByteVec, @@ -1226,7 +1226,7 @@ pub struct BootstrapWitness { pub attributes: ByteVec, } -#[derive(Encode, Decode, Debug, PartialEq)] +#[derive(Encode, Decode, Debug, PartialEq, Clone)] #[cbor(map)] pub struct TransactionWitnessSet { #[n(0)] @@ -1248,7 +1248,7 @@ pub struct TransactionWitnessSet { pub redeemer: Option>, } -#[derive(Encode, Decode, Debug, PartialEq)] +#[derive(Encode, Decode, Debug, PartialEq, Clone)] #[cbor(map)] pub struct AlonzoAuxiliaryData { #[n(0)] @@ -1259,7 +1259,7 @@ pub struct AlonzoAuxiliaryData { pub plutus_scripts: Option>, } -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)] pub enum Metadatum { Int(Int), Bytes(ByteVec), @@ -1350,7 +1350,7 @@ pub type MetadatumLabel = AnyUInt; pub type Metadata = KeyValuePairs; -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone)] pub enum AuxiliaryData { Shelley(Metadata), ShelleyMa { @@ -1417,7 +1417,7 @@ impl minicbor::Encode for AuxiliaryData { pub type TransactionIndex = u32; -#[derive(Encode, Decode, Debug, PartialEq)] +#[derive(Encode, Decode, Debug, PartialEq, Clone)] pub struct Block { #[n(0)] pub header: Header, @@ -1459,18 +1459,33 @@ pub struct MintedBlock<'b> { } #[derive(Encode, Decode, Debug)] -pub struct Transaction { +pub struct Tx { #[n(0)] - transaction_body: TransactionBody, + pub transaction_body: TransactionBody, #[n(1)] - transaction_witness_set: TransactionWitnessSet, + pub transaction_witness_set: TransactionWitnessSet, #[n(2)] - success: bool, + pub success: bool, #[n(3)] - auxiliary_data: Option, + pub auxiliary_data: Option, +} + +#[derive(Encode, Decode, Debug)] +pub struct MintedTx<'b> { + #[b(0)] + pub transaction_body: KeepRaw<'b, TransactionBody>, + + #[n(1)] + pub transaction_witness_set: TransactionWitnessSet, + + #[n(2)] + pub success: bool, + + #[n(3)] + pub auxiliary_data: Option>, } #[cfg(test)] diff --git a/pallas-primitives/src/byron/address.rs b/pallas-primitives/src/byron/address.rs index 73974a0..0d08ab6 100644 --- a/pallas-primitives/src/byron/address.rs +++ b/pallas-primitives/src/byron/address.rs @@ -13,9 +13,9 @@ impl Address { #[cfg(test)] mod tests { - use crate::byron::MintedMainBlock; + use crate::byron::MintedBlock; - type BlockWrapper<'b> = (u16, MintedMainBlock<'b>); + type BlockWrapper<'b> = (u16, MintedBlock<'b>); const KNOWN_ADDRESSES: &[&str] = &[ "DdzFFzCqrht8QHTQXbWy2qoyPaqTN8BjyfKygGmpy9dtot1tvkBfCaVTnR22XCaaDVn3M1U6aiMShoCLzw6VWSwzQKhhJrM3YjYp3wyy", diff --git a/pallas-primitives/src/byron/crypto.rs b/pallas-primitives/src/byron/crypto.rs index 84881f1..d57bfb6 100644 --- a/pallas-primitives/src/byron/crypto.rs +++ b/pallas-primitives/src/byron/crypto.rs @@ -48,9 +48,9 @@ impl ToHash<32> for KeepRaw<'_, Tx> { mod tests { use pallas_codec::minicbor; - use crate::{byron::MintedMainBlock, ToHash}; + use crate::{byron::MintedBlock, ToHash}; - type BlockWrapper<'b> = (u16, MintedMainBlock<'b>); + type BlockWrapper<'b> = (u16, MintedBlock<'b>); const KNOWN_HASH: &'static str = "5c196e7394ace0449ba5a51c919369699b13896e97432894b4f0354dce8670b6"; diff --git a/pallas-primitives/src/byron/fees.rs b/pallas-primitives/src/byron/fees.rs index e9d0f1b..586b1d3 100644 --- a/pallas-primitives/src/byron/fees.rs +++ b/pallas-primitives/src/byron/fees.rs @@ -48,9 +48,9 @@ impl TxPayload { mod tests { use pallas_codec::minicbor; - use crate::{byron::MainBlock, ToHash}; + use crate::{byron::Block, ToHash}; - type BlockWrapper = (u16, MainBlock); + type BlockWrapper = (u16, Block); #[test] fn known_fee_matches() { diff --git a/pallas-primitives/src/byron/model.rs b/pallas-primitives/src/byron/model.rs index ab29554..d99ddf1 100644 --- a/pallas-primitives/src/byron/model.rs +++ b/pallas-primitives/src/byron/model.rs @@ -51,7 +51,7 @@ pub type Attributes = EmptyMap; // Addresses -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum AddrDistr { Variant0(StakeholderId), Variant1, @@ -96,7 +96,7 @@ impl minicbor::Encode<()> for AddrDistr { } } -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum AddrType { PubKey, Script, @@ -137,7 +137,7 @@ impl minicbor::Encode for AddrType { } } -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum AddrAttrProperty { AddrDistr(AddrDistr), Bytes(ByteVec), @@ -187,7 +187,7 @@ impl minicbor::Encode for AddrAttrProperty { pub type AddrAttr = OrderPreservingProperties; -#[derive(Debug, Encode, Decode)] +#[derive(Debug, Encode, Decode, Clone)] pub struct AddressPayload { #[n(0)] pub root: AddressId, @@ -200,7 +200,7 @@ pub struct AddressPayload { } // address = [ #6.24(bytes .cbor ([addressid, addrattr, addrtype])), u64 ] -#[derive(Debug, Encode, Decode)] +#[derive(Debug, Encode, Decode, Clone)] pub struct Address { #[n(0)] pub payload: CborWrap, @@ -212,7 +212,7 @@ pub struct Address { // Transactions // txout = [address, u64] -#[derive(Debug, Encode, Decode)] +#[derive(Debug, Encode, Decode, Clone)] pub struct TxOut { #[n(0)] pub address: Address, @@ -221,7 +221,7 @@ pub struct TxOut { pub amount: u64, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum TxIn { // [0, #6.24(bytes .cbor ([txid, u32]))] Variant0(CborWrap<(TxId, u32)>), @@ -269,7 +269,7 @@ impl minicbor::Encode for TxIn { } // tx = [[+ txin], [+ txout], attributes] -#[derive(Debug, Encode, Decode)] +#[derive(Debug, Encode, Decode, Clone)] pub struct Tx { #[n(0)] pub inputs: MaybeIndefArray, @@ -287,7 +287,7 @@ pub type TxProof = (u32, ByronHash, ByronHash); pub type ValidatorScript = (u16, ByteVec); pub type RedeemerScript = (u16, ByteVec); -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum Twit { // [0, #6.24(bytes .cbor ([pubkey, signature]))] PkWitness(CborWrap<(PubKey, Signature)>), @@ -841,7 +841,6 @@ pub struct BlockHead { pub extra_data: BlockHeadEx, } -// [tx, [* twit]] #[derive(Debug, Encode, Decode)] pub struct TxPayload { #[n(0)] @@ -851,6 +850,15 @@ pub struct TxPayload { pub witness: MaybeIndefArray, } +#[derive(Debug, Encode, Decode, Clone)] +pub struct MintedTxPayload<'b> { + #[b(0)] + pub transaction: KeepRaw<'b, Tx>, + + #[n(1)] + pub witness: MaybeIndefArray, +} + #[derive(Encode, Decode, Debug)] pub struct BlockBody { #[n(0)] @@ -866,6 +874,21 @@ pub struct BlockBody { pub upd_payload: Up, } +#[derive(Encode, Decode, Debug)] +pub struct MintedBlockBody<'b> { + #[b(0)] + pub tx_payload: MaybeIndefArray>, + + #[b(1)] + pub ssc_payload: Ssc, + + #[b(2)] + pub dlg_payload: MaybeIndefArray, + + #[b(3)] + pub upd_payload: Up, +} + // Epoch Boundary Blocks #[derive(Encode, Decode, Debug)] @@ -896,7 +919,7 @@ pub struct EbbHead { } #[derive(Encode, Decode, Debug)] -pub struct MainBlock { +pub struct Block { #[n(0)] pub header: BlockHead, @@ -908,12 +931,12 @@ pub struct MainBlock { } #[derive(Encode, Decode, Debug)] -pub struct MintedMainBlock<'b> { +pub struct MintedBlock<'b> { #[b(0)] pub header: KeepRaw<'b, BlockHead>, - #[n(1)] - pub body: BlockBody, + #[b(1)] + pub body: MintedBlockBody<'b>, #[n(2)] pub extra: MaybeIndefArray, @@ -933,7 +956,7 @@ pub struct EbBlock { #[cfg(test)] mod tests { - use super::{BlockHead, EbBlock, MintedMainBlock}; + use super::{BlockHead, EbBlock, MintedBlock}; use pallas_codec::minicbor::{self, to_vec}; #[test] @@ -958,7 +981,7 @@ mod tests { #[test] fn main_block_isomorphic_decoding_encoding() { - type BlockWrapper<'b> = (u16, MintedMainBlock<'b>); + type BlockWrapper<'b> = (u16, MintedBlock<'b>); let test_blocks = vec![ //include_str!("../../../test_data/genesis.block"), diff --git a/pallas-primitives/src/byron/time.rs b/pallas-primitives/src/byron/time.rs index 3d1d389..1f1f21b 100644 --- a/pallas-primitives/src/byron/time.rs +++ b/pallas-primitives/src/byron/time.rs @@ -24,9 +24,9 @@ impl EbbHead { mod tests { use pallas_codec::minicbor; - use crate::byron::MainBlock; + use crate::byron::Block; - type BlockWrapper = (u16, MainBlock); + type BlockWrapper = (u16, Block); #[test] fn knwon_slot_matches() { diff --git a/pallas-primitives/src/framework.rs b/pallas-primitives/src/framework.rs index b4ce4a0..813a17e 100644 --- a/pallas-primitives/src/framework.rs +++ b/pallas-primitives/src/framework.rs @@ -23,15 +23,6 @@ where } } -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] -pub enum Era { - Byron, - Shelley, - Allegra, // time-locks - Mary, // multi-assets - Alonzo, // smart-contracts -} - #[cfg(feature = "json")] pub trait ToCanonicalJson { fn to_json(&self) -> serde_json::Value; diff --git a/pallas-primitives/src/lib.rs b/pallas-primitives/src/lib.rs index a623304..39fdb6a 100644 --- a/pallas-primitives/src/lib.rs +++ b/pallas-primitives/src/lib.rs @@ -4,6 +4,5 @@ mod framework; pub mod alonzo; pub mod byron; -pub mod probing; pub use framework::*; diff --git a/pallas-traverse/Cargo.toml b/pallas-traverse/Cargo.toml new file mode 100644 index 0000000..0621377 --- /dev/null +++ b/pallas-traverse/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "pallas-traverse" +description = "Utilities to traverse over multi-era block data" +version = "0.11.0-alpha.0" +edition = "2021" +repository = "https://github.com/txpipe/pallas" +homepage = "https://github.com/txpipe/pallas" +documentation = "https://docs.rs/pallas-traverse" +license = "Apache-2.0" +readme = "README.md" +authors = [ + "Santiago Carmuega ", +] + +[dependencies] +pallas-primitives = { version = "0.11.0-alpha.0", path = "../pallas-primitives" } +pallas-crypto = { version = "0.11.0-alpha.0", path = "../pallas-crypto" } +pallas-codec = { version = "0.11.0-alpha.0", path = "../pallas-codec" } +hex = "0.4.3" +thiserror = "1.0.31" diff --git a/pallas-traverse/src/block.rs b/pallas-traverse/src/block.rs new file mode 100644 index 0000000..c8b7cd3 --- /dev/null +++ b/pallas-traverse/src/block.rs @@ -0,0 +1,63 @@ +use pallas_codec::minicbor; +use pallas_crypto::hash::Hash; +use pallas_primitives::{alonzo, byron, ToHash}; + +use crate::{probe, Era, Error, MultiEraBlock}; + +type BlockWrapper = (u16, T); + +impl<'b> MultiEraBlock<'b> { + pub fn from_epoch_boundary(block: byron::EbBlock) -> Self { + Self::EpochBoundary(Box::new(block)) + } + + pub fn from_byron(block: byron::MintedBlock<'b>) -> Self { + Self::Byron(Box::new(block)) + } + + pub fn from_alonzo_compatible(block: alonzo::MintedBlock<'b>) -> Self { + Self::AlonzoCompatible(Box::new(block)) + } + + 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::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)) + } + }, + probe::Outcome::Inconclusive => Err(Error::unknown_cbor(cbor)), + } + } + + pub fn hash(&self) -> Hash<32> { + match self { + MultiEraBlock::EpochBoundary(x) => x.header.to_hash(), + MultiEraBlock::AlonzoCompatible(x) => x.header.to_hash(), + MultiEraBlock::Byron(x) => x.header.to_hash(), + } + } + + pub fn slot(&self) -> u64 { + match self { + MultiEraBlock::EpochBoundary(x) => x.header.to_abs_slot(), + MultiEraBlock::AlonzoCompatible(x) => x.header.header_body.slot, + MultiEraBlock::Byron(x) => x.header.consensus_data.0.to_abs_slot(), + } + } +} diff --git a/pallas-traverse/src/iter.rs b/pallas-traverse/src/iter.rs new file mode 100644 index 0000000..ef602a6 --- /dev/null +++ b/pallas-traverse/src/iter.rs @@ -0,0 +1,96 @@ +//! 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 new file mode 100644 index 0000000..65b9a2f --- /dev/null +++ b/pallas-traverse/src/lib.rs @@ -0,0 +1,54 @@ +//! Utilities to traverse over multi-era block data +use std::fmt::Display; + +use pallas_primitives::{alonzo, byron}; +use thiserror::Error; + +pub mod block; +pub mod iter; +pub mod probe; +pub mod tx; + +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] +#[non_exhaustive] +pub enum Era { + Byron, + Shelley, + Allegra, // time-locks + Mary, // multi-assets + Alonzo, // smart-contracts +} + +#[derive(Debug)] +#[non_exhaustive] +pub enum MultiEraTx<'b> { + AlonzoCompatible(Box>), + Byron(Box>), +} + +#[derive(Debug)] +#[non_exhaustive] +pub enum MultiEraBlock<'b> { + EpochBoundary(Box), + AlonzoCompatible(Box>), + Byron(Box>), +} + +#[derive(Debug, Error)] +pub enum Error { + #[error("Invalid CBOR structure: {0}")] + InvalidCbor(String), + + #[error("Unknown CBOR structure: {0}")] + UnknownCbor(String), +} + +impl Error { + pub fn invalid_cbor(error: impl Display) -> Self { + Error::InvalidCbor(format!("{}", error)) + } + + pub fn unknown_cbor(bytes: &[u8]) -> Self { + Error::UnknownCbor(hex::encode(bytes)) + } +} diff --git a/pallas-primitives/src/probing.rs b/pallas-traverse/src/probe.rs similarity index 79% rename from pallas-primitives/src/probing.rs rename to pallas-traverse/src/probe.rs index 73fcbb6..91ac19c 100644 --- a/pallas-primitives/src/probing.rs +++ b/pallas-traverse/src/probe.rs @@ -1,4 +1,4 @@ -//! Heuristics for detecting cbor content without decoding +//! Lightweight inspection of block data without full CBOR decoding use pallas_codec::minicbor::decode::{Token, Tokenizer}; @@ -12,9 +12,9 @@ pub enum Outcome { } // Executes a very lightweight inspection of the initial tokens of the CBOR -// payload and infers with a certain degree of confidence the type of Cardano -// structure within. -pub fn probe_block_cbor_era(cbor: &[u8]) -> Outcome { +// block payload to extract the tag of the block wrapper which defines the era +// of the contained bytes. +pub fn block_era(cbor: &[u8]) -> Outcome { let mut tokenizer = Tokenizer::new(cbor); if !matches!(tokenizer.next(), Some(Ok(Token::Array(2)))) { @@ -44,7 +44,7 @@ mod tests { let block_str = include_str!("../../test_data/genesis.block"); let bytes = hex::decode(block_str).unwrap(); - let inference = probe_block_cbor_era(bytes.as_slice()); + let inference = block_era(bytes.as_slice()); assert!(matches!(inference, Outcome::EpochBoundary)); } @@ -54,7 +54,7 @@ mod tests { let block_str = include_str!("../../test_data/byron1.block"); let bytes = hex::decode(block_str).unwrap(); - let inference = probe_block_cbor_era(bytes.as_slice()); + let inference = block_era(bytes.as_slice()); assert!(matches!(inference, Outcome::Matched(Era::Byron))); } @@ -64,7 +64,7 @@ mod tests { let block_str = include_str!("../../test_data/shelley1.block"); let bytes = hex::decode(block_str).unwrap(); - let inference = probe_block_cbor_era(bytes.as_slice()); + let inference = block_era(bytes.as_slice()); assert!(matches!(inference, Outcome::Matched(Era::Shelley))); } @@ -74,7 +74,7 @@ mod tests { let block_str = include_str!("../../test_data/allegra1.block"); let bytes = hex::decode(block_str).unwrap(); - let inference = probe_block_cbor_era(bytes.as_slice()); + let inference = block_era(bytes.as_slice()); assert!(matches!(inference, Outcome::Matched(Era::Allegra))); } @@ -84,7 +84,7 @@ mod tests { let block_str = include_str!("../../test_data/mary1.block"); let bytes = hex::decode(block_str).unwrap(); - let inference = probe_block_cbor_era(bytes.as_slice()); + let inference = block_era(bytes.as_slice()); assert!(matches!(inference, Outcome::Matched(Era::Mary))); } @@ -94,7 +94,7 @@ mod tests { let block_str = include_str!("../../test_data/alonzo1.block"); let bytes = hex::decode(block_str).unwrap(); - let inference = probe_block_cbor_era(bytes.as_slice()); + let inference = block_era(bytes.as_slice()); assert!(matches!(inference, Outcome::Matched(Era::Alonzo))); } diff --git a/pallas-traverse/src/tx.rs b/pallas-traverse/src/tx.rs new file mode 100644 index 0000000..15b6336 --- /dev/null +++ b/pallas-traverse/src/tx.rs @@ -0,0 +1,13 @@ +use pallas_primitives::{alonzo, byron}; + +use crate::MultiEraTx; + +impl<'b> MultiEraTx<'b> { + pub fn from_byron(tx: byron::MintedTxPayload<'b>) -> Self { + Self::Byron(Box::new(tx)) + } + + pub fn from_alonzo_compatible(tx: alonzo::MintedTx<'b>) -> Self { + Self::AlonzoCompatible(Box::new(tx)) + } +} diff --git a/pallas/Cargo.toml b/pallas/Cargo.toml index 2a00532..3ad0813 100644 --- a/pallas/Cargo.toml +++ b/pallas/Cargo.toml @@ -16,5 +16,6 @@ authors = [ pallas-multiplexer = { version = "0.11.0-alpha.0", path = "../pallas-multiplexer/" } pallas-miniprotocols = { version = "0.11.0-alpha.0", path = "../pallas-miniprotocols/" } pallas-primitives = { version = "0.11.0-alpha.0", path = "../pallas-primitives/" } +pallas-traverse = { version = "0.11.0-alpha.0", path = "../pallas-traverse/" } pallas-crypto = { version = "0.11.0-alpha.0", path = "../pallas-crypto/" } pallas-codec = { version = "0.11.0-alpha.0", path = "../pallas-codec/" } diff --git a/pallas/src/ledger.rs b/pallas/src/ledger.rs index 81be084..70fa942 100644 --- a/pallas/src/ledger.rs +++ b/pallas/src/ledger.rs @@ -2,3 +2,6 @@ #[doc(inline)] pub use pallas_primitives as primitives; + +#[doc(inline)] +pub use pallas_traverse as traverse;