Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions rust/cardano-blockchain-types/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
name = "cardano-blockchain-types"
description = "Common Cardano Blockchain data types for use in both applications and crates"
keywords = ["cardano", "catalyst", ]
version = "0.0.2"
version = "0.0.3"
authors = [
"Steven Johnson <steven.johnson@iohk.io>"
]
Expand All @@ -20,8 +20,8 @@ workspace = true
[dependencies]
pallas = { version = "0.30.1", git = "https://github.com/input-output-hk/catalyst-pallas.git", rev = "9b5183c8b90b90fe2cc319d986e933e9518957b3" }
# pallas-hardano = { version = "0.30.1", git = "https://github.com/input-output-hk/catalyst-pallas.git", rev = "9b5183c8b90b90fe2cc319d986e933e9518957b3" }
cbork-utils = { version = "0.0.1", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "r20250218-00" }
catalyst-types = { version = "0.0.2", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "r20250218-00" }
cbork-utils = { version = "0.0.1", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "r20250220-00" }
catalyst-types = { version = "0.0.3", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "r20250220-00" }

ouroboros = "0.18.4"
tracing = "0.1.41"
Expand Down
4 changes: 2 additions & 2 deletions rust/cardano-blockchain-types/src/hashes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ use catalyst_types::{
};

define_hashes!(
/// A transaction hash - Blake2b-256 hash of a transaction.
(TransactionHash, Blake2b256Hash),
/// A transaction ID - Blake2b-256 hash of a transaction.
(TransactionId, Blake2b256Hash),
/// A public key hash - raw Blake2b-224 hash of an Ed25519 public key (has no discriminator, just the hash).
(PubKeyHash, Blake2b224Hash),
);
4 changes: 3 additions & 1 deletion rust/cardano-blockchain-types/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ mod multi_era_block_data;
mod network;
mod point;
mod slot;
mod stake_address;
mod txn_index;
mod txn_output_offset;
mod txn_witness;
Expand All @@ -23,12 +24,13 @@ pub use auxdata::{
};
pub use cip134_uri::Cip0134Uri;
pub use fork::Fork;
pub use hashes::{PubKeyHash, TransactionHash};
pub use hashes::{PubKeyHash, TransactionId};
pub use metadata::cip36::{voting_pk::VotingPubKey, Cip36};
pub use multi_era_block_data::MultiEraBlock;
pub use network::Network;
pub use point::Point;
pub use slot::Slot;
pub use stake_address::StakeAddress;
pub use txn_index::TxnIndex;
pub use txn_output_offset::TxnOutputOffset;
pub use txn_witness::{TxnWitness, VKeyHash};
26 changes: 23 additions & 3 deletions rust/cardano-blockchain-types/src/network.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,13 @@

use std::{ffi::OsStr, path::PathBuf};

use anyhow::anyhow;
use catalyst_types::conversion::from_saturating;
use chrono::{DateTime, Utc};
use pallas::{
ledger::traverse::wellknown::GenesisValues,
ledger::{addresses::Network as PallasNetwork, traverse::wellknown::GenesisValues},
network::miniprotocols::{MAINNET_MAGIC, PREVIEW_MAGIC, PRE_PRODUCTION_MAGIC},
};
// use strum::IntoEnumIterator;
// use strum_macros;
use tracing::debug;

use crate::Slot;
Expand Down Expand Up @@ -220,6 +219,27 @@ impl From<Network> for u64 {
}
}

impl From<Network> for PallasNetwork {
fn from(value: Network) -> Self {
match value {
Network::Mainnet => PallasNetwork::Mainnet,
Network::Preprod | Network::Preview => PallasNetwork::Testnet,
}
}
}

impl TryFrom<PallasNetwork> for Network {
type Error = anyhow::Error;

fn try_from(value: PallasNetwork) -> Result<Self, Self::Error> {
match value {
PallasNetwork::Mainnet => Ok(Network::Mainnet),
PallasNetwork::Testnet => Ok(Network::Preprod),
n @ PallasNetwork::Other(_) => Err(anyhow!("Unsupported network: {n:?}")),
}
}
}

