feat: Add Vasil / Babbage compatibility (#126)

* feat: Bump n2n protocol versions for Babbage (#125)
* feat: Allow a specified timeout on tcp connection (#127)
* feat: Add Babbage primitives (#128)
* fix: Inaccurate header-body CDDL (#129)
* fix: Babbage CBOR codec issues (#130)
* feat: Include Babbage in traverse lib
* Parse Babbage headers (#131)
* Add Babbage nonce/leader vrf extension (#132)

Co-authored-by: Andrew Westberg <andrewwestberg@gmail.com>
This commit is contained in:
Santiago Carmuega 2022-06-20 22:09:42 -03:00 committed by GitHub
parent dd42b951a3
commit 4e08620bc4
22 changed files with 1826 additions and 90 deletions

View file

@ -2,7 +2,7 @@ use std::borrow::Cow;
use pallas_codec::minicbor;
use pallas_crypto::hash::Hash;
use pallas_primitives::{alonzo, byron, ToHash};
use pallas_primitives::{alonzo, babbage, byron, ToHash};
use crate::{probe, support, Era, Error, MultiEraBlock, MultiEraTx};
@ -63,6 +63,13 @@ impl<'b> MultiEraBlock<'b> {
))
}
pub fn decode_babbage(cbor: &'b [u8]) -> Result<Self, Error> {
let (_, block): BlockWrapper<babbage::MintedBlock> =
minicbor::decode(cbor).map_err(Error::invalid_cbor)?;
Ok(Self::Babbage(Box::new(Cow::Owned(block))))
}
pub fn decode(cbor: &'b [u8]) -> Result<MultiEraBlock<'b>, Error> {
match probe::block_era(cbor) {
probe::Outcome::EpochBoundary => Self::decode_epoch_boundary(cbor),
@ -72,6 +79,7 @@ impl<'b> MultiEraBlock<'b> {
Era::Allegra => Self::decode_allegra(cbor),
Era::Mary => Self::decode_mary(cbor),
Era::Alonzo => Self::decode_alonzo(cbor),
Era::Babbage => Self::decode_babbage(cbor),
},
probe::Outcome::Inconclusive => Err(Error::unknown_cbor(cbor)),
}
@ -81,6 +89,7 @@ impl<'b> MultiEraBlock<'b> {
match self {
MultiEraBlock::EpochBoundary(_) => Era::Byron,
MultiEraBlock::AlonzoCompatible(_, x) => *x,
MultiEraBlock::Babbage(_) => Era::Babbage,
MultiEraBlock::Byron(_) => Era::Byron,
}
}
@ -89,6 +98,7 @@ impl<'b> MultiEraBlock<'b> {
match self {
MultiEraBlock::EpochBoundary(x) => x.header.to_hash(),
MultiEraBlock::AlonzoCompatible(x, _) => x.header.to_hash(),
MultiEraBlock::Babbage(x) => x.header.to_hash(),
MultiEraBlock::Byron(x) => x.header.to_hash(),
}
}
@ -97,6 +107,7 @@ impl<'b> MultiEraBlock<'b> {
match self {
MultiEraBlock::EpochBoundary(x) => x.header.to_abs_slot(),
MultiEraBlock::AlonzoCompatible(x, _) => x.header.header_body.slot,
MultiEraBlock::Babbage(x) => x.header.header_body.slot,
MultiEraBlock::Byron(x) => x.header.consensus_data.0.to_abs_slot(),
}
}
@ -107,6 +118,10 @@ impl<'b> MultiEraBlock<'b> {
.into_iter()
.map(|x| MultiEraTx::AlonzoCompatible(Box::new(Cow::Owned(x)), *era))
.collect(),
MultiEraBlock::Babbage(x) => support::clone_babbage_txs(x)
.into_iter()
.map(|x| MultiEraTx::Babbage(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))))
@ -119,6 +134,16 @@ impl<'b> MultiEraBlock<'b> {
match self {
MultiEraBlock::EpochBoundary(_) => None,
MultiEraBlock::AlonzoCompatible(x, _) => Some(x),
MultiEraBlock::Babbage(_) => None,
MultiEraBlock::Byron(_) => None,
}
}
pub fn as_babbage(&self) -> Option<&babbage::MintedBlock> {
match self {
MultiEraBlock::EpochBoundary(_) => None,
MultiEraBlock::AlonzoCompatible(_, _) => None,
MultiEraBlock::Babbage(x) => Some(x),
MultiEraBlock::Byron(_) => None,
}
}
@ -127,6 +152,7 @@ impl<'b> MultiEraBlock<'b> {
match self {
MultiEraBlock::EpochBoundary(_) => None,
MultiEraBlock::AlonzoCompatible(_, _) => None,
MultiEraBlock::Babbage(_) => None,
MultiEraBlock::Byron(x) => Some(x),
}
}

View file

@ -1,17 +1,18 @@
use std::fmt::{Display, Formatter};
use crate::{Era, Feature};
impl Era {
#[allow(clippy::match_like_matches_macro)]
pub fn has_feature(&self, feature: Feature) -> bool {
match (self, feature) {
(Era::Byron, _) => false,
(Era::Shelley, Feature::SmartContracts) => false,
(Era::Shelley, Feature::TimeLocks) => false,
(Era::Shelley, Feature::MultiAssets) => false,
(Era::Allegra, Feature::MultiAssets) => false,
(Era::Allegra, Feature::SmartContracts) => false,
(Era::Mary, Feature::SmartContracts) => false,
_ => true,
match feature {
Feature::Staking => self.ge(&Era::Shelley),
Feature::MultiAssets => self.ge(&Era::Mary),
Feature::TimeLocks => self.ge(&Era::Allegra),
Feature::SmartContracts => self.ge(&Era::Alonzo),
Feature::CIP31 => self.ge(&Era::Babbage),
Feature::CIP32 => self.ge(&Era::Babbage),
Feature::CIP33 => self.ge(&Era::Babbage),
}
}
}
@ -29,7 +30,8 @@ impl TryFrom<u16> for Era {
3 => Ok(Era::Allegra),
4 => Ok(Era::Mary),
5 => Ok(Era::Alonzo),
x => Err(crate::Error::UnkownEra(x)),
6 => Ok(Era::Babbage),
x => Err(crate::Error::UnknownEra(x)),
}
}
}
@ -42,6 +44,20 @@ impl From<Era> for u16 {
Era::Allegra => 3,
Era::Mary => 4,
Era::Alonzo => 5,
Era::Babbage => 6,
}
}
}
impl Display for Era {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Era::Byron => write!(f, "Byron"),
Era::Shelley => write!(f, "Shelley"),
Era::Allegra => write!(f, "Allegra"),
Era::Mary => write!(f, "Mary"),
Era::Alonzo => write!(f, "Alonzo"),
Era::Babbage => write!(f, "Babbage"),
}
}
}

View file

@ -1,7 +1,8 @@
use pallas_codec::minicbor;
use pallas_crypto::hash::Hash;
use pallas_crypto::hash::{Hash, Hasher};
use pallas_primitives::ToHash;
use crate::Era::Byron;
use crate::{Error, MultiEraHeader};
impl<'b> MultiEraHeader<'b> {
@ -17,6 +18,10 @@ impl<'b> MultiEraHeader<'b> {
Ok(MultiEraHeader::Byron(header))
}
},
5 => {
let header = minicbor::decode(cbor).map_err(Error::invalid_cbor)?;
Ok(MultiEraHeader::Babbage(header))
}
_ => {
let header = minicbor::decode(cbor).map_err(Error::invalid_cbor)?;
Ok(MultiEraHeader::AlonzoCompatible(header))
@ -28,6 +33,7 @@ impl<'b> MultiEraHeader<'b> {
match self {
MultiEraHeader::EpochBoundary(x) => x.to_abs_slot(),
MultiEraHeader::AlonzoCompatible(x) => x.header_body.slot,
MultiEraHeader::Babbage(x) => x.header_body.slot,
MultiEraHeader::Byron(x) => x.consensus_data.0.to_abs_slot(),
}
}
@ -36,7 +42,34 @@ impl<'b> MultiEraHeader<'b> {
match self {
MultiEraHeader::EpochBoundary(x) => x.to_hash(),
MultiEraHeader::AlonzoCompatible(x) => x.to_hash(),
MultiEraHeader::Babbage(x) => x.to_hash(),
MultiEraHeader::Byron(x) => x.to_hash(),
}
}
pub fn leader_vrf_output(&self) -> Result<Vec<u8>, Error> {
match self {
MultiEraHeader::EpochBoundary(_) => Err(Error::InvalidEra(Byron)),
MultiEraHeader::AlonzoCompatible(x) => Ok(x.header_body.leader_vrf.0.to_vec()),
MultiEraHeader::Babbage(x) => {
let mut leader_tagged_vrf: Vec<u8> = vec![0x4C_u8]; /* "L" */
leader_tagged_vrf.extend(&*x.header_body.vrf_result.0);
Ok(Hasher::<256>::hash(&leader_tagged_vrf).to_vec())
}
MultiEraHeader::Byron(_) => Err(Error::InvalidEra(Byron)),
}
}
pub fn nonce_vrf_output(&self) -> Result<Vec<u8>, Error> {
match self {
MultiEraHeader::EpochBoundary(_) => Err(Error::InvalidEra(Byron)),
MultiEraHeader::AlonzoCompatible(x) => Ok(x.header_body.nonce_vrf.0.to_vec()),
MultiEraHeader::Babbage(x) => {
let mut nonce_tagged_vrf: Vec<u8> = vec![0x4E_u8]; /* "N" */
nonce_tagged_vrf.extend(&*x.header_body.vrf_result.0);
Ok(Hasher::<256>::hash(&nonce_tagged_vrf).to_vec())
}
MultiEraHeader::Byron(_) => Err(Error::InvalidEra(Byron)),
}
}
}

View file

@ -3,10 +3,11 @@
use std::borrow::Cow;
use std::fmt::Display;
use thiserror::Error;
use pallas_codec::utils::KeepRaw;
use pallas_crypto::hash::Hash;
use pallas_primitives::{alonzo, byron};
use thiserror::Error;
use pallas_primitives::{alonzo, babbage, byron};
pub mod block;
pub mod cert;
@ -26,6 +27,7 @@ pub enum Era {
Allegra, // time-locks
Mary, // multi-assets
Alonzo, // smart-contracts
Babbage, // CIP-31/32/33
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
@ -35,12 +37,16 @@ pub enum Feature {
MultiAssets,
Staking,
SmartContracts,
CIP31,
CIP32,
CIP33,
}
#[derive(Debug)]
pub enum MultiEraHeader<'b> {
EpochBoundary(KeepRaw<'b, byron::EbbHead>),
AlonzoCompatible(KeepRaw<'b, alonzo::Header>),
Babbage(KeepRaw<'b, babbage::Header>),
Byron(KeepRaw<'b, byron::BlockHead>),
}
@ -49,6 +55,7 @@ pub enum MultiEraHeader<'b> {
pub enum MultiEraBlock<'b> {
EpochBoundary(Box<Cow<'b, byron::EbBlock>>),
AlonzoCompatible(Box<Cow<'b, alonzo::MintedBlock<'b>>>, Era),
Babbage(Box<Cow<'b, babbage::MintedBlock<'b>>>),
Byron(Box<Cow<'b, byron::MintedBlock<'b>>>),
}
@ -56,14 +63,16 @@ pub enum MultiEraBlock<'b> {
#[non_exhaustive]
pub enum MultiEraTx<'b> {
AlonzoCompatible(Box<Cow<'b, alonzo::MintedTx<'b>>>, Era),
Babbage(Box<Cow<'b, babbage::MintedTx<'b>>>),
Byron(Box<Cow<'b, byron::MintedTxPayload<'b>>>),
}
#[derive(Debug)]
#[non_exhaustive]
pub enum MultiEraOutput<'b> {
Byron(Box<Cow<'b, byron::TxOut>>),
AlonzoCompatible(Box<Cow<'b, alonzo::TransactionOutput>>),
Babbage(Box<Cow<'b, babbage::TransactionOutput>>),
Byron(Box<Cow<'b, byron::TxOut>>),
}
#[derive(Debug)]
@ -89,7 +98,10 @@ pub enum Error {
UnknownCbor(String),
#[error("Unknown era tag: {0}")]
UnkownEra(u16),
UnknownEra(u16),
#[error("Invalid era for request: {0}")]
InvalidEra(Era),
}
impl Error {

View file

@ -1,6 +1,6 @@
use std::borrow::Cow;
use std::{borrow::Cow, ops::Deref};
use pallas_primitives::{alonzo::{self, Value}, byron};
use pallas_primitives::{alonzo, babbage, byron};
use crate::MultiEraOutput;
@ -13,36 +13,61 @@ impl<'b> MultiEraOutput<'b> {
Self::AlonzoCompatible(Box::new(Cow::Borrowed(output)))
}
pub fn from_babbage(output: &'b babbage::TransactionOutput) -> Self {
Self::Babbage(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")
}
MultiEraOutput::Babbage(x) => x.to_bech32_address(hrp).expect("invalid address value"),
MultiEraOutput::Byron(x) => x.address.to_addr_string().expect("invalid address value"),
}
}
pub fn ada_amount(&self) -> u64 {
match self {
MultiEraOutput::Byron(x) => x.amount,
MultiEraOutput::Babbage(x) => match x.deref().deref() {
babbage::TransactionOutput::Legacy(x) => match x.amount {
babbage::Value::Coin(c) => u64::from(c),
babbage::Value::Multiasset(c, _) => u64::from(c),
},
babbage::TransactionOutput::PostAlonzo(x) => match x.value {
babbage::Value::Coin(c) => u64::from(c),
babbage::Value::Multiasset(c, _) => u64::from(c),
},
},
MultiEraOutput::AlonzoCompatible(x) => match x.amount {
Value::Coin(c) => u64::from(c),
Value::Multiasset(c, _) => u64::from(c)
}
alonzo::Value::Coin(c) => u64::from(c),
alonzo::Value::Multiasset(c, _) => u64::from(c),
},
}
}
pub fn as_babbage(&self) -> Option<&babbage::TransactionOutput> {
match self {
MultiEraOutput::AlonzoCompatible(_) => None,
MultiEraOutput::Babbage(x) => Some(x),
MultiEraOutput::Byron(_) => None,
}
}
pub fn as_alonzo(&self) -> Option<&alonzo::TransactionOutput> {
match self {
MultiEraOutput::Byron(_) => None,
MultiEraOutput::AlonzoCompatible(x) => Some(x),
MultiEraOutput::Babbage(_) => None,
MultiEraOutput::Byron(_) => None,
}
}
pub fn as_byron(&self) -> Option<&byron::TxOut> {
match self {
MultiEraOutput::Byron(x) => Some(x),
MultiEraOutput::AlonzoCompatible(_) => None,
MultiEraOutput::Babbage(_) => None,
MultiEraOutput::Byron(x) => Some(x),
}
}
}

View file

@ -1,44 +1,57 @@
//! Internal supporting utilities
use pallas_primitives::{alonzo, byron};
use pallas_primitives::{alonzo, babbage, byron};
pub 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()?;
macro_rules! clone_tx_fn {
($fn_name:ident, $era:tt) => {
fn $fn_name<'b>(block: &'b $era::MintedBlock, index: usize) -> Option<$era::MintedTx<'b>> {
let transaction_body = block.transaction_bodies.get(index).cloned()?;
let transaction_witness_set = block.transaction_witness_sets.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 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();
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,
})
let x = $era::MintedTx {
transaction_body,
transaction_witness_set,
success,
auxiliary_data,
};
Some(x)
}
};
}
clone_tx_fn!(babbage_clone_tx_at, babbage);
clone_tx_fn!(alonzo_clone_tx_at, alonzo);
pub fn clone_alonzo_txs<'b>(block: &'b alonzo::MintedBlock) -> Vec<alonzo::MintedTx<'b>> {
(0..block.transaction_bodies.len())
.step_by(1)
.filter_map(|idx| clone_alonzo_tx_at(block, idx))
.filter_map(|idx| alonzo_clone_tx_at(block, idx))
.collect()
}
pub fn clone_babbage_txs<'b>(block: &'b babbage::MintedBlock) -> Vec<babbage::MintedTx<'b>> {
(0..block.transaction_bodies.len())
.step_by(1)
.filter_map(|idx| babbage_clone_tx_at(block, idx))
.collect()
}

