From 1c66467877db30bd22479610bee22be00a895cbe Mon Sep 17 00:00:00 2001 From: Golddy Dev Date: Thu, 4 Dec 2025 16:22:55 +0100 Subject: [PATCH] refactor: acropolis codec to be modular --- codec/src/address.rs | 93 ++ codec/src/certs.rs | 368 ++++++ codec/src/lib.rs | 15 +- codec/src/map_parameters.rs | 1154 ----------------- codec/src/parameter.rs | 391 ++++++ codec/src/tx.rs | 83 ++ codec/src/utils.rs | 145 +++ codec/src/utxo.rs | 175 +++ common/src/validation.rs | 15 +- modules/chain_store/src/chain_store.rs | 52 +- modules/tx_unpacker/src/tx_unpacker.rs | 22 +- .../src/validations/shelley/utxo.rs | 27 +- 12 files changed, 1323 insertions(+), 1217 deletions(-) create mode 100644 codec/src/address.rs create mode 100644 codec/src/certs.rs delete mode 100644 codec/src/map_parameters.rs create mode 100644 codec/src/parameter.rs create mode 100644 codec/src/tx.rs create mode 100644 codec/src/utils.rs create mode 100644 codec/src/utxo.rs diff --git a/codec/src/address.rs b/codec/src/address.rs new file mode 100644 index 00000000..bf871e0b --- /dev/null +++ b/codec/src/address.rs @@ -0,0 +1,93 @@ +use acropolis_common::{ + Address, ByronAddress, NetworkId, ShelleyAddress, ShelleyAddressDelegationPart, + ShelleyAddressPaymentPart, ShelleyAddressPointer, StakeAddress, StakeCredential, +}; +use anyhow::Result; +use pallas::ledger::{ + addresses as pallas_addresses, primitives::StakeCredential as PallasStakeCredential, +}; + +use crate::{tx::map_network, utils::to_hash}; + +/// Derive our Address from a Pallas address +// This is essentially a 1:1 mapping but makes the Message definitions independent +// of Pallas +pub fn map_address(address: &pallas_addresses::Address) -> Result
{ + match address { + pallas_addresses::Address::Byron(byron_address) => Ok(Address::Byron(ByronAddress { + payload: byron_address.payload.to_vec(), + })), + + pallas_addresses::Address::Shelley(shelley_address) => { + Ok(Address::Shelley(ShelleyAddress { + network: map_network(shelley_address.network())?, + + payment: match shelley_address.payment() { + pallas_addresses::ShelleyPaymentPart::Key(hash) => { + ShelleyAddressPaymentPart::PaymentKeyHash(to_hash(hash)) + } + pallas_addresses::ShelleyPaymentPart::Script(hash) => { + ShelleyAddressPaymentPart::ScriptHash(to_hash(hash)) + } + }, + + delegation: match shelley_address.delegation() { + pallas_addresses::ShelleyDelegationPart::Null => { + ShelleyAddressDelegationPart::None + } + pallas_addresses::ShelleyDelegationPart::Key(hash) => { + ShelleyAddressDelegationPart::StakeKeyHash(to_hash(hash)) + } + pallas_addresses::ShelleyDelegationPart::Script(hash) => { + ShelleyAddressDelegationPart::ScriptHash(to_hash(hash)) + } + pallas_addresses::ShelleyDelegationPart::Pointer(pointer) => { + ShelleyAddressDelegationPart::Pointer(ShelleyAddressPointer { + slot: pointer.slot(), + tx_index: pointer.tx_idx(), + cert_index: pointer.cert_idx(), + }) + } + }, + })) + } + + pallas_addresses::Address::Stake(stake_address) => Ok(Address::Stake(StakeAddress { + network: map_network(stake_address.network())?, + credential: match stake_address.payload() { + pallas_addresses::StakePayload::Stake(hash) => { + StakeCredential::AddrKeyHash(to_hash(hash)) + } + pallas_addresses::StakePayload::Script(hash) => { + StakeCredential::ScriptHash(to_hash(hash)) + } + }, + })), + } +} + +/// Map a Pallas StakeCredential to ours +pub fn map_stake_credential(cred: &PallasStakeCredential) -> StakeCredential { + match cred { + PallasStakeCredential::AddrKeyhash(key_hash) => { + StakeCredential::AddrKeyHash(to_hash(key_hash)) + } + PallasStakeCredential::ScriptHash(script_hash) => { + StakeCredential::ScriptHash(to_hash(script_hash)) + } + } +} + +/// Map a PallasStakeCredential to our StakeAddress +pub fn map_stake_address(cred: &PallasStakeCredential, network_id: NetworkId) -> StakeAddress { + let payload = match cred { + PallasStakeCredential::AddrKeyhash(key_hash) => { + StakeCredential::AddrKeyHash(to_hash(key_hash)) + } + PallasStakeCredential::ScriptHash(script_hash) => { + StakeCredential::ScriptHash(to_hash(script_hash)) + } + }; + + StakeAddress::new(payload, network_id) +} diff --git a/codec/src/certs.rs b/codec/src/certs.rs new file mode 100644 index 00000000..c0b8770c --- /dev/null +++ b/codec/src/certs.rs @@ -0,0 +1,368 @@ +use crate::{ + address::{map_stake_address, map_stake_credential}, + utils::*, +}; +use acropolis_common::*; +use anyhow::{Result, anyhow}; +use pallas_primitives::{Nullable, alonzo, conway}; +use pallas_traverse::MultiEraCert; + +#[allow(clippy::too_many_arguments)] +pub fn to_pool_reg( + operator: &pallas_primitives::PoolKeyhash, + vrf_keyhash: &pallas_primitives::VrfKeyhash, + pledge: &pallas_primitives::Coin, + cost: &pallas_primitives::Coin, + margin: &pallas_primitives::UnitInterval, + reward_account: &pallas_primitives::RewardAccount, + pool_owners: &[pallas_primitives::AddrKeyhash], + relays: &[pallas_primitives::Relay], + pool_metadata: &Nullable, + network_id: NetworkId, + force_reward_network_id: bool, +) -> Result { + Ok(PoolRegistration { + operator: to_pool_id(operator), + vrf_key_hash: to_vrf_key(vrf_keyhash), + pledge: *pledge, + cost: *cost, + margin: Ratio { + numerator: margin.numerator, + denominator: margin.denominator, + }, + reward_account: if force_reward_network_id { + StakeAddress::new( + StakeAddress::from_binary(reward_account)?.credential, + network_id.clone(), + ) + } else { + StakeAddress::from_binary(reward_account)? + }, + pool_owners: pool_owners + .iter() + .map(|v| { + StakeAddress::new(StakeCredential::AddrKeyHash(to_hash(v)), network_id.clone()) + }) + .collect(), + relays: relays.iter().map(map_relay).collect(), + pool_metadata: match pool_metadata { + Nullable::Some(md) => Some(PoolMetadata { + url: md.url.clone(), + hash: md.hash.to_vec(), + }), + _ => None, + }, + }) +} + +/// Derive our TxCertificate from a Pallas Certificate +pub fn map_certificate( + cert: &MultiEraCert, + tx_identifier: TxIdentifier, + cert_index: usize, + network_id: NetworkId, +) -> Result { + match cert { + MultiEraCert::NotApplicable => Err(anyhow!("Not applicable cert!")), + + MultiEraCert::AlonzoCompatible(cert) => match cert.as_ref().as_ref() { + alonzo::Certificate::StakeRegistration(cred) => Ok(TxCertificateWithPos { + cert: TxCertificate::StakeRegistration(map_stake_address(cred, network_id)), + tx_identifier, + cert_index: cert_index.try_into().unwrap(), + }), + alonzo::Certificate::StakeDeregistration(cred) => Ok(TxCertificateWithPos { + cert: TxCertificate::StakeDeregistration(map_stake_address(cred, network_id)), + tx_identifier, + cert_index: cert_index.try_into().unwrap(), + }), + alonzo::Certificate::StakeDelegation(cred, pool_key_hash) => Ok(TxCertificateWithPos { + cert: TxCertificate::StakeDelegation(StakeDelegation { + stake_address: map_stake_address(cred, network_id), + operator: to_pool_id(pool_key_hash), + }), + tx_identifier, + cert_index: cert_index.try_into().unwrap(), + }), + alonzo::Certificate::PoolRegistration { + operator, + vrf_keyhash, + pledge, + cost, + margin, + reward_account, + pool_owners, + relays, + pool_metadata, + } => Ok(TxCertificateWithPos { + cert: TxCertificate::PoolRegistration(to_pool_reg( + operator, + vrf_keyhash, + pledge, + cost, + margin, + reward_account, + pool_owners, + relays, + pool_metadata, + network_id, + false, + )?), + tx_identifier, + cert_index: cert_index as u64, + }), + alonzo::Certificate::PoolRetirement(pool_key_hash, epoch) => Ok(TxCertificateWithPos { + cert: TxCertificate::PoolRetirement(PoolRetirement { + operator: to_pool_id(pool_key_hash), + epoch: *epoch, + }), + tx_identifier, + cert_index: cert_index as u64, + }), + alonzo::Certificate::GenesisKeyDelegation( + genesis_hash, + genesis_delegate_hash, + vrf_key_hash, + ) => Ok(TxCertificateWithPos { + cert: TxCertificate::GenesisKeyDelegation(GenesisKeyDelegation { + genesis_hash: genesis_to_hash(genesis_hash), + genesis_delegate_hash: genesis_delegate_to_hash(genesis_delegate_hash), + vrf_key_hash: to_vrf_key(vrf_key_hash), + }), + tx_identifier, + cert_index: cert_index as u64, + }), + alonzo::Certificate::MoveInstantaneousRewardsCert(mir) => Ok(TxCertificateWithPos { + cert: TxCertificate::MoveInstantaneousReward(MoveInstantaneousReward { + source: match mir.source { + alonzo::InstantaneousRewardSource::Reserves => { + InstantaneousRewardSource::Reserves + } + alonzo::InstantaneousRewardSource::Treasury => { + InstantaneousRewardSource::Treasury + } + }, + target: match &mir.target { + alonzo::InstantaneousRewardTarget::StakeCredentials(creds) => { + InstantaneousRewardTarget::StakeAddresses( + creds + .iter() + .map(|(sc, v)| (map_stake_address(sc, network_id.clone()), *v)) + .collect(), + ) + } + alonzo::InstantaneousRewardTarget::OtherAccountingPot(n) => { + InstantaneousRewardTarget::OtherAccountingPot(*n) + } + }, + }), + tx_identifier, + cert_index: cert_index as u64, + }), + }, + + // Now repeated for a different type! + MultiEraCert::Conway(cert) => { + match cert.as_ref().as_ref() { + conway::Certificate::StakeRegistration(cred) => Ok(TxCertificateWithPos { + cert: TxCertificate::StakeRegistration(map_stake_address(cred, network_id)), + tx_identifier, + + cert_index: cert_index.try_into().unwrap(), + }), + + conway::Certificate::StakeDeregistration(cred) => Ok(TxCertificateWithPos { + cert: TxCertificate::StakeDeregistration(map_stake_address(cred, network_id)), + tx_identifier, + cert_index: cert_index.try_into().unwrap(), + }), + + conway::Certificate::StakeDelegation(cred, pool_key_hash) => { + Ok(TxCertificateWithPos { + cert: TxCertificate::StakeDelegation(StakeDelegation { + stake_address: map_stake_address(cred, network_id), + operator: to_pool_id(pool_key_hash), + }), + tx_identifier, + cert_index: cert_index.try_into().unwrap(), + }) + } + + conway::Certificate::PoolRegistration { + // TODO relays, pool_metadata + operator, + vrf_keyhash, + pledge, + cost, + margin, + reward_account, + pool_owners, + relays, + pool_metadata, + } => Ok(TxCertificateWithPos { + cert: TxCertificate::PoolRegistration(to_pool_reg( + operator, + vrf_keyhash, + pledge, + cost, + margin, + reward_account, + pool_owners, + relays, + pool_metadata, + network_id, + // Force networkId - in mainnet epoch 208, one SPO (c63dab6d780a) uses + // an e0 (testnet!) address, and this then fails to match their actual + // reward account (e1). Feels like this should have been + // a validation failure, but clearly wasn't! + true, + )?), + tx_identifier, + cert_index: cert_index as u64, + }), + conway::Certificate::PoolRetirement(pool_key_hash, epoch) => { + Ok(TxCertificateWithPos { + cert: TxCertificate::PoolRetirement(PoolRetirement { + operator: to_pool_id(pool_key_hash), + epoch: *epoch, + }), + tx_identifier, + cert_index: cert_index as u64, + }) + } + + conway::Certificate::Reg(cred, coin) => Ok(TxCertificateWithPos { + cert: TxCertificate::Registration(Registration { + stake_address: map_stake_address(cred, network_id), + deposit: *coin, + }), + tx_identifier, + cert_index: cert_index as u64, + }), + + conway::Certificate::UnReg(cred, coin) => Ok(TxCertificateWithPos { + cert: TxCertificate::Deregistration(Deregistration { + stake_address: map_stake_address(cred, network_id), + refund: *coin, + }), + tx_identifier, + cert_index: cert_index as u64, + }), + + conway::Certificate::VoteDeleg(cred, drep) => Ok(TxCertificateWithPos { + cert: TxCertificate::VoteDelegation(VoteDelegation { + stake_address: map_stake_address(cred, network_id), + drep: map_drep(drep), + }), + tx_identifier, + cert_index: cert_index as u64, + }), + + conway::Certificate::StakeVoteDeleg(cred, pool_key_hash, drep) => { + Ok(TxCertificateWithPos { + cert: TxCertificate::StakeAndVoteDelegation(StakeAndVoteDelegation { + stake_address: map_stake_address(cred, network_id), + operator: to_pool_id(pool_key_hash), + drep: map_drep(drep), + }), + tx_identifier, + cert_index: cert_index as u64, + }) + } + + conway::Certificate::StakeRegDeleg(cred, pool_key_hash, coin) => { + Ok(TxCertificateWithPos { + cert: TxCertificate::StakeRegistrationAndDelegation( + StakeRegistrationAndDelegation { + stake_address: map_stake_address(cred, network_id), + operator: to_pool_id(pool_key_hash), + deposit: *coin, + }, + ), + tx_identifier, + cert_index: cert_index as u64, + }) + } + + conway::Certificate::VoteRegDeleg(cred, drep, coin) => Ok(TxCertificateWithPos { + cert: TxCertificate::StakeRegistrationAndVoteDelegation( + StakeRegistrationAndVoteDelegation { + stake_address: map_stake_address(cred, network_id), + drep: map_drep(drep), + deposit: *coin, + }, + ), + tx_identifier, + cert_index: cert_index as u64, + }), + + conway::Certificate::StakeVoteRegDeleg(cred, pool_key_hash, drep, coin) => { + Ok(TxCertificateWithPos { + cert: TxCertificate::StakeRegistrationAndStakeAndVoteDelegation( + StakeRegistrationAndStakeAndVoteDelegation { + stake_address: map_stake_address(cred, network_id), + operator: to_pool_id(pool_key_hash), + drep: map_drep(drep), + deposit: *coin, + }, + ), + tx_identifier, + cert_index: cert_index as u64, + }) + } + + conway::Certificate::AuthCommitteeHot(cold_cred, hot_cred) => { + Ok(TxCertificateWithPos { + cert: TxCertificate::AuthCommitteeHot(AuthCommitteeHot { + cold_credential: map_stake_credential(cold_cred), + hot_credential: map_stake_credential(hot_cred), + }), + tx_identifier, + cert_index: cert_index as u64, + }) + } + + conway::Certificate::ResignCommitteeCold(cold_cred, anchor) => { + Ok(TxCertificateWithPos { + cert: TxCertificate::ResignCommitteeCold(ResignCommitteeCold { + cold_credential: map_stake_credential(cold_cred), + anchor: map_nullable_anchor(anchor), + }), + tx_identifier, + cert_index: cert_index as u64, + }) + } + + conway::Certificate::RegDRepCert(cred, coin, anchor) => Ok(TxCertificateWithPos { + cert: TxCertificate::DRepRegistration(DRepRegistration { + credential: map_stake_credential(cred), + deposit: *coin, + anchor: map_nullable_anchor(anchor), + }), + tx_identifier, + cert_index: cert_index as u64, + }), + + conway::Certificate::UnRegDRepCert(cred, coin) => Ok(TxCertificateWithPos { + cert: TxCertificate::DRepDeregistration(DRepDeregistration { + credential: map_stake_credential(cred), + refund: *coin, + }), + tx_identifier, + cert_index: cert_index as u64, + }), + + conway::Certificate::UpdateDRepCert(cred, anchor) => Ok(TxCertificateWithPos { + cert: TxCertificate::DRepUpdate(DRepUpdate { + credential: map_stake_credential(cred), + anchor: map_nullable_anchor(anchor), + }), + tx_identifier, + cert_index: cert_index as u64, + }), + } + } + + _ => Err(anyhow!("Unknown certificate era {:?} ignored", cert)), + } +} diff --git a/codec/src/lib.rs b/codec/src/lib.rs index 7cb7bc0d..88b65133 100644 --- a/codec/src/lib.rs +++ b/codec/src/lib.rs @@ -1,2 +1,15 @@ +pub mod address; pub mod block; -pub mod map_parameters; +pub mod certs; +pub mod parameter; +pub mod tx; +pub mod utils; +pub mod utxo; + +pub use address::*; +pub use block::*; +pub use certs::*; +pub use parameter::*; +pub use tx::*; +pub use utils::*; +pub use utxo::*; diff --git a/codec/src/map_parameters.rs b/codec/src/map_parameters.rs deleted file mode 100644 index 150609d3..00000000 --- a/codec/src/map_parameters.rs +++ /dev/null @@ -1,1154 +0,0 @@ -//! Acropolis transaction unpacker module for Caryatid -//! Performs conversion from Pallas library data to Acropolis - -use anyhow::{Result, anyhow, bail}; -use pallas::ledger::{ - primitives::{ - ExUnitPrices as PallasExUnitPrices, Nullable, ProtocolVersion as PallasProtocolVersion, - Relay as PallasRelay, ScriptHash, StakeCredential as PallasStakeCredential, alonzo, - babbage, conway, - }, - traverse::{MultiEraCert, MultiEraPolicyAssets, MultiEraValue}, - *, -}; - -use acropolis_common::hash::Hash; -use acropolis_common::{ - protocol_params::{Nonce, NonceVariant, ProtocolVersion}, - rational_number::RationalNumber, - *, -}; -use pallas_primitives::conway::PseudoScript; -use std::{ - collections::{HashMap, HashSet}, - net::{Ipv4Addr, Ipv6Addr}, -}; - -/// Map Pallas Network to our NetworkId -pub fn map_network(network: addresses::Network) -> Result { - match network { - addresses::Network::Mainnet => Ok(NetworkId::Mainnet), - addresses::Network::Testnet => Ok(NetworkId::Testnet), - _ => Err(anyhow!("Unknown network in address")), - } -} - -/// Convert a Pallas Hash reference to an Acropolis Hash (owned) -/// Works for any hash size N -pub fn to_hash(pallas_hash: &pallas_primitives::Hash) -> Hash { - Hash::try_from(pallas_hash.as_ref()).unwrap() -} - -/// Convert a Pallas Hash reference to an Acropolis Hash (owned) -/// Works for any hash size N -pub fn genesis_to_hash(pallas_hash: &pallas_primitives::Genesishash) -> Hash<28> { - Hash::try_from(pallas_hash.as_ref()).unwrap() -} - -/// Convert a Pallas Hash reference to an Acropolis Hash (owned) -/// Works for any hash size N -pub fn genesis_delegate_to_hash(pallas_hash: &pallas_primitives::GenesisDelegateHash) -> PoolId { - PoolId::try_from(pallas_hash.as_ref()).unwrap() -} - -/// Convert a Pallas Hash<28> reference to an Acropolis PoolId -pub fn to_pool_id(pallas_hash: &pallas_primitives::Hash<28>) -> PoolId { - to_hash(pallas_hash).into() -} - -/// Convert a Pallas Hash<32> reference to an Acropolis VRFKey -pub fn to_vrf_key(pallas_hash: &pallas_primitives::Hash<32>) -> VrfKeyHash { - VrfKeyHash::try_from(pallas_hash.as_ref()).unwrap() -} - -/// Derive our Address from a Pallas address -// This is essentially a 1:1 mapping but makes the Message definitions independent -// of Pallas -pub fn map_address(address: &addresses::Address) -> Result
{ - match address { - addresses::Address::Byron(byron_address) => Ok(Address::Byron(ByronAddress { - payload: byron_address.payload.to_vec(), - })), - - addresses::Address::Shelley(shelley_address) => Ok(Address::Shelley(ShelleyAddress { - network: map_network(shelley_address.network())?, - - payment: match shelley_address.payment() { - addresses::ShelleyPaymentPart::Key(hash) => { - ShelleyAddressPaymentPart::PaymentKeyHash(to_hash(hash)) - } - addresses::ShelleyPaymentPart::Script(hash) => { - ShelleyAddressPaymentPart::ScriptHash(to_hash(hash)) - } - }, - - delegation: match shelley_address.delegation() { - addresses::ShelleyDelegationPart::Null => ShelleyAddressDelegationPart::None, - addresses::ShelleyDelegationPart::Key(hash) => { - ShelleyAddressDelegationPart::StakeKeyHash(to_hash(hash)) - } - addresses::ShelleyDelegationPart::Script(hash) => { - ShelleyAddressDelegationPart::ScriptHash(to_hash(hash)) - } - addresses::ShelleyDelegationPart::Pointer(pointer) => { - ShelleyAddressDelegationPart::Pointer(ShelleyAddressPointer { - slot: pointer.slot(), - tx_index: pointer.tx_idx(), - cert_index: pointer.cert_idx(), - }) - } - }, - })), - - addresses::Address::Stake(stake_address) => Ok(Address::Stake(StakeAddress { - network: map_network(stake_address.network())?, - credential: match stake_address.payload() { - addresses::StakePayload::Stake(hash) => StakeCredential::AddrKeyHash(to_hash(hash)), - addresses::StakePayload::Script(hash) => StakeCredential::ScriptHash(to_hash(hash)), - }, - })), - } -} - -/// Map a Pallas StakeCredential to ours -pub fn map_stake_credential(cred: &PallasStakeCredential) -> StakeCredential { - match cred { - PallasStakeCredential::AddrKeyhash(key_hash) => { - StakeCredential::AddrKeyHash(to_hash(key_hash)) - } - PallasStakeCredential::ScriptHash(script_hash) => { - StakeCredential::ScriptHash(to_hash(script_hash)) - } - } -} - -/// Map a PallasStakeCredential to our StakeAddress -pub fn map_stake_address(cred: &PallasStakeCredential, network_id: NetworkId) -> StakeAddress { - let payload = match cred { - PallasStakeCredential::AddrKeyhash(key_hash) => { - StakeCredential::AddrKeyHash(to_hash(key_hash)) - } - PallasStakeCredential::ScriptHash(script_hash) => { - StakeCredential::ScriptHash(to_hash(script_hash)) - } - }; - - StakeAddress::new(payload, network_id) -} - -/// Map a Pallas DRep to our DRepChoice -pub fn map_drep(drep: &conway::DRep) -> DRepChoice { - match drep { - conway::DRep::Key(key_hash) => DRepChoice::Key(to_hash(key_hash)), - conway::DRep::Script(script_hash) => DRepChoice::Script(to_hash(script_hash)), - conway::DRep::Abstain => DRepChoice::Abstain, - conway::DRep::NoConfidence => DRepChoice::NoConfidence, - } -} - -pub fn map_nullable( - f: impl FnOnce(&Src) -> Dst, - nullable_src: &Nullable, -) -> Option { - match nullable_src { - Nullable::Some(src) => Some(f(src)), - _ => None, - } -} - -pub fn map_nullable_result( - f: impl FnOnce(&Src) -> Result, - nullable_src: &Nullable, -) -> Result> { - match nullable_src { - Nullable::Some(src) => { - let res = f(src)?; - Ok(Some(res)) - } - _ => Ok(None), - } -} - -pub fn map_anchor(anchor: &conway::Anchor) -> Anchor { - Anchor { - url: anchor.url.clone(), - data_hash: anchor.content_hash.to_vec(), - } -} - -/// Map a Nullable Anchor to ours -pub fn map_nullable_anchor(anchor: &Nullable) -> Option { - map_nullable(map_anchor, anchor) -} - -pub fn map_gov_action_id(pallas_action_id: &conway::GovActionId) -> Result { - let act_idx_u8: u8 = match pallas_action_id.action_index.try_into() { - Ok(v) => v, - Err(e) => return Err(anyhow!("Invalid action index {e}")), - }; - - Ok(GovActionId { - transaction_id: TxHash::from(*pallas_action_id.transaction_id), - action_index: act_idx_u8, - }) -} - -pub fn map_nullable_gov_action_id( - id: &Nullable, -) -> Result> { - map_nullable_result(map_gov_action_id, id) -} - -fn map_constitution(constitution: &conway::Constitution) -> Constitution { - Constitution { - anchor: map_anchor(&constitution.anchor), - guardrail_script: map_nullable(to_hash, &constitution.guardrail_script), - } -} - -/// Map a Pallas Relay to ours -pub fn map_relay(relay: &PallasRelay) -> Relay { - match relay { - PallasRelay::SingleHostAddr(port, ipv4, ipv6) => Relay::SingleHostAddr(SingleHostAddr { - port: match port { - Nullable::Some(port) => Some(*port as u16), - _ => None, - }, - ipv4: match ipv4 { - Nullable::Some(ipv4) => <[u8; 4]>::try_from(ipv4).ok().map(Ipv4Addr::from), - _ => None, - }, - ipv6: match ipv6 { - Nullable::Some(ipv6) => <[u8; 16]>::try_from(ipv6).ok().map(Ipv6Addr::from), - _ => None, - }, - }), - PallasRelay::SingleHostName(port, dns_name) => Relay::SingleHostName(SingleHostName { - port: match port { - Nullable::Some(port) => Some(*port as u16), - _ => None, - }, - dns_name: dns_name.clone(), - }), - PallasRelay::MultiHostName(dns_name) => Relay::MultiHostName(MultiHostName { - dns_name: dns_name.clone(), - }), - } -} - -// -// Certificates -// -#[allow(clippy::too_many_arguments)] -pub fn to_pool_reg( - operator: &pallas_primitives::PoolKeyhash, - vrf_keyhash: &pallas_primitives::VrfKeyhash, - pledge: &pallas_primitives::Coin, - cost: &pallas_primitives::Coin, - margin: &pallas_primitives::UnitInterval, - reward_account: &pallas_primitives::RewardAccount, - pool_owners: &[pallas_primitives::AddrKeyhash], - relays: &[pallas_primitives::Relay], - pool_metadata: &Nullable, - network_id: NetworkId, - force_reward_network_id: bool, -) -> Result { - Ok(PoolRegistration { - operator: to_pool_id(operator), - vrf_key_hash: to_vrf_key(vrf_keyhash), - pledge: *pledge, - cost: *cost, - margin: Ratio { - numerator: margin.numerator, - denominator: margin.denominator, - }, - reward_account: if force_reward_network_id { - StakeAddress::new( - StakeAddress::from_binary(reward_account)?.credential, - network_id.clone(), - ) - } else { - StakeAddress::from_binary(reward_account)? - }, - pool_owners: pool_owners - .iter() - .map(|v| { - StakeAddress::new(StakeCredential::AddrKeyHash(to_hash(v)), network_id.clone()) - }) - .collect(), - relays: relays.iter().map(map_relay).collect(), - pool_metadata: match pool_metadata { - Nullable::Some(md) => Some(PoolMetadata { - url: md.url.clone(), - hash: md.hash.to_vec(), - }), - _ => None, - }, - }) -} - -/// Derive our TxCertificate from a Pallas Certificate -pub fn map_certificate( - cert: &MultiEraCert, - tx_identifier: TxIdentifier, - cert_index: usize, - network_id: NetworkId, -) -> Result { - match cert { - MultiEraCert::NotApplicable => Err(anyhow!("Not applicable cert!")), - - MultiEraCert::AlonzoCompatible(cert) => match cert.as_ref().as_ref() { - alonzo::Certificate::StakeRegistration(cred) => Ok(TxCertificateWithPos { - cert: TxCertificate::StakeRegistration(map_stake_address(cred, network_id)), - tx_identifier, - cert_index: cert_index.try_into().unwrap(), - }), - alonzo::Certificate::StakeDeregistration(cred) => Ok(TxCertificateWithPos { - cert: TxCertificate::StakeDeregistration(map_stake_address(cred, network_id)), - tx_identifier, - cert_index: cert_index.try_into().unwrap(), - }), - alonzo::Certificate::StakeDelegation(cred, pool_key_hash) => Ok(TxCertificateWithPos { - cert: TxCertificate::StakeDelegation(StakeDelegation { - stake_address: map_stake_address(cred, network_id), - operator: to_pool_id(pool_key_hash), - }), - tx_identifier, - cert_index: cert_index.try_into().unwrap(), - }), - alonzo::Certificate::PoolRegistration { - operator, - vrf_keyhash, - pledge, - cost, - margin, - reward_account, - pool_owners, - relays, - pool_metadata, - } => Ok(TxCertificateWithPos { - cert: TxCertificate::PoolRegistration(to_pool_reg( - operator, - vrf_keyhash, - pledge, - cost, - margin, - reward_account, - pool_owners, - relays, - pool_metadata, - network_id, - false, - )?), - tx_identifier, - cert_index: cert_index as u64, - }), - alonzo::Certificate::PoolRetirement(pool_key_hash, epoch) => Ok(TxCertificateWithPos { - cert: TxCertificate::PoolRetirement(PoolRetirement { - operator: to_pool_id(pool_key_hash), - epoch: *epoch, - }), - tx_identifier, - cert_index: cert_index as u64, - }), - alonzo::Certificate::GenesisKeyDelegation( - genesis_hash, - genesis_delegate_hash, - vrf_key_hash, - ) => Ok(TxCertificateWithPos { - cert: TxCertificate::GenesisKeyDelegation(GenesisKeyDelegation { - genesis_hash: genesis_to_hash(genesis_hash), - genesis_delegate_hash: genesis_delegate_to_hash(genesis_delegate_hash), - vrf_key_hash: to_vrf_key(vrf_key_hash), - }), - tx_identifier, - cert_index: cert_index as u64, - }), - alonzo::Certificate::MoveInstantaneousRewardsCert(mir) => Ok(TxCertificateWithPos { - cert: TxCertificate::MoveInstantaneousReward(MoveInstantaneousReward { - source: match mir.source { - alonzo::InstantaneousRewardSource::Reserves => { - InstantaneousRewardSource::Reserves - } - alonzo::InstantaneousRewardSource::Treasury => { - InstantaneousRewardSource::Treasury - } - }, - target: match &mir.target { - alonzo::InstantaneousRewardTarget::StakeCredentials(creds) => { - InstantaneousRewardTarget::StakeAddresses( - creds - .iter() - .map(|(sc, v)| (map_stake_address(sc, network_id.clone()), *v)) - .collect(), - ) - } - alonzo::InstantaneousRewardTarget::OtherAccountingPot(n) => { - InstantaneousRewardTarget::OtherAccountingPot(*n) - } - }, - }), - tx_identifier, - cert_index: cert_index as u64, - }), - }, - - // Now repeated for a different type! - MultiEraCert::Conway(cert) => { - match cert.as_ref().as_ref() { - conway::Certificate::StakeRegistration(cred) => Ok(TxCertificateWithPos { - cert: TxCertificate::StakeRegistration(map_stake_address(cred, network_id)), - tx_identifier, - - cert_index: cert_index.try_into().unwrap(), - }), - - conway::Certificate::StakeDeregistration(cred) => Ok(TxCertificateWithPos { - cert: TxCertificate::StakeDeregistration(map_stake_address(cred, network_id)), - tx_identifier, - cert_index: cert_index.try_into().unwrap(), - }), - - conway::Certificate::StakeDelegation(cred, pool_key_hash) => { - Ok(TxCertificateWithPos { - cert: TxCertificate::StakeDelegation(StakeDelegation { - stake_address: map_stake_address(cred, network_id), - operator: to_pool_id(pool_key_hash), - }), - tx_identifier, - cert_index: cert_index.try_into().unwrap(), - }) - } - - conway::Certificate::PoolRegistration { - // TODO relays, pool_metadata - operator, - vrf_keyhash, - pledge, - cost, - margin, - reward_account, - pool_owners, - relays, - pool_metadata, - } => Ok(TxCertificateWithPos { - cert: TxCertificate::PoolRegistration(to_pool_reg( - operator, - vrf_keyhash, - pledge, - cost, - margin, - reward_account, - pool_owners, - relays, - pool_metadata, - network_id, - // Force networkId - in mainnet epoch 208, one SPO (c63dab6d780a) uses - // an e0 (testnet!) address, and this then fails to match their actual - // reward account (e1). Feels like this should have been - // a validation failure, but clearly wasn't! - true, - )?), - tx_identifier, - cert_index: cert_index as u64, - }), - conway::Certificate::PoolRetirement(pool_key_hash, epoch) => { - Ok(TxCertificateWithPos { - cert: TxCertificate::PoolRetirement(PoolRetirement { - operator: to_pool_id(pool_key_hash), - epoch: *epoch, - }), - tx_identifier, - cert_index: cert_index as u64, - }) - } - - conway::Certificate::Reg(cred, coin) => Ok(TxCertificateWithPos { - cert: TxCertificate::Registration(Registration { - stake_address: map_stake_address(cred, network_id), - deposit: *coin, - }), - tx_identifier, - cert_index: cert_index as u64, - }), - - conway::Certificate::UnReg(cred, coin) => Ok(TxCertificateWithPos { - cert: TxCertificate::Deregistration(Deregistration { - stake_address: map_stake_address(cred, network_id), - refund: *coin, - }), - tx_identifier, - cert_index: cert_index as u64, - }), - - conway::Certificate::VoteDeleg(cred, drep) => Ok(TxCertificateWithPos { - cert: TxCertificate::VoteDelegation(VoteDelegation { - stake_address: map_stake_address(cred, network_id), - drep: map_drep(drep), - }), - tx_identifier, - cert_index: cert_index as u64, - }), - - conway::Certificate::StakeVoteDeleg(cred, pool_key_hash, drep) => { - Ok(TxCertificateWithPos { - cert: TxCertificate::StakeAndVoteDelegation(StakeAndVoteDelegation { - stake_address: map_stake_address(cred, network_id), - operator: to_pool_id(pool_key_hash), - drep: map_drep(drep), - }), - tx_identifier, - cert_index: cert_index as u64, - }) - } - - conway::Certificate::StakeRegDeleg(cred, pool_key_hash, coin) => { - Ok(TxCertificateWithPos { - cert: TxCertificate::StakeRegistrationAndDelegation( - StakeRegistrationAndDelegation { - stake_address: map_stake_address(cred, network_id), - operator: to_pool_id(pool_key_hash), - deposit: *coin, - }, - ), - tx_identifier, - cert_index: cert_index as u64, - }) - } - - conway::Certificate::VoteRegDeleg(cred, drep, coin) => Ok(TxCertificateWithPos { - cert: TxCertificate::StakeRegistrationAndVoteDelegation( - StakeRegistrationAndVoteDelegation { - stake_address: map_stake_address(cred, network_id), - drep: map_drep(drep), - deposit: *coin, - }, - ), - tx_identifier, - cert_index: cert_index as u64, - }), - - conway::Certificate::StakeVoteRegDeleg(cred, pool_key_hash, drep, coin) => { - Ok(TxCertificateWithPos { - cert: TxCertificate::StakeRegistrationAndStakeAndVoteDelegation( - StakeRegistrationAndStakeAndVoteDelegation { - stake_address: map_stake_address(cred, network_id), - operator: to_pool_id(pool_key_hash), - drep: map_drep(drep), - deposit: *coin, - }, - ), - tx_identifier, - cert_index: cert_index as u64, - }) - } - - conway::Certificate::AuthCommitteeHot(cold_cred, hot_cred) => { - Ok(TxCertificateWithPos { - cert: TxCertificate::AuthCommitteeHot(AuthCommitteeHot { - cold_credential: map_stake_credential(cold_cred), - hot_credential: map_stake_credential(hot_cred), - }), - tx_identifier, - cert_index: cert_index as u64, - }) - } - - conway::Certificate::ResignCommitteeCold(cold_cred, anchor) => { - Ok(TxCertificateWithPos { - cert: TxCertificate::ResignCommitteeCold(ResignCommitteeCold { - cold_credential: map_stake_credential(cold_cred), - anchor: map_nullable_anchor(anchor), - }), - tx_identifier, - cert_index: cert_index as u64, - }) - } - - conway::Certificate::RegDRepCert(cred, coin, anchor) => Ok(TxCertificateWithPos { - cert: TxCertificate::DRepRegistration(DRepRegistration { - credential: map_stake_credential(cred), - deposit: *coin, - anchor: map_nullable_anchor(anchor), - }), - tx_identifier, - cert_index: cert_index as u64, - }), - - conway::Certificate::UnRegDRepCert(cred, coin) => Ok(TxCertificateWithPos { - cert: TxCertificate::DRepDeregistration(DRepDeregistration { - credential: map_stake_credential(cred), - refund: *coin, - }), - tx_identifier, - cert_index: cert_index as u64, - }), - - conway::Certificate::UpdateDRepCert(cred, anchor) => Ok(TxCertificateWithPos { - cert: TxCertificate::DRepUpdate(DRepUpdate { - credential: map_stake_credential(cred), - anchor: map_nullable_anchor(anchor), - }), - tx_identifier, - cert_index: cert_index as u64, - }), - } - } - - _ => Err(anyhow!("Unknown certificate era {:?} ignored", cert)), - } -} - -fn map_unit_interval(pallas_interval: &conway::UnitInterval) -> RationalNumber { - RationalNumber::new(pallas_interval.numerator, pallas_interval.denominator) -} - -fn map_ex_units(pallas_units: &conway::ExUnits) -> ExUnits { - ExUnits { - mem: pallas_units.mem, - steps: pallas_units.steps, - } -} - -fn map_execution_costs(pallas_ex_costs: &PallasExUnitPrices) -> ExUnitPrices { - ExUnitPrices { - mem_price: map_unit_interval(&pallas_ex_costs.mem_price), - step_price: map_unit_interval(&pallas_ex_costs.step_price), - } -} - -fn map_conway_execution_costs(pallas_ex_costs: &conway::ExUnitPrices) -> ExUnitPrices { - ExUnitPrices { - mem_price: map_unit_interval(&pallas_ex_costs.mem_price), - step_price: map_unit_interval(&pallas_ex_costs.step_price), - } -} - -fn map_conway_cost_models(pallas_cost_models: &conway::CostModels) -> CostModels { - CostModels { - plutus_v1: pallas_cost_models.plutus_v1.as_ref().map(|x| CostModel::new(x.clone())), - plutus_v2: pallas_cost_models.plutus_v2.as_ref().map(|x| CostModel::new(x.clone())), - plutus_v3: pallas_cost_models.plutus_v3.as_ref().map(|x| CostModel::new(x.clone())), - } -} - -fn map_alonzo_nonce(e: &alonzo::Nonce) -> Nonce { - Nonce { - tag: match &e.variant { - alonzo::NonceVariant::NeutralNonce => NonceVariant::NeutralNonce, - alonzo::NonceVariant::Nonce => NonceVariant::Nonce, - }, - hash: e.hash.map(|v| *v), - } -} - -fn map_alonzo_single_model(model: &alonzo::CostModel) -> Option { - Some(CostModel::new(model.clone())) -} - -fn map_alonzo_cost_models(pallas_cost_models: &alonzo::CostModels) -> Result { - let mut res = CostModels { - plutus_v1: None, - plutus_v2: None, - plutus_v3: None, - }; - for (lang, mdl) in pallas_cost_models.iter() { - if *lang == alonzo::Language::PlutusV1 { - res.plutus_v1 = map_alonzo_single_model(mdl); - } else { - bail!("Alonzo may not contain {lang:?} language"); - } - } - Ok(res) -} - -fn map_protocol_version((major, minor): &PallasProtocolVersion) -> ProtocolVersion { - ProtocolVersion { - minor: *minor, - major: *major, - } -} - -fn map_pool_voting_thresholds(ts: &conway::PoolVotingThresholds) -> PoolVotingThresholds { - PoolVotingThresholds { - motion_no_confidence: map_unit_interval(&ts.motion_no_confidence), - committee_normal: map_unit_interval(&ts.committee_normal), - committee_no_confidence: map_unit_interval(&ts.committee_no_confidence), - hard_fork_initiation: map_unit_interval(&ts.hard_fork_initiation), - security_voting_threshold: map_unit_interval(&ts.security_voting_threshold), - } -} - -fn map_drep_voting_thresholds(ts: &conway::DRepVotingThresholds) -> DRepVotingThresholds { - DRepVotingThresholds { - motion_no_confidence: map_unit_interval(&ts.motion_no_confidence), - committee_normal: map_unit_interval(&ts.committee_normal), - committee_no_confidence: map_unit_interval(&ts.committee_no_confidence), - update_constitution: map_unit_interval(&ts.update_constitution), - hard_fork_initiation: map_unit_interval(&ts.hard_fork_initiation), - pp_network_group: map_unit_interval(&ts.pp_network_group), - pp_economic_group: map_unit_interval(&ts.pp_economic_group), - pp_technical_group: map_unit_interval(&ts.pp_technical_group), - pp_governance_group: map_unit_interval(&ts.pp_governance_group), - treasury_withdrawal: map_unit_interval(&ts.treasury_withdrawal), - } -} - -fn map_conway_protocol_param_update(p: &conway::ProtocolParamUpdate) -> Box { - Box::new(ProtocolParamUpdate { - // Fields, common for Conway and Alonzo-compatible - minfee_a: p.minfee_a, - minfee_b: p.minfee_b, - max_block_body_size: p.max_block_body_size, - max_transaction_size: p.max_transaction_size, - max_block_header_size: p.max_block_header_size, - key_deposit: p.key_deposit, - pool_deposit: p.pool_deposit, - maximum_epoch: p.maximum_epoch, - desired_number_of_stake_pools: p.desired_number_of_stake_pools, - pool_pledge_influence: p.pool_pledge_influence.as_ref().map(&map_unit_interval), - expansion_rate: p.expansion_rate.as_ref().map(&map_unit_interval), - treasury_growth_rate: p.treasury_growth_rate.as_ref().map(&map_unit_interval), - min_pool_cost: p.min_pool_cost, - coins_per_utxo_byte: p.ada_per_utxo_byte, - lovelace_per_utxo_word: None, - cost_models_for_script_languages: p - .cost_models_for_script_languages - .as_ref() - .map(&map_conway_cost_models), - execution_costs: p.execution_costs.as_ref().map(&map_conway_execution_costs), - max_tx_ex_units: p.max_tx_ex_units.as_ref().map(&map_ex_units), - max_block_ex_units: p.max_block_ex_units.as_ref().map(&map_ex_units), - max_value_size: p.max_value_size, - collateral_percentage: p.collateral_percentage, - max_collateral_inputs: p.max_collateral_inputs, - - // Fields, specific for Conway - pool_voting_thresholds: p.pool_voting_thresholds.as_ref().map(&map_pool_voting_thresholds), - drep_voting_thresholds: p.drep_voting_thresholds.as_ref().map(&map_drep_voting_thresholds), - min_committee_size: p.min_committee_size, - committee_term_limit: p.committee_term_limit, - governance_action_validity_period: p.governance_action_validity_period, - governance_action_deposit: p.governance_action_deposit, - drep_deposit: p.drep_deposit, - drep_inactivity_period: p.drep_inactivity_period, - minfee_refscript_cost_per_byte: p - .minfee_refscript_cost_per_byte - .as_ref() - .map(&map_unit_interval), - - // Fields, missing from Conway - decentralisation_constant: None, - extra_enthropy: None, - protocol_version: None, - }) -} - -fn map_governance_action(action: &conway::GovAction) -> Result { - match action { - conway::GovAction::ParameterChange(id, protocol_update, script) => { - Ok(GovernanceAction::ParameterChange(ParameterChangeAction { - previous_action_id: map_nullable_gov_action_id(id)?, - protocol_param_update: map_conway_protocol_param_update(protocol_update), - script_hash: map_nullable(|x: &ScriptHash| x.to_vec(), script), - })) - } - - conway::GovAction::HardForkInitiation(id, version) => Ok( - GovernanceAction::HardForkInitiation(HardForkInitiationAction { - previous_action_id: map_nullable_gov_action_id(id)?, - protocol_version: protocol_params::ProtocolVersion::new(version.0, version.1), - }), - ), - - conway::GovAction::TreasuryWithdrawals(withdrawals, script) => Ok( - GovernanceAction::TreasuryWithdrawals(TreasuryWithdrawalsAction { - rewards: HashMap::from_iter( - withdrawals.iter().map(|(account, coin)| (account.to_vec(), *coin)), - ), - script_hash: map_nullable(|x: &ScriptHash| x.to_vec(), script), - }), - ), - - conway::GovAction::NoConfidence(id) => Ok(GovernanceAction::NoConfidence( - map_nullable_gov_action_id(id)?, - )), - - conway::GovAction::UpdateCommittee(id, committee, threshold, terms) => { - Ok(GovernanceAction::UpdateCommittee(UpdateCommitteeAction { - previous_action_id: map_nullable_gov_action_id(id)?, - data: CommitteeChange { - removed_committee_members: HashSet::from_iter( - committee.iter().map(map_stake_credential), - ), - new_committee_members: HashMap::from_iter( - threshold.iter().map(|(k, v)| (map_stake_credential(k), *v)), - ), - terms: map_unit_interval(terms), - }, - })) - } - - conway::GovAction::NewConstitution(id, constitution) => { - Ok(GovernanceAction::NewConstitution(NewConstitutionAction { - previous_action_id: map_nullable_gov_action_id(id)?, - new_constitution: map_constitution(constitution), - })) - } - - conway::GovAction::Information => Ok(GovernanceAction::Information), - } -} - -fn map_u32_to_u64(n: Option) -> Option { - n.as_ref().map(|x| *x as u64) -} - -pub fn map_alonzo_protocol_param_update( - p: &alonzo::ProtocolParamUpdate, -) -> Result> { - Ok(Box::new(ProtocolParamUpdate { - // Fields, common for Conway and Alonzo-compatible - minfee_a: map_u32_to_u64(p.minfee_a), - minfee_b: map_u32_to_u64(p.minfee_b), - max_block_body_size: map_u32_to_u64(p.max_block_body_size), - max_transaction_size: map_u32_to_u64(p.max_transaction_size), - max_block_header_size: map_u32_to_u64(p.max_block_header_size), - key_deposit: p.key_deposit, - pool_deposit: p.pool_deposit, - maximum_epoch: p.maximum_epoch, - desired_number_of_stake_pools: map_u32_to_u64(p.desired_number_of_stake_pools), - pool_pledge_influence: p.pool_pledge_influence.as_ref().map(&map_unit_interval), - expansion_rate: p.expansion_rate.as_ref().map(&map_unit_interval), - treasury_growth_rate: p.treasury_growth_rate.as_ref().map(&map_unit_interval), - min_pool_cost: p.min_pool_cost, - lovelace_per_utxo_word: p.ada_per_utxo_byte, // Pre Babbage (Represents cost per 8-byte word) - coins_per_utxo_byte: None, - cost_models_for_script_languages: p - .cost_models_for_script_languages - .as_ref() - .map(&map_alonzo_cost_models) - .transpose()?, - execution_costs: p.execution_costs.as_ref().map(&map_execution_costs), - max_tx_ex_units: p.max_tx_ex_units.as_ref().map(&map_ex_units), - max_block_ex_units: p.max_block_ex_units.as_ref().map(&map_ex_units), - max_value_size: map_u32_to_u64(p.max_value_size), - collateral_percentage: map_u32_to_u64(p.collateral_percentage), - max_collateral_inputs: map_u32_to_u64(p.max_collateral_inputs), - - // Fields, specific for Conway - pool_voting_thresholds: None, - drep_voting_thresholds: None, - min_committee_size: None, - committee_term_limit: None, - governance_action_validity_period: None, - governance_action_deposit: None, - drep_deposit: None, - drep_inactivity_period: None, - minfee_refscript_cost_per_byte: None, - - // Fields, specific for Alonzo-compatible (Alonzo, Babbage, Shelley) - decentralisation_constant: p.decentralization_constant.as_ref().map(&map_unit_interval), - extra_enthropy: p.extra_entropy.as_ref().map(&map_alonzo_nonce), - protocol_version: p.protocol_version.as_ref().map(map_protocol_version), - })) -} - -fn map_babbage_cost_models(cost_models: &babbage::CostModels) -> CostModels { - CostModels { - plutus_v1: cost_models.plutus_v1.as_ref().map(|p| CostModel::new(p.clone())), - plutus_v2: cost_models.plutus_v2.as_ref().map(|p| CostModel::new(p.clone())), - plutus_v3: None, - } -} - -pub fn map_babbage_protocol_param_update( - p: &babbage::ProtocolParamUpdate, -) -> Result> { - Ok(Box::new(ProtocolParamUpdate { - // Fields, common for Conway and Alonzo-compatible - minfee_a: map_u32_to_u64(p.minfee_a), - minfee_b: map_u32_to_u64(p.minfee_b), - max_block_body_size: map_u32_to_u64(p.max_block_body_size), - max_transaction_size: map_u32_to_u64(p.max_transaction_size), - max_block_header_size: map_u32_to_u64(p.max_block_header_size), - key_deposit: p.key_deposit, - pool_deposit: p.pool_deposit, - maximum_epoch: p.maximum_epoch, - desired_number_of_stake_pools: map_u32_to_u64(p.desired_number_of_stake_pools), - pool_pledge_influence: p.pool_pledge_influence.as_ref().map(&map_unit_interval), - expansion_rate: p.expansion_rate.as_ref().map(&map_unit_interval), - treasury_growth_rate: p.treasury_growth_rate.as_ref().map(&map_unit_interval), - min_pool_cost: p.min_pool_cost, - lovelace_per_utxo_word: None, - coins_per_utxo_byte: p.ada_per_utxo_byte, - cost_models_for_script_languages: p - .cost_models_for_script_languages - .as_ref() - .map(&map_babbage_cost_models), - execution_costs: p.execution_costs.as_ref().map(&map_execution_costs), - max_tx_ex_units: p.max_tx_ex_units.as_ref().map(&map_ex_units), - max_block_ex_units: p.max_block_ex_units.as_ref().map(&map_ex_units), - max_value_size: map_u32_to_u64(p.max_value_size), - collateral_percentage: map_u32_to_u64(p.collateral_percentage), - max_collateral_inputs: map_u32_to_u64(p.max_collateral_inputs), - - // Fields, specific for Conway - pool_voting_thresholds: None, - drep_voting_thresholds: None, - min_committee_size: None, - committee_term_limit: None, - governance_action_validity_period: None, - governance_action_deposit: None, - drep_deposit: None, - drep_inactivity_period: None, - minfee_refscript_cost_per_byte: None, - - // Fields not found in Babbage - decentralisation_constant: None, - extra_enthropy: None, - // Fields, specific for Alonzo-compatible (Alonzo, Babbage, Shelley) - protocol_version: p.protocol_version.as_ref().map(map_protocol_version), - })) -} - -pub fn map_governance_proposals_procedures( - gov_action_id: &GovActionId, - prop: &conway::ProposalProcedure, -) -> Result { - Ok(ProposalProcedure { - deposit: prop.deposit, - reward_account: StakeAddress::from_binary(&prop.reward_account)?, - gov_action_id: gov_action_id.clone(), - gov_action: map_governance_action(&prop.gov_action)?, - anchor: map_anchor(&prop.anchor), - }) -} - -fn map_voter(voter: &conway::Voter) -> Voter { - match voter { - conway::Voter::ConstitutionalCommitteeKey(key_hash) => { - Voter::ConstitutionalCommitteeKey(to_hash(key_hash).into()) - } - conway::Voter::ConstitutionalCommitteeScript(script_hash) => { - Voter::ConstitutionalCommitteeScript(to_hash(script_hash).into()) - } - conway::Voter::DRepKey(addr_key_hash) => Voter::DRepKey(to_hash(addr_key_hash).into()), - conway::Voter::DRepScript(script_hash) => Voter::DRepScript(to_hash(script_hash).into()), - conway::Voter::StakePoolKey(key_hash) => Voter::StakePoolKey(to_pool_id(key_hash)), - } -} - -fn map_vote(vote: &conway::Vote) -> Vote { - match vote { - conway::Vote::No => Vote::No, - conway::Vote::Yes => Vote::Yes, - conway::Vote::Abstain => Vote::Abstain, - } -} - -fn map_single_governance_voting_procedure( - vote_index: u32, - proc: &conway::VotingProcedure, -) -> VotingProcedure { - VotingProcedure { - vote: map_vote(&proc.vote), - anchor: map_nullable_anchor(&proc.anchor), - vote_index, - } -} - -pub fn map_all_governance_voting_procedures( - vote_procs: &conway::VotingProcedures, -) -> Result { - let mut procs = VotingProcedures { - votes: HashMap::new(), - }; - - for (pallas_voter, pallas_pair) in vote_procs.iter() { - let voter = map_voter(pallas_voter); - - if let Some(existing) = procs.votes.insert(voter.clone(), SingleVoterVotes::default()) { - bail!("Duplicate voter {voter:?}: procedure {vote_procs:?}, existing {existing:?}"); - } - - let single_voter = procs - .votes - .get_mut(&voter) - .ok_or_else(|| anyhow!("Cannot find voter {:?}, which must present", voter))?; - - for (vote_index, (pallas_action_id, pallas_voting_procedure)) in - pallas_pair.iter().enumerate() - { - let action_id = map_gov_action_id(pallas_action_id)?; - let vp = - map_single_governance_voting_procedure(vote_index as u32, pallas_voting_procedure); - single_voter.voting_procedures.insert(action_id, vp); - } - } - - Ok(procs) -} - -pub fn map_value(pallas_value: &MultiEraValue) -> Value { - let lovelace = pallas_value.coin(); - let pallas_assets = pallas_value.assets(); - - let mut assets: NativeAssets = Vec::new(); - - for policy_group in pallas_assets { - match policy_group { - MultiEraPolicyAssets::AlonzoCompatibleOutput(policy, kvps) => { - match policy.as_ref().try_into() { - Ok(policy_id) => { - let native_assets = kvps - .iter() - .filter_map(|(name, amt)| { - AssetName::new(name).map(|asset_name| NativeAsset { - name: asset_name, - amount: *amt, - }) - }) - .collect::>(); - - assets.push((policy_id, native_assets)); - } - Err(_) => { - tracing::error!( - "Invalid policy id length: expected 28 bytes, got {}", - policy.len() - ); - continue; - } - } - } - MultiEraPolicyAssets::ConwayOutput(policy, kvps) => match policy.as_ref().try_into() { - Ok(policy_id) => { - let native_assets = kvps - .iter() - .filter_map(|(name, amt)| { - AssetName::new(name).map(|asset_name| NativeAsset { - name: asset_name, - amount: u64::from(*amt), - }) - }) - .collect(); - - assets.push((policy_id, native_assets)); - } - Err(_) => { - tracing::error!( - "Invalid policy id length: expected 28 bytes, got {}", - policy.len() - ); - continue; - } - }, - _ => {} - } - } - Value::new(lovelace, assets) -} - -pub fn map_mint_burn( - policy_group: &MultiEraPolicyAssets<'_>, -) -> Option<(PolicyId, Vec)> { - match policy_group { - MultiEraPolicyAssets::AlonzoCompatibleMint(policy, kvps) => { - let policy_id: PolicyId = match policy.as_ref().try_into() { - Ok(id) => id, - Err(_) => { - tracing::error!( - "Invalid policy id length: expected 28 bytes, got {}", - policy.len() - ); - return None; - } - }; - - let deltas = kvps - .iter() - .filter_map(|(name, amt)| { - AssetName::new(name).map(|asset_name| NativeAssetDelta { - name: asset_name, - amount: *amt, - }) - }) - .collect::>(); - - Some((policy_id, deltas)) - } - - MultiEraPolicyAssets::ConwayMint(policy, kvps) => { - let policy_id: PolicyId = match policy.as_ref().try_into() { - Ok(id) => id, - Err(_) => { - tracing::error!( - "Invalid policy id length: expected 28 bytes, got {}", - policy.len() - ); - return None; - } - }; - - let deltas = kvps - .iter() - .filter_map(|(name, amt)| { - AssetName::new(name).map(|asset_name| NativeAssetDelta { - name: asset_name, - amount: i64::from(*amt), - }) - }) - .collect::>(); - Some((policy_id, deltas)) - } - - _ => None, - } -} - -pub fn map_datum(datum: &Option) -> Option { - match datum { - Some(pallas::ledger::primitives::conway::MintedDatumOption::Hash(h)) => { - Some(Datum::Hash(h.to_vec())) - } - Some(pallas::ledger::primitives::conway::MintedDatumOption::Data(d)) => { - Some(Datum::Inline(d.raw_cbor().to_vec())) - } - None => None, - } -} - -pub fn map_reference_script(script: &Option) -> Option { - match script { - Some(PseudoScript::NativeScript(script)) => { - Some(ReferenceScript::Native(script.raw_cbor().to_vec())) - } - Some(PseudoScript::PlutusV1Script(script)) => { - Some(ReferenceScript::PlutusV1(script.as_ref().to_vec())) - } - Some(PseudoScript::PlutusV2Script(script)) => { - Some(ReferenceScript::PlutusV2(script.as_ref().to_vec())) - } - Some(PseudoScript::PlutusV3Script(script)) => { - Some(ReferenceScript::PlutusV3(script.as_ref().to_vec())) - } - None => None, - } -} - -pub fn map_metadata(metadata: &pallas_primitives::Metadatum) -> Metadata { - match metadata { - pallas_primitives::Metadatum::Int(pallas_primitives::Int(i)) => { - Metadata::Int(MetadataInt(*i)) - } - pallas_primitives::Metadatum::Bytes(b) => Metadata::Bytes(b.to_vec()), - pallas_primitives::Metadatum::Text(s) => Metadata::Text(s.clone()), - pallas_primitives::Metadatum::Array(a) => { - Metadata::Array(a.iter().map(map_metadata).collect()) - } - pallas_primitives::Metadatum::Map(m) => { - Metadata::Map(m.iter().map(|(k, v)| (map_metadata(k), map_metadata(v))).collect()) - } - } -} diff --git a/codec/src/parameter.rs b/codec/src/parameter.rs new file mode 100644 index 00000000..d137e962 --- /dev/null +++ b/codec/src/parameter.rs @@ -0,0 +1,391 @@ +use crate::{address::map_stake_credential, utils::*}; +use acropolis_common::{ + protocol_params::{Nonce, NonceVariant, ProtocolVersion}, + *, +}; +use anyhow::{Result, anyhow, bail}; +use pallas_primitives::{ + ProtocolVersion as PallasProtocolVersion, ScriptHash as PallasScriptHash, alonzo, babbage, + conway, +}; +use std::collections::{HashMap, HashSet}; + +fn map_u32_to_u64(n: Option) -> Option { + n.as_ref().map(|x| *x as u64) +} + +fn map_constitution(constitution: &conway::Constitution) -> Constitution { + Constitution { + anchor: map_anchor(&constitution.anchor), + guardrail_script: map_nullable(to_hash, &constitution.guardrail_script), + } +} + +fn map_protocol_version((major, minor): &PallasProtocolVersion) -> ProtocolVersion { + ProtocolVersion { + minor: *minor, + major: *major, + } +} + +fn map_alonzo_single_model(model: &alonzo::CostModel) -> Option { + Some(CostModel::new(model.clone())) +} + +fn map_alonzo_nonce(e: &alonzo::Nonce) -> Nonce { + Nonce { + tag: match &e.variant { + alonzo::NonceVariant::NeutralNonce => NonceVariant::NeutralNonce, + alonzo::NonceVariant::Nonce => NonceVariant::Nonce, + }, + hash: e.hash.map(|v| *v), + } +} + +fn map_alonzo_cost_models(pallas_cost_models: &alonzo::CostModels) -> Result { + let mut res = CostModels { + plutus_v1: None, + plutus_v2: None, + plutus_v3: None, + }; + for (lang, mdl) in pallas_cost_models.iter() { + if *lang == alonzo::Language::PlutusV1 { + res.plutus_v1 = map_alonzo_single_model(mdl); + } else { + bail!("Alonzo may not contain {lang:?} language"); + } + } + Ok(res) +} + +fn map_conway_execution_costs(pallas_ex_costs: &conway::ExUnitPrices) -> ExUnitPrices { + ExUnitPrices { + mem_price: map_unit_interval(&pallas_ex_costs.mem_price), + step_price: map_unit_interval(&pallas_ex_costs.step_price), + } +} + +fn map_conway_cost_models(pallas_cost_models: &conway::CostModels) -> CostModels { + CostModels { + plutus_v1: pallas_cost_models.plutus_v1.as_ref().map(|x| CostModel::new(x.clone())), + plutus_v2: pallas_cost_models.plutus_v2.as_ref().map(|x| CostModel::new(x.clone())), + plutus_v3: pallas_cost_models.plutus_v3.as_ref().map(|x| CostModel::new(x.clone())), + } +} + +fn map_pool_voting_thresholds(ts: &conway::PoolVotingThresholds) -> PoolVotingThresholds { + PoolVotingThresholds { + motion_no_confidence: map_unit_interval(&ts.motion_no_confidence), + committee_normal: map_unit_interval(&ts.committee_normal), + committee_no_confidence: map_unit_interval(&ts.committee_no_confidence), + hard_fork_initiation: map_unit_interval(&ts.hard_fork_initiation), + security_voting_threshold: map_unit_interval(&ts.security_voting_threshold), + } +} + +fn map_drep_voting_thresholds(ts: &conway::DRepVotingThresholds) -> DRepVotingThresholds { + DRepVotingThresholds { + motion_no_confidence: map_unit_interval(&ts.motion_no_confidence), + committee_normal: map_unit_interval(&ts.committee_normal), + committee_no_confidence: map_unit_interval(&ts.committee_no_confidence), + update_constitution: map_unit_interval(&ts.update_constitution), + hard_fork_initiation: map_unit_interval(&ts.hard_fork_initiation), + pp_network_group: map_unit_interval(&ts.pp_network_group), + pp_economic_group: map_unit_interval(&ts.pp_economic_group), + pp_technical_group: map_unit_interval(&ts.pp_technical_group), + pp_governance_group: map_unit_interval(&ts.pp_governance_group), + treasury_withdrawal: map_unit_interval(&ts.treasury_withdrawal), + } +} + +pub fn map_alonzo_protocol_param_update( + p: &alonzo::ProtocolParamUpdate, +) -> Result> { + Ok(Box::new(ProtocolParamUpdate { + // Fields, common for Conway and Alonzo-compatible + minfee_a: map_u32_to_u64(p.minfee_a), + minfee_b: map_u32_to_u64(p.minfee_b), + max_block_body_size: map_u32_to_u64(p.max_block_body_size), + max_transaction_size: map_u32_to_u64(p.max_transaction_size), + max_block_header_size: map_u32_to_u64(p.max_block_header_size), + key_deposit: p.key_deposit, + pool_deposit: p.pool_deposit, + maximum_epoch: p.maximum_epoch, + desired_number_of_stake_pools: map_u32_to_u64(p.desired_number_of_stake_pools), + pool_pledge_influence: p.pool_pledge_influence.as_ref().map(&map_unit_interval), + expansion_rate: p.expansion_rate.as_ref().map(&map_unit_interval), + treasury_growth_rate: p.treasury_growth_rate.as_ref().map(&map_unit_interval), + min_pool_cost: p.min_pool_cost, + lovelace_per_utxo_word: p.ada_per_utxo_byte, // Pre Babbage (Represents cost per 8-byte word) + coins_per_utxo_byte: None, + cost_models_for_script_languages: p + .cost_models_for_script_languages + .as_ref() + .map(&map_alonzo_cost_models) + .transpose()?, + execution_costs: p.execution_costs.as_ref().map(&map_execution_costs), + max_tx_ex_units: p.max_tx_ex_units.as_ref().map(&map_ex_units), + max_block_ex_units: p.max_block_ex_units.as_ref().map(&map_ex_units), + max_value_size: map_u32_to_u64(p.max_value_size), + collateral_percentage: map_u32_to_u64(p.collateral_percentage), + max_collateral_inputs: map_u32_to_u64(p.max_collateral_inputs), + + // Fields, specific for Conway + pool_voting_thresholds: None, + drep_voting_thresholds: None, + min_committee_size: None, + committee_term_limit: None, + governance_action_validity_period: None, + governance_action_deposit: None, + drep_deposit: None, + drep_inactivity_period: None, + minfee_refscript_cost_per_byte: None, + + // Fields, specific for Alonzo-compatible (Alonzo, Babbage, Shelley) + decentralisation_constant: p.decentralization_constant.as_ref().map(&map_unit_interval), + extra_enthropy: p.extra_entropy.as_ref().map(&map_alonzo_nonce), + protocol_version: p.protocol_version.as_ref().map(map_protocol_version), + })) +} + +fn map_babbage_cost_models(cost_models: &babbage::CostModels) -> CostModels { + CostModels { + plutus_v1: cost_models.plutus_v1.as_ref().map(|p| CostModel::new(p.clone())), + plutus_v2: cost_models.plutus_v2.as_ref().map(|p| CostModel::new(p.clone())), + plutus_v3: None, + } +} + +pub fn map_babbage_protocol_param_update( + p: &babbage::ProtocolParamUpdate, +) -> Result> { + Ok(Box::new(ProtocolParamUpdate { + // Fields, common for Conway and Alonzo-compatible + minfee_a: map_u32_to_u64(p.minfee_a), + minfee_b: map_u32_to_u64(p.minfee_b), + max_block_body_size: map_u32_to_u64(p.max_block_body_size), + max_transaction_size: map_u32_to_u64(p.max_transaction_size), + max_block_header_size: map_u32_to_u64(p.max_block_header_size), + key_deposit: p.key_deposit, + pool_deposit: p.pool_deposit, + maximum_epoch: p.maximum_epoch, + desired_number_of_stake_pools: map_u32_to_u64(p.desired_number_of_stake_pools), + pool_pledge_influence: p.pool_pledge_influence.as_ref().map(&map_unit_interval), + expansion_rate: p.expansion_rate.as_ref().map(&map_unit_interval), + treasury_growth_rate: p.treasury_growth_rate.as_ref().map(&map_unit_interval), + min_pool_cost: p.min_pool_cost, + lovelace_per_utxo_word: None, + coins_per_utxo_byte: p.ada_per_utxo_byte, + cost_models_for_script_languages: p + .cost_models_for_script_languages + .as_ref() + .map(&map_babbage_cost_models), + execution_costs: p.execution_costs.as_ref().map(&map_execution_costs), + max_tx_ex_units: p.max_tx_ex_units.as_ref().map(&map_ex_units), + max_block_ex_units: p.max_block_ex_units.as_ref().map(&map_ex_units), + max_value_size: map_u32_to_u64(p.max_value_size), + collateral_percentage: map_u32_to_u64(p.collateral_percentage), + max_collateral_inputs: map_u32_to_u64(p.max_collateral_inputs), + + // Fields, specific for Conway + pool_voting_thresholds: None, + drep_voting_thresholds: None, + min_committee_size: None, + committee_term_limit: None, + governance_action_validity_period: None, + governance_action_deposit: None, + drep_deposit: None, + drep_inactivity_period: None, + minfee_refscript_cost_per_byte: None, + + // Fields not found in Babbage + decentralisation_constant: None, + extra_enthropy: None, + // Fields, specific for Alonzo-compatible (Alonzo, Babbage, Shelley) + protocol_version: p.protocol_version.as_ref().map(map_protocol_version), + })) +} + +fn map_conway_protocol_param_update(p: &conway::ProtocolParamUpdate) -> Box { + Box::new(ProtocolParamUpdate { + // Fields, common for Conway and Alonzo-compatible + minfee_a: p.minfee_a, + minfee_b: p.minfee_b, + max_block_body_size: p.max_block_body_size, + max_transaction_size: p.max_transaction_size, + max_block_header_size: p.max_block_header_size, + key_deposit: p.key_deposit, + pool_deposit: p.pool_deposit, + maximum_epoch: p.maximum_epoch, + desired_number_of_stake_pools: p.desired_number_of_stake_pools, + pool_pledge_influence: p.pool_pledge_influence.as_ref().map(&map_unit_interval), + expansion_rate: p.expansion_rate.as_ref().map(&map_unit_interval), + treasury_growth_rate: p.treasury_growth_rate.as_ref().map(&map_unit_interval), + min_pool_cost: p.min_pool_cost, + coins_per_utxo_byte: p.ada_per_utxo_byte, + lovelace_per_utxo_word: None, + cost_models_for_script_languages: p + .cost_models_for_script_languages + .as_ref() + .map(&map_conway_cost_models), + execution_costs: p.execution_costs.as_ref().map(&map_conway_execution_costs), + max_tx_ex_units: p.max_tx_ex_units.as_ref().map(&map_ex_units), + max_block_ex_units: p.max_block_ex_units.as_ref().map(&map_ex_units), + max_value_size: p.max_value_size, + collateral_percentage: p.collateral_percentage, + max_collateral_inputs: p.max_collateral_inputs, + + // Fields, specific for Conway + pool_voting_thresholds: p.pool_voting_thresholds.as_ref().map(&map_pool_voting_thresholds), + drep_voting_thresholds: p.drep_voting_thresholds.as_ref().map(&map_drep_voting_thresholds), + min_committee_size: p.min_committee_size, + committee_term_limit: p.committee_term_limit, + governance_action_validity_period: p.governance_action_validity_period, + governance_action_deposit: p.governance_action_deposit, + drep_deposit: p.drep_deposit, + drep_inactivity_period: p.drep_inactivity_period, + minfee_refscript_cost_per_byte: p + .minfee_refscript_cost_per_byte + .as_ref() + .map(&map_unit_interval), + + // Fields, missing from Conway + decentralisation_constant: None, + extra_enthropy: None, + protocol_version: None, + }) +} + +fn map_governance_action(action: &conway::GovAction) -> Result { + match action { + conway::GovAction::ParameterChange(id, protocol_update, script) => { + Ok(GovernanceAction::ParameterChange(ParameterChangeAction { + previous_action_id: map_nullable_gov_action_id(id)?, + protocol_param_update: map_conway_protocol_param_update(protocol_update), + script_hash: map_nullable(|x: &PallasScriptHash| x.to_vec(), script), + })) + } + + conway::GovAction::HardForkInitiation(id, version) => Ok( + GovernanceAction::HardForkInitiation(HardForkInitiationAction { + previous_action_id: map_nullable_gov_action_id(id)?, + protocol_version: ProtocolVersion::new(version.0, version.1), + }), + ), + + conway::GovAction::TreasuryWithdrawals(withdrawals, script) => Ok( + GovernanceAction::TreasuryWithdrawals(TreasuryWithdrawalsAction { + rewards: HashMap::from_iter( + withdrawals.iter().map(|(account, coin)| (account.to_vec(), *coin)), + ), + script_hash: map_nullable(|x: &PallasScriptHash| x.to_vec(), script), + }), + ), + + conway::GovAction::NoConfidence(id) => Ok(GovernanceAction::NoConfidence( + map_nullable_gov_action_id(id)?, + )), + + conway::GovAction::UpdateCommittee(id, committee, threshold, terms) => { + Ok(GovernanceAction::UpdateCommittee(UpdateCommitteeAction { + previous_action_id: map_nullable_gov_action_id(id)?, + data: CommitteeChange { + removed_committee_members: HashSet::from_iter( + committee.iter().map(map_stake_credential), + ), + new_committee_members: HashMap::from_iter( + threshold.iter().map(|(k, v)| (map_stake_credential(k), *v)), + ), + terms: map_unit_interval(terms), + }, + })) + } + + conway::GovAction::NewConstitution(id, constitution) => { + Ok(GovernanceAction::NewConstitution(NewConstitutionAction { + previous_action_id: map_nullable_gov_action_id(id)?, + new_constitution: map_constitution(constitution), + })) + } + + conway::GovAction::Information => Ok(GovernanceAction::Information), + } +} + +pub fn map_governance_proposals_procedures( + gov_action_id: &GovActionId, + prop: &conway::ProposalProcedure, +) -> Result { + Ok(ProposalProcedure { + deposit: prop.deposit, + reward_account: StakeAddress::from_binary(&prop.reward_account)?, + gov_action_id: gov_action_id.clone(), + gov_action: map_governance_action(&prop.gov_action)?, + anchor: map_anchor(&prop.anchor), + }) +} + +fn map_voter(voter: &conway::Voter) -> Voter { + match voter { + conway::Voter::ConstitutionalCommitteeKey(key_hash) => { + Voter::ConstitutionalCommitteeKey(to_hash(key_hash).into()) + } + conway::Voter::ConstitutionalCommitteeScript(script_hash) => { + Voter::ConstitutionalCommitteeScript(to_hash(script_hash).into()) + } + conway::Voter::DRepKey(addr_key_hash) => Voter::DRepKey(to_hash(addr_key_hash).into()), + conway::Voter::DRepScript(script_hash) => Voter::DRepScript(to_hash(script_hash).into()), + conway::Voter::StakePoolKey(key_hash) => Voter::StakePoolKey(to_pool_id(key_hash)), + } +} + +fn map_vote(vote: &conway::Vote) -> Vote { + match vote { + conway::Vote::No => Vote::No, + conway::Vote::Yes => Vote::Yes, + conway::Vote::Abstain => Vote::Abstain, + } +} + +fn map_single_governance_voting_procedure( + vote_index: u32, + proc: &conway::VotingProcedure, +) -> VotingProcedure { + VotingProcedure { + vote: map_vote(&proc.vote), + anchor: map_nullable_anchor(&proc.anchor), + vote_index, + } +} + +pub fn map_all_governance_voting_procedures( + vote_procs: &conway::VotingProcedures, +) -> Result { + let mut procs = VotingProcedures { + votes: HashMap::new(), + }; + + for (pallas_voter, pallas_pair) in vote_procs.iter() { + let voter = map_voter(pallas_voter); + + if let Some(existing) = procs.votes.insert(voter.clone(), SingleVoterVotes::default()) { + bail!("Duplicate voter {voter:?}: procedure {vote_procs:?}, existing {existing:?}"); + } + + let single_voter = procs + .votes + .get_mut(&voter) + .ok_or_else(|| anyhow!("Cannot find voter {:?}, which must present", voter))?; + + for (vote_index, (pallas_action_id, pallas_voting_procedure)) in + pallas_pair.iter().enumerate() + { + let action_id = map_gov_action_id(pallas_action_id)?; + let vp = + map_single_governance_voting_procedure(vote_index as u32, pallas_voting_procedure); + single_voter.voting_procedures.insert(action_id, vp); + } + } + + Ok(procs) +} diff --git a/codec/src/tx.rs b/codec/src/tx.rs new file mode 100644 index 00000000..5762a6b3 --- /dev/null +++ b/codec/src/tx.rs @@ -0,0 +1,83 @@ +use acropolis_common::{AssetName, Metadata, MetadataInt, NativeAssetDelta, NetworkId, PolicyId}; +use anyhow::{Result, anyhow}; +use pallas::ledger::addresses; +use pallas_primitives::Metadatum as PallasMetadatum; +use pallas_traverse::MultiEraPolicyAssets; + +/// Map Pallas Network to our NetworkId +pub fn map_network(network: addresses::Network) -> Result { + match network { + addresses::Network::Mainnet => Ok(NetworkId::Mainnet), + addresses::Network::Testnet => Ok(NetworkId::Testnet), + _ => Err(anyhow!("Unknown network in address")), + } +} + +pub fn map_mint_burn( + policy_group: &MultiEraPolicyAssets<'_>, +) -> Option<(PolicyId, Vec)> { + match policy_group { + MultiEraPolicyAssets::AlonzoCompatibleMint(policy, kvps) => { + let policy_id: PolicyId = match policy.as_ref().try_into() { + Ok(id) => id, + Err(_) => { + tracing::error!( + "Invalid policy id length: expected 28 bytes, got {}", + policy.len() + ); + return None; + } + }; + + let deltas = kvps + .iter() + .filter_map(|(name, amt)| { + AssetName::new(name).map(|asset_name| NativeAssetDelta { + name: asset_name, + amount: *amt, + }) + }) + .collect::>(); + + Some((policy_id, deltas)) + } + + MultiEraPolicyAssets::ConwayMint(policy, kvps) => { + let policy_id: PolicyId = match policy.as_ref().try_into() { + Ok(id) => id, + Err(_) => { + tracing::error!( + "Invalid policy id length: expected 28 bytes, got {}", + policy.len() + ); + return None; + } + }; + + let deltas = kvps + .iter() + .filter_map(|(name, amt)| { + AssetName::new(name).map(|asset_name| NativeAssetDelta { + name: asset_name, + amount: i64::from(*amt), + }) + }) + .collect::>(); + Some((policy_id, deltas)) + } + + _ => None, + } +} + +pub fn map_metadata(metadata: &PallasMetadatum) -> Metadata { + match metadata { + PallasMetadatum::Int(pallas_primitives::Int(i)) => Metadata::Int(MetadataInt(*i)), + PallasMetadatum::Bytes(b) => Metadata::Bytes(b.to_vec()), + PallasMetadatum::Text(s) => Metadata::Text(s.clone()), + PallasMetadatum::Array(a) => Metadata::Array(a.iter().map(map_metadata).collect()), + PallasMetadatum::Map(m) => { + Metadata::Map(m.iter().map(|(k, v)| (map_metadata(k), map_metadata(v))).collect()) + } + } +} diff --git a/codec/src/utils.rs b/codec/src/utils.rs new file mode 100644 index 00000000..340f7b47 --- /dev/null +++ b/codec/src/utils.rs @@ -0,0 +1,145 @@ +use acropolis_common::{hash::Hash, rational_number::RationalNumber, *}; +use anyhow::{Result, anyhow}; +use pallas_primitives::{ + ExUnitPrices as PallasExUnitPrices, Nullable, Relay as PallasRelay, conway, +}; +use std::net::{Ipv4Addr, Ipv6Addr}; + +/// Convert a Pallas Hash reference to an Acropolis Hash (owned) +/// Works for any hash size N +pub fn to_hash(pallas_hash: &pallas_primitives::Hash) -> Hash { + Hash::try_from(pallas_hash.as_ref()).unwrap() +} + +/// Convert a Pallas Hash reference to an Acropolis Hash (owned) +/// Works for any hash size N +pub fn genesis_to_hash(pallas_hash: &pallas_primitives::Genesishash) -> Hash<28> { + Hash::try_from(pallas_hash.as_ref()).unwrap() +} + +/// Convert a Pallas Hash reference to an Acropolis Hash (owned) +/// Works for any hash size N +pub fn genesis_delegate_to_hash(pallas_hash: &pallas_primitives::GenesisDelegateHash) -> PoolId { + PoolId::try_from(pallas_hash.as_ref()).unwrap() +} + +/// Convert a Pallas Hash<28> reference to an Acropolis PoolId +pub fn to_pool_id(pallas_hash: &pallas_primitives::Hash<28>) -> PoolId { + to_hash(pallas_hash).into() +} + +/// Convert a Pallas Hash<32> reference to an Acropolis VRFKey +pub fn to_vrf_key(pallas_hash: &pallas_primitives::Hash<32>) -> VrfKeyHash { + VrfKeyHash::try_from(pallas_hash.as_ref()).unwrap() +} + +pub fn map_nullable( + f: impl FnOnce(&Src) -> Dst, + nullable_src: &Nullable, +) -> Option { + match nullable_src { + Nullable::Some(src) => Some(f(src)), + _ => None, + } +} + +pub fn map_nullable_result( + f: impl FnOnce(&Src) -> Result, + nullable_src: &Nullable, +) -> Result> { + match nullable_src { + Nullable::Some(src) => { + let res = f(src)?; + Ok(Some(res)) + } + _ => Ok(None), + } +} + +pub fn map_unit_interval(pallas_interval: &conway::UnitInterval) -> RationalNumber { + RationalNumber::new(pallas_interval.numerator, pallas_interval.denominator) +} + +pub fn map_ex_units(pallas_units: &conway::ExUnits) -> ExUnits { + ExUnits { + mem: pallas_units.mem, + steps: pallas_units.steps, + } +} + +pub fn map_execution_costs(pallas_ex_costs: &PallasExUnitPrices) -> ExUnitPrices { + ExUnitPrices { + mem_price: map_unit_interval(&pallas_ex_costs.mem_price), + step_price: map_unit_interval(&pallas_ex_costs.step_price), + } +} + +/// Map a Pallas Relay to ours +pub fn map_relay(relay: &PallasRelay) -> Relay { + match relay { + PallasRelay::SingleHostAddr(port, ipv4, ipv6) => Relay::SingleHostAddr(SingleHostAddr { + port: match port { + Nullable::Some(port) => Some(*port as u16), + _ => None, + }, + ipv4: match ipv4 { + Nullable::Some(ipv4) => <[u8; 4]>::try_from(ipv4).ok().map(Ipv4Addr::from), + _ => None, + }, + ipv6: match ipv6 { + Nullable::Some(ipv6) => <[u8; 16]>::try_from(ipv6).ok().map(Ipv6Addr::from), + _ => None, + }, + }), + PallasRelay::SingleHostName(port, dns_name) => Relay::SingleHostName(SingleHostName { + port: match port { + Nullable::Some(port) => Some(*port as u16), + _ => None, + }, + dns_name: dns_name.clone(), + }), + PallasRelay::MultiHostName(dns_name) => Relay::MultiHostName(MultiHostName { + dns_name: dns_name.clone(), + }), + } +} + +/// Map a Pallas DRep to our DRepChoice +pub fn map_drep(drep: &conway::DRep) -> DRepChoice { + match drep { + conway::DRep::Key(key_hash) => DRepChoice::Key(to_hash(key_hash)), + conway::DRep::Script(script_hash) => DRepChoice::Script(to_hash(script_hash)), + conway::DRep::Abstain => DRepChoice::Abstain, + conway::DRep::NoConfidence => DRepChoice::NoConfidence, + } +} + +pub fn map_gov_action_id(pallas_action_id: &conway::GovActionId) -> Result { + let act_idx_u8: u8 = match pallas_action_id.action_index.try_into() { + Ok(v) => v, + Err(e) => return Err(anyhow!("Invalid action index {e}")), + }; + + Ok(GovActionId { + transaction_id: TxHash::from(*pallas_action_id.transaction_id), + action_index: act_idx_u8, + }) +} + +pub fn map_nullable_gov_action_id( + id: &Nullable, +) -> Result> { + map_nullable_result(map_gov_action_id, id) +} + +pub fn map_anchor(anchor: &conway::Anchor) -> Anchor { + Anchor { + url: anchor.url.clone(), + data_hash: anchor.content_hash.to_vec(), + } +} + +/// Map a Nullable Anchor to ours +pub fn map_nullable_anchor(anchor: &Nullable) -> Option { + map_nullable(map_anchor, anchor) +} diff --git a/codec/src/utxo.rs b/codec/src/utxo.rs new file mode 100644 index 00000000..fa84df49 --- /dev/null +++ b/codec/src/utxo.rs @@ -0,0 +1,175 @@ +use crate::address::map_address; +use acropolis_common::{validation::ValidationError, *}; +use pallas_primitives::conway; +use pallas_traverse::{MultiEraInput, MultiEraPolicyAssets, MultiEraTx, MultiEraValue}; + +pub fn map_value(pallas_value: &MultiEraValue) -> Value { + let lovelace = pallas_value.coin(); + let pallas_assets = pallas_value.assets(); + + let mut assets: NativeAssets = Vec::new(); + + for policy_group in pallas_assets { + match policy_group { + MultiEraPolicyAssets::AlonzoCompatibleOutput(policy, kvps) => { + match policy.as_ref().try_into() { + Ok(policy_id) => { + let native_assets = kvps + .iter() + .filter_map(|(name, amt)| { + AssetName::new(name).map(|asset_name| NativeAsset { + name: asset_name, + amount: *amt, + }) + }) + .collect::>(); + + assets.push((policy_id, native_assets)); + } + Err(_) => { + tracing::error!( + "Invalid policy id length: expected 28 bytes, got {}", + policy.len() + ); + continue; + } + } + } + MultiEraPolicyAssets::ConwayOutput(policy, kvps) => match policy.as_ref().try_into() { + Ok(policy_id) => { + let native_assets = kvps + .iter() + .filter_map(|(name, amt)| { + AssetName::new(name).map(|asset_name| NativeAsset { + name: asset_name, + amount: u64::from(*amt), + }) + }) + .collect(); + + assets.push((policy_id, native_assets)); + } + Err(_) => { + tracing::error!( + "Invalid policy id length: expected 28 bytes, got {}", + policy.len() + ); + continue; + } + }, + _ => {} + } + } + Value::new(lovelace, assets) +} + +pub fn map_transaction_inputs(inputs: &Vec) -> Vec { + let mut parsed_inputs = Vec::new(); + for input in inputs { + // MultiEraInput + let oref = input.output_ref(); + let tx_ref = TxOutRef::new(TxHash::from(**oref.hash()), oref.index() as u16); + + parsed_inputs.push(tx_ref); + } + + parsed_inputs +} + +pub fn map_datum(datum: &Option) -> Option { + match datum { + Some(conway::MintedDatumOption::Hash(h)) => Some(Datum::Hash(h.to_vec())), + Some(conway::MintedDatumOption::Data(d)) => Some(Datum::Inline(d.raw_cbor().to_vec())), + None => None, + } +} + +pub fn map_reference_script(script: &Option) -> Option { + match script { + Some(conway::PseudoScript::NativeScript(script)) => { + Some(ReferenceScript::Native(script.raw_cbor().to_vec())) + } + Some(conway::PseudoScript::PlutusV1Script(script)) => { + Some(ReferenceScript::PlutusV1(script.as_ref().to_vec())) + } + Some(conway::PseudoScript::PlutusV2Script(script)) => { + Some(ReferenceScript::PlutusV2(script.as_ref().to_vec())) + } + Some(conway::PseudoScript::PlutusV3Script(script)) => { + Some(ReferenceScript::PlutusV3(script.as_ref().to_vec())) + } + None => None, + } +} + +pub fn map_transaction_inputs_outputs( + block_number: u32, + tx_index: u16, + tx: &MultiEraTx, +) -> ( + Vec, + Vec<(TxOutRef, TxOutput)>, + Vec, +) { + let mut parsed_inputs = Vec::new(); + let mut parsed_outputs = Vec::new(); + let mut errors = Vec::new(); + + let Ok(tx_hash) = tx.hash().to_vec().try_into() else { + errors.push(ValidationError::MalformedTransaction( + tx_index, + format!("Tx has incorrect hash length ({:?})", tx.hash().to_vec()), + )); + return (parsed_inputs, parsed_outputs, errors); + }; + + let inputs = tx.consumes(); + let outputs = tx.produces(); + + for input in inputs { + let tx_ref = TxOutRef::new( + TxHash::from(**input.output_ref().hash()), + input.output_ref().index() as u16, + ); + parsed_inputs.push(tx_ref); + } + + for (index, output) in outputs { + let tx_out_ref = TxOutRef { + tx_hash, + output_index: index as u16, + }; + + let utxo_id = UTxOIdentifier::new(block_number, tx_index, tx_out_ref.output_index); + + match output.address() { + Ok(pallas_address) => match map_address(&pallas_address) { + Ok(address) => { + // Add TxOutput to utxo_deltas + parsed_outputs.push(( + tx_out_ref, + TxOutput { + utxo_identifier: utxo_id, + address, + value: map_value(&output.value()), + datum: map_datum(&output.datum()), + reference_script: map_reference_script(&output.script_ref()), + }, + )); + } + Err(e) => { + errors.push(ValidationError::MalformedTransaction( + tx_index, + format!("Output {index} has been ignored: {e}"), + )); + } + }, + Err(e) => errors.push(ValidationError::MalformedTransaction( + tx_index, + format!("Can't parse output {index} in tx: {e}"), + )), + } + } + + (parsed_inputs, parsed_outputs, errors) +} diff --git a/common/src/validation.rs b/common/src/validation.rs index 2e9ff385..d10b8a13 100644 --- a/common/src/validation.rs +++ b/common/src/validation.rs @@ -13,13 +13,6 @@ use crate::{ }; /// Transaction Validation Error -/// -/// Shelley Era Errors: -/// Reference: https://github.com/IntersectMBO/cardano-ledger/blob/24ef1741c5e0109e4d73685a24d8e753e225656d/eras/shelley/impl/src/Cardano/Ledger/Shelley/Rules/Utxo.hs#L343 -/// -/// Allegra Era Errors: -/// Reference: https://github.com/IntersectMBO/cardano-ledger/blob/24ef1741c5e0109e4d73685a24d8e753e225656d/eras/allegra/impl/src/Cardano/Ledger/Allegra/Rules/Utxo.hs#L160 -/// #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Error, PartialEq, Eq)] pub enum TransactionValidationError { /// **Cause**: Raw Transaction CBOR is invalid @@ -30,7 +23,7 @@ pub enum TransactionValidationError { #[error("Malformed Transaction: era={era}, reason={reason}")] MalformedTransaction { era: Era, reason: String }, - /// **Cause**: UTXO validation error + /// **Cause**: UTxO rules failure #[error("{0}")] UTxOValidationError(#[from] UTxOValidationError), @@ -39,6 +32,12 @@ pub enum TransactionValidationError { Other(String), } +/// UTxO rules failure +/// Shelley Era Errors: +/// Reference: https://github.com/IntersectMBO/cardano-ledger/blob/24ef1741c5e0109e4d73685a24d8e753e225656d/eras/shelley/impl/src/Cardano/Ledger/Shelley/Rules/Utxo.hs#L343 +/// +/// Allegra Era Errors: +/// Reference: https://github.com/IntersectMBO/cardano-ledger/blob/24ef1741c5e0109e4d73685a24d8e753e225656d/eras/allegra/impl/src/Cardano/Ledger/Allegra/Rules/Utxo.hs#L160 #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Error, PartialEq, Eq)] pub enum UTxOValidationError { /// ------------ Shelley Era Errors ------------ diff --git a/modules/chain_store/src/chain_store.rs b/modules/chain_store/src/chain_store.rs index a2f4225a..8d1ba02b 100644 --- a/modules/chain_store/src/chain_store.rs +++ b/modules/chain_store/src/chain_store.rs @@ -1,11 +1,6 @@ mod stores; use crate::stores::{fjall::FjallStore, Block, Store, Tx}; -use acropolis_codec::{ - block::map_to_block_issuer, - map_parameters, - map_parameters::{map_metadata, map_stake_address, to_pool_id, to_pool_reg}, -}; use acropolis_common::queries::blocks::TransactionHashesAndTimeStamps; use acropolis_common::queries::errors::QueryError; use acropolis_common::{ @@ -668,7 +663,7 @@ impl ChainStore { slot: header.slot(), epoch: block.extra.epoch, epoch_slot: block.extra.epoch_slot, - issuer: map_to_block_issuer( + issuer: acropolis_codec::map_to_block_issuer( &header, &state.byron_heavy_delegates, &state.shelley_genesis_delegates, @@ -753,7 +748,7 @@ impl ChainStore { let hash = TxHash::from(*tx.hash()); for output in tx.outputs() { if let Ok(pallas_address) = output.address() { - if let Ok(address) = map_parameters::map_address(&pallas_address) { + if let Ok(address) = acropolis_codec::map_address(&pallas_address) { addresses .entry(BechOrdAddress(address)) .or_insert_with(Vec::new) @@ -889,14 +884,14 @@ impl ChainStore { alonzo::Certificate::StakeRegistration(cred) => { certs.push(TransactionStakeCertificate { index: index as u64, - address: map_stake_address(cred, network_id.clone()), + address: acropolis_codec::map_stake_address(cred, network_id.clone()), registration: true, }); } alonzo::Certificate::StakeDeregistration(cred) => { certs.push(TransactionStakeCertificate { index: index as u64, - address: map_stake_address(cred, network_id.clone()), + address: acropolis_codec::map_stake_address(cred, network_id.clone()), registration: false, }); } @@ -906,28 +901,28 @@ impl ChainStore { conway::Certificate::StakeRegistration(cred) => { certs.push(TransactionStakeCertificate { index: index as u64, - address: map_stake_address(cred, network_id.clone()), + address: acropolis_codec::map_stake_address(cred, network_id.clone()), registration: true, }); } conway::Certificate::StakeDeregistration(cred) => { certs.push(TransactionStakeCertificate { index: index as u64, - address: map_stake_address(cred, network_id.clone()), + address: acropolis_codec::map_stake_address(cred, network_id.clone()), registration: false, }); } conway::Certificate::StakeRegDeleg(cred, _, _) => { certs.push(TransactionStakeCertificate { index: index as u64, - address: map_stake_address(cred, network_id.clone()), + address: acropolis_codec::map_stake_address(cred, network_id.clone()), registration: true, }); } conway::Certificate::StakeVoteRegDeleg(cred, _, _, _) => { certs.push(TransactionStakeCertificate { index: index as u64, - address: map_stake_address(cred, network_id.clone()), + address: acropolis_codec::map_stake_address(cred, network_id.clone()), registration: true, }); } @@ -957,8 +952,8 @@ impl ChainStore { { certs.push(TransactionDelegationCertificate { index: index as u64, - address: map_stake_address(cred, network_id.clone()), - pool_id: to_pool_id(pool_key_hash), + address: acropolis_codec::map_stake_address(cred, network_id.clone()), + pool_id: acropolis_codec::to_pool_id(pool_key_hash), active_epoch: tx.block.extra.epoch + 1, }); } @@ -967,24 +962,24 @@ impl ChainStore { conway::Certificate::StakeDelegation(cred, pool_key_hash) => { certs.push(TransactionDelegationCertificate { index: index as u64, - address: map_stake_address(cred, network_id.clone()), - pool_id: to_pool_id(pool_key_hash), + address: acropolis_codec::map_stake_address(cred, network_id.clone()), + pool_id: acropolis_codec::to_pool_id(pool_key_hash), active_epoch: tx.block.extra.epoch + 1, }); } conway::Certificate::StakeRegDeleg(cred, pool_key_hash, _) => { certs.push(TransactionDelegationCertificate { index: index as u64, - address: map_stake_address(cred, network_id.clone()), - pool_id: to_pool_id(pool_key_hash), + address: acropolis_codec::map_stake_address(cred, network_id.clone()), + pool_id: acropolis_codec::to_pool_id(pool_key_hash), active_epoch: tx.block.extra.epoch + 1, }); } conway::Certificate::StakeVoteRegDeleg(cred, pool_key_hash, _, _) => { certs.push(TransactionDelegationCertificate { index: index as u64, - address: map_stake_address(cred, network_id.clone()), - pool_id: to_pool_id(pool_key_hash), + address: acropolis_codec::map_stake_address(cred, network_id.clone()), + pool_id: acropolis_codec::to_pool_id(pool_key_hash), active_epoch: tx.block.extra.epoch + 1, }); } @@ -1037,7 +1032,10 @@ impl ChainStore { InstantaneousRewardSource::Treasury } }, - address: map_stake_address(&cred, network_id.clone()), + address: acropolis_codec::map_stake_address( + &cred, + network_id.clone(), + ), amount: amount as u64, }); } @@ -1079,7 +1077,7 @@ impl ChainStore { { certs.push(TransactionPoolUpdateCertificate { cert_index: cert_index as u64, - pool_reg: to_pool_reg( + pool_reg: acropolis_codec::to_pool_reg( operator, vrf_keyhash, pledge, @@ -1112,7 +1110,7 @@ impl ChainStore { { certs.push(TransactionPoolUpdateCertificate { cert_index: cert_index as u64, - pool_reg: to_pool_reg( + pool_reg: acropolis_codec::to_pool_reg( operator, vrf_keyhash, pledge, @@ -1151,7 +1149,7 @@ impl ChainStore { { certs.push(TransactionPoolRetirementCertificate { cert_index: cert_index as u64, - pool_id: to_pool_id(operator), + pool_id: acropolis_codec::to_pool_id(operator), retirement_epoch: *epoch, }); } @@ -1162,7 +1160,7 @@ impl ChainStore { { certs.push(TransactionPoolRetirementCertificate { cert_index: cert_index as u64, - pool_id: to_pool_id(operator), + pool_id: acropolis_codec::to_pool_id(operator), retirement_epoch: *epoch, }); } @@ -1184,7 +1182,7 @@ impl ChainStore { for (label, datum) in &metadata.clone().to_vec() { items.push(TransactionMetadataItem { label: label.to_string(), - json_metadata: map_metadata(datum), + json_metadata: acropolis_codec::map_metadata(datum), }); } } diff --git a/modules/tx_unpacker/src/tx_unpacker.rs b/modules/tx_unpacker/src/tx_unpacker.rs index 74750507..943601dc 100644 --- a/modules/tx_unpacker/src/tx_unpacker.rs +++ b/modules/tx_unpacker/src/tx_unpacker.rs @@ -1,7 +1,5 @@ //! Acropolis transaction unpacker module for Caryatid //! Unpacks transaction bodies into UTXO events - -use acropolis_codec::*; use acropolis_common::{ messages::{ AssetDeltasMessage, BlockTxsMessage, CardanoMessage, GovernanceProceduresMessage, Message, @@ -227,14 +225,14 @@ impl TxUnpacker { ) { Ok(utxo_id) => { match output.address() { - Ok(pallas_address) => match map_parameters::map_address(&pallas_address) { + Ok(pallas_address) => match acropolis_codec::map_address(&pallas_address) { Ok(address) => { tx_utxo_deltas.outputs.push(TxOutput { utxo_identifier: utxo_id, address, - value: map_parameters::map_value(&output.value()), - datum: map_parameters::map_datum(&output.datum()), - reference_script: map_parameters::map_reference_script(&output.script_ref()) + value: acropolis_codec::map_value(&output.value()), + datum: acropolis_codec::map_datum(&output.datum()), + reference_script: acropolis_codec::map_reference_script(&output.script_ref()) }); // catch all output lovelaces @@ -258,7 +256,7 @@ impl TxUnpacker { // Mint deltas for policy_group in tx.mints().iter() { - if let Some((policy_id, deltas)) = map_parameters::map_mint_burn(policy_group) { + if let Some((policy_id, deltas)) = acropolis_codec::map_mint_burn(policy_group) { tx_deltas.push((policy_id, deltas)); } } @@ -282,7 +280,7 @@ impl TxUnpacker { if publish_certificates_topic.is_some() { for ( cert_index, cert) in certs.iter().enumerate() { - match map_parameters::map_certificate(cert, tx_identifier, cert_index, network_id.clone()) { + match acropolis_codec::map_certificate(cert, tx_identifier, cert_index, network_id.clone()) { Ok(tx_cert) => { certificates.push(tx_cert); }, @@ -319,7 +317,7 @@ impl TxUnpacker { &mut alonzo_babbage_update_proposals, &alonzo_update.proposed_protocol_parameter_updates, alonzo_update.epoch, - map_parameters::map_alonzo_protocol_param_update + acropolis_codec::map_alonzo_protocol_param_update ); } } @@ -333,7 +331,7 @@ impl TxUnpacker { &mut alonzo_babbage_update_proposals, &babbage_update.proposed_protocol_parameter_updates, babbage_update.epoch, - map_parameters::map_babbage_protocol_param_update + acropolis_codec::map_babbage_protocol_param_update ); } } @@ -358,7 +356,7 @@ impl TxUnpacker { let mut proc_id = GovActionId { transaction_id: tx_hash, action_index: 0 }; for (action_index, pallas_governance_proposals) in pp.iter().enumerate() { match proc_id.set_action_index(action_index) - .and_then (|proc_id| map_parameters::map_governance_proposals_procedures(proc_id, pallas_governance_proposals)) + .and_then (|proc_id| acropolis_codec::map_governance_proposals_procedures(proc_id, pallas_governance_proposals)) { Ok(g) => proposal_procedures.push(g), Err(e) => error!("Cannot decode governance proposal procedure {} idx {} in slot {}: {e}", proc_id, action_index, block.slot) @@ -368,7 +366,7 @@ impl TxUnpacker { if let Some(pallas_vp) = votes { // Nonempty set -- governance_message.voting_procedures will not be empty - match map_parameters::map_all_governance_voting_procedures(pallas_vp) { + match acropolis_codec::map_all_governance_voting_procedures(pallas_vp) { Ok(vp) => voting_procedures.push((tx_hash, vp)), Err(e) => error!("Cannot decode governance voting procedures in slot {}: {e}", block.slot) } diff --git a/modules/tx_unpacker/src/validations/shelley/utxo.rs b/modules/tx_unpacker/src/validations/shelley/utxo.rs index 2dc1fa56..757c292f 100644 --- a/modules/tx_unpacker/src/validations/shelley/utxo.rs +++ b/modules/tx_unpacker/src/validations/shelley/utxo.rs @@ -1,7 +1,6 @@ //! Shelley era transaction validation //! Reference: https://github.com/IntersectMBO/cardano-ledger/blob/24ef1741c5e0109e4d73685a24d8e753e225656d/eras/shelley/impl/src/Cardano/Ledger/Shelley/Rules/Utxo.hs#L343 -use acropolis_codec; use acropolis_common::{ protocol_params::ShelleyParams, validation::UTxOValidationError, Address, Era, Lovelace, NetworkId, TxIdentifier, TxOutRef, @@ -168,13 +167,12 @@ pub fn validate_wrong_network( }) })?; - let address = - acropolis_codec::map_parameters::map_address(&pallas_address).map_err(|e| { - Box::new(UTxOValidationError::MalformedUTxO { - era: Era::Shelley, - reason: format!("Invalid address at output {index}: {}", e), - }) - })?; + let address = acropolis_codec::map_address(&pallas_address).map_err(|e| { + Box::new(UTxOValidationError::MalformedUTxO { + era: Era::Shelley, + reason: format!("Invalid address at output {index}: {}", e), + }) + })?; let is_network_correct = match &address { // NOTE: @@ -218,13 +216,12 @@ pub fn validate_wrong_network_withdrawal( }) })?; - let stake_address = acropolis_codec::map_parameters::map_address(&pallas_reward_adddess) - .map_err(|e| { - Box::new(UTxOValidationError::MalformedUTxO { - era: Era::Shelley, - reason: format!("Invalid reward address at withdrawal {index}: {}", e), - }) - })?; + let stake_address = acropolis_codec::map_address(&pallas_reward_adddess).map_err(|e| { + Box::new(UTxOValidationError::MalformedUTxO { + era: Era::Shelley, + reason: format!("Invalid reward address at withdrawal {index}: {}", e), + }) + })?; let stake_address = match stake_address { Address::Stake(stake_address) => stake_address,