#[cfg(test)]
mod tests {
use std::str::FromStr;
Expand Down
234 changes: 234 additions & 0 deletions rust/cardano-blockchain-types/src/stake_address.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
//! A stake address.

// cspell: words Scripthash, Keyhash

use std::fmt::{Display, Formatter};

use anyhow::{anyhow, Context};
use pallas::{
crypto::hash::Hash,
ledger::{
addresses::{
ShelleyAddress, ShelleyDelegationPart, ShelleyPaymentPart,
StakeAddress as PallasStakeAddress,
},
primitives::conway,
},
};

use crate::Network;

/// A stake address.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Hash)]
pub struct StakeAddress(PallasStakeAddress);

impl StakeAddress {
/// Creates a new instance from the given parameters.
#[allow(clippy::expect_used, clippy::missing_panics_doc)]
#[must_use]
pub fn new(network: Network, is_script: bool, hash: Hash<28>) -> Self {
let network = network.into();
// `pallas::StakeAddress` can only be constructed from `ShelleyAddress`, so we are forced
// to create a dummy shelley address. The input hash parameter is used to construct both
// payment and delegation parts, but the payment part isn't used in the stake address
// construction, so it doesn't matter.
let payment = ShelleyPaymentPart::Key(hash);
let delegation = if is_script {
ShelleyDelegationPart::Script(hash)
} else {
ShelleyDelegationPart::Key(hash)
};
let address = ShelleyAddress::new(network, payment, delegation);
// This conversion can only fail if the delegation part isn't key or script, but we know
// it is valid because we construct it just above.
let address = address.try_into().expect("Unexpected delegation part");
Self(address)
}

/// Creates `StakeAddress` from `StakeCredential`.
#[must_use]
pub fn from_stake_cred(network: Network, cred: &conway::StakeCredential) -> Self {
match cred {
conway::StakeCredential::Scripthash(h) => Self::new(network, true, *h),
conway::StakeCredential::AddrKeyhash(h) => Self::new(network, false, *h),
}
}

/// Returns true if it is a script address.
#[must_use]
pub fn is_script(&self) -> bool {
self.0.is_script()
}
}

impl From<PallasStakeAddress> for StakeAddress {
fn from(value: PallasStakeAddress) -> Self {
Self(value)
}
}

impl TryFrom<ShelleyAddress> for StakeAddress {
type Error = anyhow::Error;

fn try_from(value: ShelleyAddress) -> Result<Self, Self::Error> {
let address = PallasStakeAddress::try_from(value.clone())
.with_context(|| format!("Unable to get stake address from {value:?}"))?;
Ok(Self(address))
}
}

impl TryFrom<&[u8]> for StakeAddress {
type Error = anyhow::Error;

fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
/// A stake address length in bytes.
const ADDRESS_LENGTH: usize = 29;
/// A hash length in bytes.
const HASH_LENGTH: usize = 28;

let (header, hash) = match bytes {
[header, hash @ ..] if hash.len() == HASH_LENGTH => (header, Hash::<28>::from(hash)),
_ => {
return Err(anyhow!(
"Invalid bytes length: {}, expected {ADDRESS_LENGTH}",
bytes.len()
));
},
};

// The network part stored in the last four bits of the header.
let network = match header & 0b0000_1111 {
0 => Network::Preprod,
1 => Network::Mainnet,
v => return Err(anyhow!("Unexpected network value: {v}, header = {header}")),
};

// The 'type' (stake or script) is stored in the first four bits of the header.
let type_ = header >> 4;
let is_script = match type_ {
0b1110 => false,
0b1111 => true,
v => return Err(anyhow!("Unexpected type value: {v}, header = {header}")),
};

Ok(Self::new(network, is_script, hash))
}
}

/// This conversion returns a 29 bytes value that includes both header and hash.
impl From<StakeAddress> for Vec<u8> {
fn from(value: StakeAddress) -> Self {
value.0.to_vec()
}
}

impl Display for StakeAddress {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
// The `to_bech32` implementation returns an error if the network isn't equal to testnet
// or mainnet. We don't allow other networks, so it is safe to unwrap, but just in case
// return a debug representation.
let bech32 = self
.0
.to_bech32()
.unwrap_or_else(|_| format!("{:?}", self.0));
write!(f, "{bech32}")
}
}