View file

@ -1,6 +1,6 @@
use pallas_codec::minicbor;
use pallas_crypto::hash::Hash;
use pallas_primitives::{alonzo, byron, ToHash};
use pallas_primitives::{alonzo, babbage, byron, ToHash};
use std::borrow::Cow;
use crate::{Era, MultiEraCert, MultiEraInput, MultiEraOutput, MultiEraTx};
@ -14,9 +14,14 @@ impl<'b> MultiEraTx<'b> {
Self::AlonzoCompatible(Box::new(Cow::Borrowed(tx)), era)
}
pub fn from_babbage(tx: &'b babbage::MintedTx<'b>) -> Self {
Self::Babbage(Box::new(Cow::Borrowed(tx)))
}
pub fn encode(&self) -> Result<Vec<u8>, minicbor::encode::Error<std::io::Error>> {
match self {
MultiEraTx::AlonzoCompatible(x, _) => minicbor::to_vec(x),
MultiEraTx::Babbage(x) => minicbor::to_vec(x),
MultiEraTx::Byron(x) => minicbor::to_vec(x),
}
}
@ -33,12 +38,18 @@ impl<'b> MultiEraTx<'b> {
let tx = Box::new(Cow::Owned(tx));
Ok(MultiEraTx::AlonzoCompatible(tx, era))
}
Era::Babbage => {
let tx = minicbor::decode(cbor)?;
let tx = Box::new(Cow::Owned(tx));
Ok(MultiEraTx::Babbage(tx))
}
}
}
pub fn era(&self) -> Era {
match self {
MultiEraTx::AlonzoCompatible(_, era) => *era,
MultiEraTx::Babbage(_) => Era::Babbage,
MultiEraTx::Byron(_) => Era::Byron,
}
}
@ -46,6 +57,7 @@ impl<'b> MultiEraTx<'b> {
pub fn hash(&self) -> Hash<32> {
match self {
MultiEraTx::AlonzoCompatible(x, _) => x.transaction_body.to_hash(),
MultiEraTx::Babbage(x) => x.transaction_body.to_hash(),
MultiEraTx::Byron(x) => x.transaction.to_hash(),
}
}
@ -58,6 +70,12 @@ impl<'b> MultiEraTx<'b> {
.iter()
.map(MultiEraOutput::from_alonzo_compatible)
.collect(),
MultiEraTx::Babbage(x) => x
.transaction_body
.outputs
.iter()
.map(MultiEraOutput::from_babbage)
.collect(),
MultiEraTx::Byron(x) => x
.transaction
.outputs
@ -74,6 +92,11 @@ impl<'b> MultiEraTx<'b> {
.outputs
.get(index)
.map(MultiEraOutput::from_alonzo_compatible),
MultiEraTx::Babbage(x) => x
.transaction_body
.outputs
.get(index)
.map(MultiEraOutput::from_babbage),
MultiEraTx::Byron(x) => x
.transaction
.outputs
@ -90,7 +113,12 @@ impl<'b> MultiEraTx<'b> {
.iter()
.map(MultiEraInput::from_alonzo_compatible)
.collect(),
MultiEraTx::Babbage(x) => x
.transaction_body
.inputs
.iter()
.map(MultiEraInput::from_alonzo_compatible)
.collect(),
MultiEraTx::Byron(x) => x
.transaction
.inputs
@ -109,13 +137,29 @@ impl<'b> MultiEraTx<'b> {
.flat_map(|c| c.iter())
.map(|c| MultiEraCert::AlonzoCompatible(Box::new(Cow::Borrowed(c))))
.collect(),
MultiEraTx::Babbage(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_babbage(&self) -> Option<&babbage::MintedTx> {
match self {
MultiEraTx::AlonzoCompatible(_, _) => None,
MultiEraTx::Babbage(x) => Some(x),
MultiEraTx::Byron(_) => None,
}
}
pub fn as_alonzo(&self) -> Option<&alonzo::MintedTx> {
match self {
MultiEraTx::AlonzoCompatible(x, _) => Some(x),
MultiEraTx::Babbage(_) => None,
MultiEraTx::Byron(_) => None,
}
}
@ -123,6 +167,7 @@ impl<'b> MultiEraTx<'b> {
pub fn as_byron(&self) -> Option<&byron::MintedTxPayload> {
match self {
MultiEraTx::AlonzoCompatible(_, _) => None,
MultiEraTx::Babbage(_) => None,
MultiEraTx::Byron(x) => Some(x),
}
}