feat: Introduce 'traverse' library (#117)

This commit is contained in:
Santiago Carmuega 2022-06-12 19:09:33 -03:00 committed by GitHub
parent c9489c3ddf
commit 6913efef78
20 changed files with 377 additions and 106 deletions

View file

@ -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<T> = (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<MultiEraBlock<'b>, Error> {
match probe::block_era(cbor) {
probe::Outcome::EpochBoundary => {
let (_, block): BlockWrapper<byron::EbBlock> =
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<byron::MintedBlock> =
minicbor::decode(cbor).map_err(Error::invalid_cbor)?;
Ok(Self::from_byron(block))
}
Era::Shelley | Era::Allegra | Era::Mary | Era::Alonzo => {
let (_, block): BlockWrapper<alonzo::MintedBlock> =
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(),
}
}
}

View file

@ -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<alonzo::MintedTx<'b>> {
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<byron::MintedTxPayload<'b>> {
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<Self::Item> {
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);
}
}
}

View file

@ -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<alonzo::MintedTx<'b>>),
Byron(Box<byron::MintedTxPayload<'b>>),
}
#[derive(Debug)]
#[non_exhaustive]
pub enum MultiEraBlock<'b> {
EpochBoundary(Box<byron::EbBlock>),
AlonzoCompatible(Box<alonzo::MintedBlock<'b>>),
Byron(Box<byron::MintedBlock<'b>>),
}
#[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))
}
}

View file

@ -0,0 +1,101 @@
//! Lightweight inspection of block data without full CBOR decoding
use pallas_codec::minicbor::decode::{Token, Tokenizer};
use crate::Era;
#[derive(Debug)]
pub enum Outcome {
Matched(Era),
EpochBoundary,
Inconclusive,
}
// Executes a very lightweight inspection of the initial tokens of the CBOR
// 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)))) {
return Outcome::Inconclusive;
}
match tokenizer.next() {
Some(Ok(Token::U8(variant))) => match variant {
0 => Outcome::EpochBoundary,
1 => Outcome::Matched(Era::Byron),
2 => Outcome::Matched(Era::Shelley),
3 => Outcome::Matched(Era::Allegra),
4 => Outcome::Matched(Era::Mary),
5 => Outcome::Matched(Era::Alonzo),
_ => Outcome::Inconclusive,
},
_ => Outcome::Inconclusive,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn genesis_block_detected() {
let block_str = include_str!("../../test_data/genesis.block");
let bytes = hex::decode(block_str).unwrap();
let inference = block_era(bytes.as_slice());
assert!(matches!(inference, Outcome::EpochBoundary));
}
#[test]
fn byron_block_detected() {
let block_str = include_str!("../../test_data/byron1.block");
let bytes = hex::decode(block_str).unwrap();
let inference = block_era(bytes.as_slice());
assert!(matches!(inference, Outcome::Matched(Era::Byron)));
}
#[test]
fn shelley_block_detected() {
let block_str = include_str!("../../test_data/shelley1.block");
let bytes = hex::decode(block_str).unwrap();
let inference = block_era(bytes.as_slice());
assert!(matches!(inference, Outcome::Matched(Era::Shelley)));
}
#[test]
fn allegra_block_detected() {
let block_str = include_str!("../../test_data/allegra1.block");
let bytes = hex::decode(block_str).unwrap();
let inference = block_era(bytes.as_slice());
assert!(matches!(inference, Outcome::Matched(Era::Allegra)));
}
#[test]
fn mary_block_detected() {
let block_str = include_str!("../../test_data/mary1.block");
let bytes = hex::decode(block_str).unwrap();
let inference = block_era(bytes.as_slice());
assert!(matches!(inference, Outcome::Matched(Era::Mary)));
}
#[test]
fn alonzo_block_detected() {
let block_str = include_str!("../../test_data/alonzo1.block");
let bytes = hex::decode(block_str).unwrap();
let inference = block_era(bytes.as_slice());
assert!(matches!(inference, Outcome::Matched(Era::Alonzo)));
}
}

13
pallas-traverse/src/tx.rs Normal file
View file

@ -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))
}
}