#[cfg(test)]
mod tests {
use super::*;

#[allow(clippy::indexing_slicing)]
#[test]
fn roundtrip() {
let hash: Hash<28> = "276fd18711931e2c0e21430192dbeac0e458093cd9d1fcd7210f64b3"
.parse()
.unwrap();
let test_data = [
(Network::Mainnet, true, hash, 0b1111_0001),
(Network::Mainnet, false, hash, 0b1110_0001),
(Network::Preprod, true, hash, 0b1111_0000),
(Network::Preprod, false, hash, 0b1110_0000),
(Network::Preview, true, hash, 0b1111_0000),
(Network::Preview, false, hash, 0b1110_0000),
];

for (network, is_script, hash, expected_header) in test_data {
let stake_address = StakeAddress::new(network, is_script, hash);
assert_eq!(stake_address.is_script(), is_script);

// Check that conversion to bytes includes the expected header value.
let bytes: Vec<_> = stake_address.clone().into();
assert_eq!(29, bytes.len(), "Invalid length for {network} {is_script}");
assert_eq!(
&bytes[1..],
hash.as_ref(),
"Invalid hash for {network} {is_script}"
);
assert_eq!(
expected_header,
*bytes.first().unwrap(),
"Invalid header for {network} {is_script}"
);

// Check that it is possible to create an address from the bytes.
let from_bytes = StakeAddress::try_from(bytes.as_slice()).unwrap();
assert_eq!(from_bytes.is_script(), is_script);
assert_eq!(from_bytes, stake_address);
}
}

#[test]
fn display() {
let hash: Hash<28> = "276fd18711931e2c0e21430192dbeac0e458093cd9d1fcd7210f64b3"
.parse()
.unwrap();

// cSpell:disable
let test_data = [
(
Network::Mainnet,
true,
hash,
"stake17ynkl5v8zxf3utqwy9psrykmatqwgkqf8nvarlxhyy8kfvcpxcgqv",
),
(
Network::Mainnet,
false,
hash,
"stake1uynkl5v8zxf3utqwy9psrykmatqwgkqf8nvarlxhyy8kfvcgwyghv",
),
(
Network::Preprod,
true,
hash,
"stake_test17qnkl5v8zxf3utqwy9psrykmatqwgkqf8nvarlxhyy8kfvcxvj2y3",
),
(
Network::Preprod,
false,
hash,
"stake_test1uqnkl5v8zxf3utqwy9psrykmatqwgkqf8nvarlxhyy8kfvc0yw2n3",
),
(
Network::Preview,
true,
hash,
"stake_test17qnkl5v8zxf3utqwy9psrykmatqwgkqf8nvarlxhyy8kfvcxvj2y3",
),
(
Network::Preview,
false,
hash,
"stake_test1uqnkl5v8zxf3utqwy9psrykmatqwgkqf8nvarlxhyy8kfvc0yw2n3",
),
];
// cSpell:enable

for (network, is_script, hash, expected) in test_data {
let address = StakeAddress::new(network, is_script, hash);
assert_eq!(expected, format!("{address}"));
}
}
}
8 changes: 4 additions & 4 deletions rust/cardano-chain-follower/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "cardano-chain-follower"
version = "0.0.7"
version = "0.0.8"
edition.workspace = true
authors.workspace = true
homepage.workspace = true
Expand All @@ -19,8 +19,8 @@ mithril-client = { version = "0.10.4", default-features = false, features = [
"full",
"num-integer-backend",
] }
cardano-blockchain-types = { version = "0.0.2", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "r20250218-00" }
catalyst-types = { version = "0.0.2", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "r20250218-00" }
cardano-blockchain-types = { version = "0.0.3", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "r20250220-00" }
catalyst-types = { version = "0.0.3", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "r20250220-00" }

thiserror = "1.0.69"
tokio = { version = "1.42.0", features = [
Expand Down Expand Up @@ -63,7 +63,7 @@ test-log = { version = "0.2.16", default-features = false, features = [
"trace",
] }
clap = "4.5.23"
rbac-registration = { version = "0.0.3", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "r20250218-00" }
rbac-registration = { version = "0.0.4", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "r20250220-00" }

# Note, these features are for support of features exposed by dependencies.
[features]
Expand Down
2 changes: 1 addition & 1 deletion rust/catalyst-types/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "catalyst-types"
version = "0.0.2"
version = "0.0.3"
edition.workspace = true
license.workspace = true
authors.workspace = true
Expand Down
10 changes: 5 additions & 5 deletions rust/rbac-registration/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
name = "rbac-registration"
description = "Role Based Access Control Registration"
keywords = ["cardano", "catalyst", "rbac registration"]
version = "0.0.3"
version = "0.0.4"
authors = [
"Arissara Chotivichit <arissara.chotivichit@iohk.io>"
]
Expand Down Expand Up @@ -30,8 +30,8 @@ tracing = "0.1.40"
ed25519-dalek = "2.1.1"
uuid = "1.11.0"

c509-certificate = { version = "0.0.3", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "r20250218-00" }
c509-certificate = { version = "0.0.3", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "r20250220-00" }
pallas = { version = "0.30.1", git = "https://github.com/input-output-hk/catalyst-pallas.git", rev = "9b5183c8b90b90fe2cc319d986e933e9518957b3" }
cbork-utils = { version = "0.0.1", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "r20250218-00" }
cardano-blockchain-types = { version = "0.0.2", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "r20250218-00" }
catalyst-types = { version = "0.0.2", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "r20250218-00" }
cbork-utils = { version = "0.0.1", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "r20250220-00" }
cardano-blockchain-types = { version = "0.0.3", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "r20250220-00" }
catalyst-types = { version = "0.0.3", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "r20250220-00" }
Loading
Loading