feat: Introduce 'traverse' library (#117)
This commit is contained in:
parent
c9489c3ddf
commit
6913efef78
20 changed files with 377 additions and 106 deletions
63
pallas-traverse/src/block.rs
Normal file
63
pallas-traverse/src/block.rs
Normal 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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
96
pallas-traverse/src/iter.rs
Normal file
96
pallas-traverse/src/iter.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
54
pallas-traverse/src/lib.rs
Normal file
54
pallas-traverse/src/lib.rs
Normal 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))
|
||||
}
|
||||
}
|
||||
101
pallas-traverse/src/probe.rs
Normal file
101
pallas-traverse/src/probe.rs
Normal 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
13
pallas-traverse/src/tx.rs
Normal 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))
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue