diff --git a/pallas-codec/src/utils.rs b/pallas-codec/src/utils.rs index cb2eec8c1..f2a944d58 100644 --- a/pallas-codec/src/utils.rs +++ b/pallas-codec/src/utils.rs @@ -1340,11 +1340,11 @@ where fn decode(d: &mut minicbor::Decoder<'b>, ctx: &mut C) -> Result { match d.datatype()? { minicbor::data::Type::Null => { - d.null()?; + d.skip()?; Ok(Self::Null) } minicbor::data::Type::Undefined => { - d.undefined()?; + d.skip()?; Ok(Self::Undefined) } _ => { diff --git a/pallas-hardano/src/display/haskell_display.rs b/pallas-hardano/src/display/haskell_display.rs index f391bb63c..296fa5d2a 100644 --- a/pallas-hardano/src/display/haskell_display.rs +++ b/pallas-hardano/src/display/haskell_display.rs @@ -19,11 +19,11 @@ use pallas_crypto::hash::{Hash, Hasher}; use pallas_network::miniprotocols::{ handshake::NetworkMagic, localstate::queries_v16::{ - primitives::Bytes, Anchor, AssetName, BoundedBytes, Constitution, CostModel, CostModels, - DRep, DRepVotingThresholds, DatumHash, DatumOption, ExUnitPrices, GovAction, GovActionId, - PParamsUpdate, PlutusData, PolicyId, PoolMetadata, PoolVotingThresholds, ProposalProcedure, - ProtocolVersion, RationalNumber, Relay, RewardAccount, ScriptHash, TransactionInput, - TransactionOutput, Value, Vote, + primitives::Bytes, Anchor, AssetName, BigInt, BoundedBytes, Constitution, CostModel, + CostModels, DRep, DRepVotingThresholds, DatumHash, DatumOption, ExUnitPrices, + FieldedRewardAccount, GovAction, GovActionId, PParamsUpdate, PlutusData, PolicyId, + PoolMetadata, PoolVotingThresholds, ProposalProcedure, ProtocolVersion, RationalNumber, + Relay, ScriptHash, TransactionInput, TransactionOutput, Value, Vote, }, localtxsubmission::{ primitives::{ @@ -38,13 +38,16 @@ use pallas_network::miniprotocols::localtxsubmission::{ Array, BabbageContextError, CollectError, ConwayCertPredFailure, ConwayContextError, ConwayDelegPredFailure, ConwayGovCertPredFailure, ConwayGovPredFailure, ConwayLedgerFailure, ConwayTxCert, ConwayUtxoWPredFailure, DeltaCoin, DisplayAddress, DisplayCoin, DisplayOSet, - DisplayPolicyId, DisplayRewardAccount, DisplayScriptHash, DisplayVotingProcedures, EpochNo, - FailureDescription, KeyHash, Mismatch, OHashMap, PlutusPurpose, SMaybe, SafeHash, + DisplayPolicyId, DisplayScriptHash, DisplayVotingProcedures, EpochNo, FailureDescription, + KeyHash, Mismatch, MismatchArr, OHashMap, PlutusPurpose, SMaybe, SafeHash, ShelleyPoolPredFailure, SlotNo, TagMismatchDescription, TxOutSource, Utxo, UtxoFailure, UtxosFailure, VKey, ValidityInterval, }; -use super::haskells_show_string::haskell_show_string; +use super::{ + haskell_error::{DecoderError, DeserialiseFailure}, + haskells_show_string::haskell_show_string, +}; /// Trait used to generated Haskell-like string representation of a type. pub trait HaskellDisplay { @@ -72,21 +75,18 @@ impl HaskellDisplay for ConwayLedgerFailure { format!("ConwayWdrlNotDelegatedToDRep {}", v.to_haskell_str_p()) } TreasuryValueMismatch(c1, c2) => { - format!( - "ConwayTreasuryValueMismatch {} {}", - c1.to_haskell_str_p(), - c2.to_haskell_str_p() - ) + format!("ConwayTreasuryValueMismatch ({})", as_mismatch(c1, c2),) } TxRefScriptsSizeTooBig(s1, s2) => { - format!( - "ConwayTxRefScriptsSizeTooBig {} {}", - s1.to_haskell_str_p(), - s2.to_haskell_str_p() - ) + format!("ConwayTxRefScriptsSizeTooBig ({})", as_mismatch(s2, s1)) } MempoolFailure(e) => format!("ConwayMempoolFailure {}", e.to_haskell_str()), - U8(v) => format!("U8 {v}"), + WithdrawalsMissingAccounts(w) => { + format!("ConwayWithdrawalsMissingAccounts ({})", w.to_haskell_str()) + } + IncompleteWithdrawals(w) => { + format!("ConwayIncompleteWithdrawals ({})", w.to_haskell_str()) + } } } } @@ -102,22 +102,18 @@ impl HaskellDisplay for ConwayGovCertPredFailure { DRepNotRegistered(cred) => { format!("ConwayDRepNotRegistered {}", cred.to_haskell_str_p()) } - DRepIncorrectDeposit(expected, actual) => format!( - "ConwayDRepIncorrectDeposit {} {}", - expected.to_haskell_str_p(), - actual.to_haskell_str_p() - ), + DRepIncorrectDeposit(c1, c2) => { + format!("ConwayDRepIncorrectDeposit ({})", as_mismatch(c2, c1)) + } CommitteeHasPreviouslyResigned(cred) => { format!( "ConwayCommitteeHasPreviouslyResigned {}", cred.to_haskell_str_p() ) } - DRepIncorrectRefund(expected, actual) => format!( - "ConwayDRepIncorrectRefund {} {}", - expected.to_haskell_str_p(), - actual.to_haskell_str_p() - ), + DRepIncorrectRefund(v1, v2) => { + format!("ConwayDRepIncorrectRefund ({})", as_mismatch(v2, v1)) + } CommitteeIsUnknown(cred) => { format!("ConwayCommitteeIsUnknown {}", cred.to_haskell_str_p()) } @@ -142,7 +138,7 @@ impl HaskellDisplay for ConwayCertsPredFailure { match self { WithdrawalsNotInRewardsCERTS(m) => { - format!("WithdrawalsNotInRewardsCERTS {}", m.to_haskell_str_p()) + format!("WithdrawalsNotInRewardsCERTS ({})", m.as_withdrawals()) } CertFailure(e) => format!("CertFailure {}", e.to_haskell_str_p()), } @@ -180,6 +176,13 @@ impl HaskellDisplay for ShelleyPoolPredFailure { size.to_haskell_str_p() ) } + VRFKeyHashAlreadyRegistered(kh, vrf_hash) => { + format!( + "VRFKeyHashAlreadyRegistered {} {}", + kh.to_haskell_str_p(), + vrf_hash.to_haskell_str_p() + ) + } } } } @@ -201,13 +204,15 @@ impl HaskellDisplay for ConwayUtxoWPredFailure { format!("(ScriptWitnessNotValidatingUTXOW {})", e.to_haskell_str_p()) } MissingTxBodyMetadataHash(b) => { - format!("(MissingTxBodyMetadataHash ({}))", b.as_aux_data_hash()) + format!("(MissingTxBodyMetadataHash ({}))", b.as_tx_aux_data_hash()) } - MissingTxMetadata(e) => format!("(MissingTxMetadata ({}))", e.as_aux_data_hash()), + MissingTxMetadata(e) => format!("(MissingTxMetadata ({}))", e.as_tx_aux_data_hash()), ConflictingMetadataHash(e1, e2) => format!( - "(ConflictingMetadataHash ({}) ({}))", - e1.as_aux_data_hash(), - e2.as_aux_data_hash() + "(ConflictingMetadataHash ({}))", + as_mismatch( + &e2.as_tx_aux_data_hash().as_is(), + &e1.as_tx_aux_data_hash().as_is() + ) ), InvalidMetadata() => "InvalidMetadata".to_string(), ExtraneousScriptWitnessesUTXOW(vec) => { @@ -227,11 +232,9 @@ impl HaskellDisplay for ConwayUtxoWPredFailure { e1.to_haskell_str_p(), e2.to_haskell_str_p() ), - PPViewHashesDontMatch(h1, h2) => format!( - "(PPViewHashesDontMatch {} {})", - h1.to_haskell_str_p(), - h2.to_haskell_str_p() - ), + PPViewHashesDontMatch(h1, h2) => { + format!("(PPViewHashesDontMatch ({}))", as_mismatch(h2, h1)) + } UnspendableUTxONoDatumHash(e) => { format!("(UnspendableUTxONoDatumHash {})", e.to_haskell_str_p()) } @@ -242,6 +245,14 @@ impl HaskellDisplay for ConwayUtxoWPredFailure { MalformedReferenceScripts(set) => { format!("(MalformedReferenceScripts {})", set.to_haskell_str_p()) } + ScriptIntegrityHashMismatch(m, extra) => { + format!( + "(ScriptIntegrityHashMismatch (Mismatch {{mismatchSupplied = {}, mismatchExpected = {}}}) {})", + m.0.to_haskell_str(), + m.1.to_haskell_str(), + extra.to_haskell_str_p() + ) + } } } } @@ -271,11 +282,7 @@ impl HaskellDisplay for ConwayGovPredFailure { ) } ProposalDepositIncorrect(c1, c2) => { - format!( - "ProposalDepositIncorrect {} {}", - c1.to_haskell_str_p(), - c2.to_haskell_str_p() - ) + format!("ProposalDepositIncorrect ({})", as_mismatch(c2, c1)) } DisallowedVoters(v) => { format!("DisallowedVoters {}", v.to_haskell_str_p()) @@ -294,10 +301,12 @@ impl HaskellDisplay for ConwayGovPredFailure { } ProposalCantFollow(a, p1, p2) => { format!( - "ProposalCantFollow {} ({}) ({})", + "ProposalCantFollow {} ({})", a.to_haskell_str_p(), - p1.as_protocol_version(), - p2.as_protocol_version() + as_mismatch( + &p2.as_protocol_version().as_is(), + &p1.as_protocol_version().as_is() + ) ) } InvalidPolicyHash(maybe1, maybe2) => { @@ -328,6 +337,9 @@ impl HaskellDisplay for ConwayGovPredFailure { s.to_haskell_str_p() ) } + UnelectedCommitteeVoters(s) => { + format!("UnelectedCommitteeVoters {}", s.to_haskell_str_p()) + } } } } @@ -346,21 +358,12 @@ impl HaskellDisplay for UtxoFailure { interval.to_haskell_str(), slot.as_slot_no() ), - MaxTxSizeUTxO(actual, max) => format!( - "(MaxTxSizeUTxO {} {})", - actual.to_haskell_str_p(), - max.to_haskell_str_p() - ), + MaxTxSizeUTxO(s1, s2) => format!("(MaxTxSizeUTxO ({}))", as_mismatch(s2, s1)), InputSetEmptyUTxO => "InputSetEmptyUTxO".to_string(), - FeeTooSmallUTxO(required, provided) => format!( - "(FeeTooSmallUTxO {} {})", - required.to_haskell_str_p(), - provided.to_haskell_str_p() - ), + FeeTooSmallUTxO(c1, c2) => format!("(FeeTooSmallUTxO ({}))", as_mismatch(c1, c2)), ValueNotConservedUTxO(required, provided) => format!( - "(ValueNotConservedUTxO {} {})", - required.to_haskell_str_p(), - provided.to_haskell_str_p() + "(ValueNotConservedUTxO {})", + Mismatch(required.to_owned(), provided.to_owned()).to_haskell_str_p() ), OutputTooSmallUTxO(outputs) => { format!("(OutputTooSmallUTxO {})", outputs.to_haskell_str_p()) @@ -374,11 +377,7 @@ impl HaskellDisplay for UtxoFailure { required.to_haskell_str_p() ), ScriptsNotPaidUTxO(utxo) => format!("(ScriptsNotPaidUTxO {})", utxo.to_haskell_str_p()), - ExUnitsTooBigUTxO(provided, max) => format!( - "(ExUnitsTooBigUTxO {} {})", - provided.to_haskell_str_p(), - max.to_haskell_str_p() - ), + ExUnitsTooBigUTxO(u1, u2) => format!("(ExUnitsTooBigUTxO ({}))", as_mismatch(u1, u2)), WrongNetwork(network, addrs) => format!( "(WrongNetwork {} {})", network.to_haskell_str(), @@ -394,14 +393,14 @@ impl HaskellDisplay for UtxoFailure { format!("(CollateralContainsNonADA {})", value.to_haskell_str_p()) } NoCollateralInputs => "NoCollateralInputs".to_string(), + // as_mismatch order: (supplied, expected). The transaction supplies + // `actual` collateral inputs, the ledger expects at most `max`. TooManyCollateralInputs(actual, max) => { - format!("(TooManyCollateralInputs {actual} {max})") + format!("(TooManyCollateralInputs ({}))", as_mismatch(actual, max)) + } + WrongNetworkInTxBody(n1, n2) => { + format!("(WrongNetworkInTxBody ({}))", as_mismatch(n1, n2)) } - WrongNetworkInTxBody(expected, actual) => format!( - "(WrongNetworkInTxBody {} {})", - expected.to_haskell_str(), - actual.to_haskell_str() - ), IncorrectTotalCollateralField(actual, provided) => format!( "(IncorrectTotalCollateralField {} {})", actual.to_haskell_str_p(), @@ -486,6 +485,10 @@ impl HaskellDisplay for ConwayContextError { "TreasuryDonationFieldNotSupported ({})", display_coin.to_haskell_str() ), + ReferenceInputsNotDisjointFromInputs(inputs) => format!( + "ReferenceInputsNotDisjointFromInputs ({})", + inputs.to_haskell_str() + ), } .to_string() } @@ -522,6 +525,59 @@ impl HaskellDisplay for BabbageContextError { } } +impl HaskellDisplay for DecoderError { + fn to_haskell_str(&self) -> String { + match self { + DecoderError::DeserialiseFailure(s, failure) => format!( + "DecoderErrorDeserialiseFailure {} {}", + s.to_haskell_str(), + failure.to_haskell_str_p() + ), + DecoderError::CanonicityViolation(s) => { + format!("DecoderErrorCanonicityViolation {}", s.to_haskell_str()) + } + DecoderError::Custom(s1, s2) => { + format!( + "DecoderErrorCustom {} {}", + s1.to_haskell_str(), + s2.to_haskell_str() + ) + } + DecoderError::EmptyList(s) => { + format!("DecoderErrorEmptyList {}", s.to_haskell_str()) + } + DecoderError::Leftover(s, bytes) => { + format!( + "DecoderErrorLeftover {} \"{}\"", + s.to_haskell_str(), + hex::encode(bytes) + ) + } + DecoderError::SizeMismatch(s, expected, actual) => { + format!( + "DecoderErrorSizeMismatch {} {} {}", + s.to_haskell_str(), + expected, + actual + ) + } + DecoderError::UnknownTag(s, tag) => { + format!("DecoderErrorUnknownTag {} {}", s.to_haskell_str(), tag) + } + DecoderError::Void => "DecoderErrorVoid".to_string(), + } + } +} +impl HaskellDisplay for DeserialiseFailure { + fn to_haskell_str(&self) -> String { + format!( + "DeserialiseFailure {} ({})", + self.0.to_haskell_str(), + self.1.to_haskell_str() + ) + } +} + impl HaskellDisplay for TxOutSource { fn to_haskell_str(&self) -> String { use TxOutSource::*; @@ -539,6 +595,7 @@ impl HaskellDisplay for Language { PlutusV1 => "PlutusV1".to_string(), PlutusV2 => "PlutusV2".to_string(), PlutusV3 => "PlutusV3".to_string(), + PlutusV4 => "PlutusV4".to_string(), } } } @@ -599,6 +656,12 @@ impl HaskellDisplay for ConwayDelegPredFailure { "DelegateeStakePoolNotRegisteredDELEG ({})", hash.to_haskell_str() ), + DepositIncorrectDELEG(m) => { + format!("DepositIncorrectDELEG ({})", as_mismatch(&m.1, &m.0)) + } + RefundIncorrectDELEG(m) => { + format!("RefundIncorrectDELEG ({})", as_mismatch(&m.1, &m.0)) + } } } } @@ -618,34 +681,36 @@ where T: HaskellDisplay, { fn to_haskell_str(&self) -> String { - format!( - "Mismatch {{mismatchSupplied = {}, mismatchExpected = {}}}", - self.0.to_haskell_str(), - self.1.to_haskell_str() - ) + as_mismatch(&self.1, &self.0) } } -impl HaskellDisplay for DisplayRewardAccount { +impl HaskellDisplay for MismatchArr +where + T: HaskellDisplay, +{ fn to_haskell_str(&self) -> String { - let network = if self.0[0] & 0b00000001 != 0 { - Network::Mainnet - } else { - Network::Testnet - }; + as_mismatch(&self.1, &self.0) + } +} - let mut hash = [0; 28]; - hash.copy_from_slice(&self.0[1..29]); - let credential = if &self.0[0] & 0b00010000 != 0 { - StakeCredential::ScriptHash(hash.into()) - } else { - StakeCredential::AddrKeyhash(hash.into()) - }; +pub fn as_mismatch(expected: &T, supplied: &T) -> String +where + T: HaskellDisplay, +{ + format!( + "Mismatch {{mismatchSupplied = {}, mismatchExpected = {}}}", + supplied.to_haskell_str(), + expected.to_haskell_str() + ) +} +impl HaskellDisplay for FieldedRewardAccount { + fn to_haskell_str(&self) -> String { format!( "RewardAccount {{raNetwork = {}, raCredential = {}}}", - network.to_haskell_str(), - credential.to_haskell_str() + self.network.to_haskell_str(), + self.stake_credential.to_haskell_str() ) } } @@ -762,6 +827,7 @@ fn is_primitive() -> bool { || std::any::TypeId::of::() == std::any::TypeId::of::() || std::any::TypeId::of::() == std::any::TypeId::of::() || std::any::TypeId::of::() == std::any::TypeId::of::() + || std::any::TypeId::of::() == std::any::TypeId::of::() } impl HaskellDisplay for DRep { fn to_haskell_str(&self) -> String { @@ -807,8 +873,8 @@ impl HaskellDisplay for GovAction { ) } TreasuryWithdrawals(a, b) => { - let data: KeyValuePairs = - a.iter().map(|(k, v)| (k.into(), v.into())).collect(); + let data: KeyValuePairs = + a.iter().map(|(k, v)| (k.clone(), v.into())).collect(); format!( "TreasuryWithdrawals {} {}", @@ -869,7 +935,7 @@ impl HaskellDisplay for PParamsUpdate { self.max_transaction_size.to_haskell_str(), self.max_block_header_size.to_haskell_str(), self.key_deposit.as_display_coin(), - self.pool_deposit.as_display_coin(), + self.pool_deposit.as_compact_coin(), self.maximum_epoch.as_epoch_interval(), self.desired_number_of_stake_pools.to_haskell_str(), self.pool_pledge_influence.to_haskell_str_p(), @@ -891,7 +957,7 @@ impl HaskellDisplay for PParamsUpdate { self.committee_term_limit.as_epoch_interval(), self.governance_action_validity_period.as_epoch_interval(), self.governance_action_deposit.as_display_coin(), - self.drep_deposit.as_display_coin(), + self.drep_deposit.as_compact_coin(), self.drep_inactivity_period.as_epoch_interval(), self.minfee_refscript_cost_per_byte.to_haskell_str_p() ) @@ -977,7 +1043,7 @@ impl HaskellDisplay for ProposalProcedure { format!( "ProposalProcedure {{pProcDeposit = {}, pProcReturnAddr = {}, pProcGovAction = {}, pProcAnchor = {}}}", self.deposit.as_display_coin(), - self.return_addr.as_reward_account(), + self.return_addr.to_haskell_str(), self.gov_action.to_haskell_str(), self.anchor.to_haskell_str() ) @@ -1065,6 +1131,20 @@ impl HaskellDisplay for i64 { } } +impl HaskellDisplay for i128 { + fn to_haskell_str(&self) -> String { + self.to_string() + } + + fn to_haskell_str_p(&self) -> String { + if *self >= 0 { + self.to_string() + } else { + format!("({})", self) + } + } +} + impl HaskellDisplay for u8 { fn to_haskell_str(&self) -> String { format!("{self}") @@ -1214,6 +1294,10 @@ trait AsVKey { fn as_vkey(&self) -> String; } +trait AsVrfKeyHash { + fn as_vrf_key_hash(&self) -> String; +} + trait AsScriptHashObject { fn as_script_hash_obj(&self) -> String; } @@ -1314,6 +1398,13 @@ where } } +impl AsVrfKeyHash for [u8] { + fn as_vrf_key_hash(&self) -> String { + let hex = hex::encode(self); + format!("VRFVerKeyHash {{unVRFVerKeyHash = \"{}\"}}", hex) + } +} + impl AsKeyHash for [u8] { fn as_key_hash(&self) -> String { let hex = hex::encode(self); @@ -1398,16 +1489,6 @@ impl AsProtocolVersion for ProtocolVersion { } } -trait AsRewardAccount { - fn as_reward_account(&self) -> String; -} - -impl AsRewardAccount for RewardAccount { - fn as_reward_account(&self) -> String { - DisplayRewardAccount(self.clone()).to_haskell_str() - } -} - impl AsFromList for Vec<&Option> { fn as_from_list(&self) -> String { let result = self @@ -1488,6 +1569,32 @@ impl AsDisplayCoin for Coin { format!("Coin {}", self.to_haskell_str()) } } +trait AsWithdrawals { + fn as_withdrawals(&self) -> String; +} + +impl AsWithdrawals for OHashMap { + fn as_withdrawals(&self) -> String { + format!("Withdrawals {{unWithdrawals = {}}}", self.to_haskell_str()) + } +} + +trait AsCompactCoin { + fn as_compact_coin(&self) -> String; +} + +impl AsCompactCoin for Option { + fn as_compact_coin(&self) -> String { + match self { + Option::Some(v) => format!( + "SJust (CompactCoin {{unCompactCoin = {}}})", + v.to_haskell_str() + ), + _ => "SNothing".to_string(), + } + } +} + trait AsEpochInterval { fn as_epoch_interval(&self) -> String; } @@ -1533,6 +1640,42 @@ impl HaskellDisplay for u64 { } } +impl HaskellDisplay for BigInt { + fn to_haskell_str(&self) -> String { + use BigInt::*; + + match self { + Int(i) => { + let value: i128 = i.0.into(); + value.to_haskell_str() + } + BigNInt(bb) => { + format!("BigNInt {}", bb.to_haskell_str()) + } + BigUInt(bb) => { + format!("BigUInt {}", bb.to_haskell_str()) + } + } + } + + fn to_haskell_str_p(&self) -> String { + use BigInt::*; + + match self { + Int(i) => { + let value: i128 = i.0.into(); + value.to_haskell_str_p() + } + BigNInt(bb) => { + format!("BigNInt {}", bb.to_haskell_str_p()) + } + BigUInt(bb) => { + format!("BigUInt {}", bb.to_haskell_str_p()) + } + } + } +} + impl HaskellDisplay for String { fn to_haskell_str(&self) -> String { self.as_text() @@ -1740,7 +1883,7 @@ impl HaskellDisplay for PseudoScript { fn to_haskell_str(&self) -> String { use PseudoScript::*; match self { - NativeScript(ns) => format!("TimelockScript {}", ns.to_haskell_str()), + NativeScript(ns) => format!("NativeScript {}", ns.to_haskell_str()), PlutusV1Script(ps) => format!( "PlutusScript PlutusV1 {}", Hasher::<224>::hash_tagged(ps.0.as_slice(), 1).as_script_hash() @@ -1763,17 +1906,17 @@ impl HaskellDisplay for NativeScript { let str = match self { ScriptPubkey(key_hash) => { - format!("Signature ({})", key_hash.as_key_hash()) + format!("TimelockSignature ({})", key_hash.as_key_hash()) } - ScriptAll(vec) => format!("AllOf ({})", vec.as_strict_seq()), - ScriptAny(vec) => format!("AnyOf ({})", vec.as_strict_seq()), - ScriptNOfK(m, vec) => format!("MOfN {} ({})", m, vec.as_strict_seq()), - InvalidBefore(slot_no) => format!("TimeStart ({})", slot_no.as_slot_no()), - InvalidHereafter(slot_no) => format!("TimeExpire ({})", slot_no.as_slot_no()), + ScriptAll(vec) => format!("TimelockAllOf ({})", vec.as_strict_seq()), + ScriptAny(vec) => format!("TimelockAnyOf ({})", vec.as_strict_seq()), + ScriptNOfK(m, vec) => format!("TimelockMOf {} ({})", m, vec.as_strict_seq()), + InvalidBefore(slot_no) => format!("TimelockTimeStart ({})", slot_no.as_slot_no()), + InvalidHereafter(slot_no) => format!("TimelockTimeExpire ({})", slot_no.as_slot_no()), }; format!( - "TimelockConstr {} ({})", + "MkTimelock {} ({})", str, Hasher::<256>::hash_cbor(self).as_blake2b256() ) @@ -2020,7 +2163,7 @@ impl HaskellDisplay for Pointer { fn to_haskell_str(&self) -> String { format!( "Ptr ({}) ({}) ({})", - self.slot().as_slot_no(), + self.slot().as_slot_no32(), self.tx_idx().as_tx_ix(), self.cert_idx().as_cert_ix() ) @@ -2092,21 +2235,12 @@ impl AsDatumHash for DatumHash { impl HaskellDisplay for PlutusData { fn to_haskell_str(&self) -> String { - use pallas_network::miniprotocols::localstate::queries_v16::BigInt as Big; use PlutusData::*; match self { Constr(constr) => constr.fields.to_haskell_str(), Map(key_value_pairs) => key_value_pairs.to_haskell_str().to_string(), - BigInt(big_int) => match big_int { - Big::Int(i) => format!("BigInt Int {}", i.to_haskell_str()), - Big::BigNInt(bb) => { - format!("BigNInt {}", bb.to_haskell_str()) - } - Big::BigUInt(bb) => { - format!("BigUInt {}", bb.to_haskell_str()) - } - }, + BigInt(big_int) => big_int.to_haskell_str(), BoundedBytes(bb) => bb.to_haskell_str(), Array(arr) => format!("Array {}", arr.to_haskell_str()), } @@ -2140,7 +2274,7 @@ where impl HaskellDisplay for Int { fn to_haskell_str(&self) -> String { - format!("Int {}", self.0) + format!("{}", self.0) } } @@ -2154,12 +2288,22 @@ trait AsSlotNo { fn as_slot_no(&self) -> String; } +trait AsSlotNo32 { + fn as_slot_no32(&self) -> String; +} + impl AsSlotNo for u64 { fn as_slot_no(&self) -> String { format!("SlotNo {self}") } } +impl AsSlotNo32 for u64 { + fn as_slot_no32(&self) -> String { + format!("SlotNo32 {}", self) + } +} + impl AsSlotNo for SMaybe { fn as_slot_no(&self) -> String { match self { @@ -2189,7 +2333,7 @@ impl HaskellDisplay TransactionInput, PolicyId, ConwayTxCert, - DisplayRewardAccount, + FieldedRewardAccount, Voter, ProposalProcedure, > @@ -2305,11 +2449,11 @@ impl HaskellDisplay for Certificate { } => format!( "RegPool (PoolParams {{ppId = {}, ppVrf = {}, ppPledge = {}, ppCost = {}, ppMargin = {}, ppRewardAccount = {}, ppOwners = {}, ppRelays = {}, ppMetadata = {}}})", operator.as_key_hash(), - vrf_keyhash.to_string().to_haskell_str(), + vrf_keyhash.as_vrf_key_hash(), pledge.as_display_coin(), cost.as_display_coin(), margin.to_haskell_str(), - reward_account.as_reward_account(), + reward_account.to_haskell_str(), pool_owners.as_key_hash(), relays.as_strict_seq(), pool_metadata.to_haskell_str() @@ -2423,11 +2567,7 @@ impl HaskellDisplay for DisplayOSet { let mut sorted_vec = self.0.deref().clone(); - sorted_vec.sort_by(|a, b| { - a.deposit - .cmp(&b.deposit) - .then_with(|| b.return_addr.cmp(&a.return_addr)) - }); + sorted_vec.sort(); format!( "OSet {{osSSeq = {}, osSet = {}}}", @@ -2464,7 +2604,7 @@ where impl HaskellDisplay for DeltaCoin { fn to_haskell_str(&self) -> String { - format!("DeltaCoin {}", self.0.to_haskell_str()) + format!("DeltaCoin {}", self.0.to_haskell_str_p()) } } @@ -2506,13 +2646,14 @@ impl AsAddress for Bytes { .to_haskell_str() } } -trait AsAuxDataHash { - fn as_aux_data_hash(&self) -> String; + +trait AsTxAuxDataHash { + fn as_tx_aux_data_hash(&self) -> String; } -impl AsAuxDataHash for Bytes { - fn as_aux_data_hash(&self) -> String { - format!("AuxiliaryDataHash {{unsafeAuxiliaryDataHash = SafeHash \"{self}\"}}") +impl AsTxAuxDataHash for Bytes { + fn as_tx_aux_data_hash(&self) -> String { + format!("TxAuxDataHash {{unTxAuxDataHash = SafeHash \"{self}\"}}") } } @@ -2644,6 +2785,7 @@ impl HaskellDisplay for CostModels { display_cost_model(1, &self.plutus_v1), display_cost_model(2, &self.plutus_v2), display_cost_model(3, &self.plutus_v3), + display_cost_model(4, &self.plutus_v4), ] .into_iter() .flatten() diff --git a/pallas-hardano/src/display/haskell_error.rs b/pallas-hardano/src/display/haskell_error.rs index b54e0d898..cca4af647 100644 --- a/pallas-hardano/src/display/haskell_error.rs +++ b/pallas-hardano/src/display/haskell_error.rs @@ -7,18 +7,29 @@ use super::haskell_display::HaskellDisplay; /// Mimicks the json data structure of the error response from the cardano-submit-api pub fn wrap_error_response(error: TxValidationError) -> TxSubmitFail { - TxSubmitFail::TxSubmitFail(TxCmdError::TxCmdTxSubmitValidationError( + TxSubmitFail::TxSubmitFail(TxCmdError::SubmitValidationError( TxValidationErrorInCardanoMode::TxValidationErrorInCardanoMode(error), )) } -/// Generates Haskell identical string for the error response -pub fn as_node_submit_error(error: TxValidationError) -> String { - serde_json::to_string(&wrap_error_response(error)).unwrap() +/// Generates Haskell 'identical' string for the error response +pub fn as_node_submit_error(error: TxValidationError) -> Result { + serde_json::to_string(&wrap_error_response(error)) } -pub fn serialize_error(error: TxValidationError) -> serde_json::Value { - serde_json::to_value(wrap_error_response(error)).unwrap() +/// Generates Haskell 'similar' string for the error response in case of decode failure +/// Only difference will be the provided decode failure message, Rust vs Haskell +pub fn as_cbor_decode_failure(message: String, position: u64) -> Result { + let inner_errors = vec![DecoderError::DeserialiseFailure( + "Shelley Tx".to_string(), + DeserialiseFailure(position, message), + )]; + let error = TxSubmitFail::TxSubmitFail(TxCmdError::ReadError(inner_errors)); + serde_json::to_string(&error) +} + +pub fn serialize_error(error: TxValidationError) -> Result { + serde_json::to_value(wrap_error_response(error)) } /// https://github.com/IntersectMBO/cardano-node/blob/9dbf0b141e67ec2dfd677c77c63b1673cf9c5f3e/cardano-submit-api/src/Cardano/TxSubmit/Types.hs#L54 @@ -36,9 +47,12 @@ pub enum TxSubmitFail { #[derive(Debug, Serialize)] #[serde(tag = "tag", content = "contents")] pub enum TxCmdError { + #[serde(rename = "TxCmdSocketEnvError")] SocketEnvError(String), - TxReadError(Vec), - TxCmdTxSubmitValidationError(TxValidationErrorInCardanoMode), + #[serde(rename = "TxCmdTxReadError")] + ReadError(Vec), + #[serde(rename = "TxCmdTxSubmitValidationError")] + SubmitValidationError(TxValidationErrorInCardanoMode), } /// https://github.com/IntersectMBO/cardano-api/blob/d7c62a04ebf18d194a6ea70e6765eb7691d57668/cardano-api/internal/Cardano/Api/InMode.hs#L259 @@ -57,9 +71,28 @@ pub struct EraMismatch { other: String, // Era of the block, header, transaction, or query. } -/// TODO: Implement DecoderError errors from the Haskell codebase. -/// Lots of errors, skipping for now. https://github.com/IntersectMBO/cardano-base/blob/391a2c5cfd30d2234097e000dbd8d9db21ef94d7/cardano-binary/src/Cardano/Binary/FromCBOR.hs#L90 -type DecoderError = String; +/// https://github.com/IntersectMBO/cardano-base/blob/391a2c5cfd30d2234097e000dbd8d9db21ef94d7/cardano-binary/src/Cardano/Binary/FromCBOR.hs#L90 +#[derive(Debug)] +pub enum DecoderError { + CanonicityViolation(String), + Custom(String, String), + DeserialiseFailure(String, DeserialiseFailure), + EmptyList(String), + Leftover(String, Vec), + SizeMismatch(String, u64, u64), + UnknownTag(String, u8), + Void, +} + +impl Serialize for DecoderError { + fn serialize(&self, serializer: S) -> Result { + serializer.serialize_str(&self.to_haskell_str()) + } +} + +/// https://hackage.haskell.org/package/serialise-0.2.6.1/docs/Codec-Serialise.html#t:DeserialiseFailure +#[derive(Debug)] +pub struct DeserialiseFailure(pub u64, pub String); // // Haskell JSON serializations @@ -83,7 +116,6 @@ enum TxValidationErrorJson { } /// This is copy of ApplyTxError from pallas-network/src/miniprotocols/localtxsubmission/primitives.rs for Haskell json serialization - #[derive(Debug, Clone, Eq, PartialEq, Serialize)] #[serde(remote = "ApplyTxError")] struct ApplyTxErrorJson( @@ -108,9 +140,10 @@ enum ShelleyBasedEraJson { Conway, } -fn use_haskell_display(fails: &[ConwayLedgerFailure], serializer: S) -> Result +fn use_haskell_display(fails: &[T], serializer: S) -> Result where S: Serializer, + T: HaskellDisplay, { let fails_str = fails.iter().map(|fail| fail.to_haskell_str()); serializer.collect_seq(fails_str) @@ -122,7 +155,15 @@ where fn test_submit_api_serialization() { let error = decode_error("81820681820764f0aab883"); - assert_eq!("{\"tag\":\"TxSubmitFail\",\"contents\":{\"tag\":\"TxCmdTxSubmitValidationError\",\"contents\":{\"tag\":\"TxValidationErrorInCardanoMode\",\"contents\":{\"kind\":\"ShelleyTxValidationError\",\"error\":[\"ConwayMempoolFailure \\\"\\\\175619\\\"\"],\"era\":\"ShelleyBasedEraConway\"}}}}", as_node_submit_error(error)); + assert_eq!("{\"tag\":\"TxSubmitFail\",\"contents\":{\"tag\":\"TxCmdTxSubmitValidationError\",\"contents\":{\"tag\":\"TxValidationErrorInCardanoMode\",\"contents\":{\"kind\":\"ShelleyTxValidationError\",\"error\":[\"ConwayMempoolFailure \\\"\\\\175619\\\"\"],\"era\":\"ShelleyBasedEraConway\"}}}}", + as_node_submit_error(error).unwrap()); +} + +#[test] +#[allow(non_snake_case)] +fn test_submit_api_decode_failure() { + assert_eq!( "{\"tag\":\"TxSubmitFail\",\"contents\":{\"tag\":\"TxCmdTxReadError\",\"contents\":[\"DecoderErrorDeserialiseFailure \\\"Shelley Tx\\\" (DeserialiseFailure 0 (\\\"expected list len or indef\\\"))\"]}}", + as_cbor_decode_failure("expected list len or indef".to_string(), 0).unwrap()); } #[cfg(test)] diff --git a/pallas-network/src/miniprotocols/localstate/queries_v16/codec.rs b/pallas-network/src/miniprotocols/localstate/queries_v16/codec.rs index 15760302b..aadf6f35b 100644 --- a/pallas-network/src/miniprotocols/localstate/queries_v16/codec.rs +++ b/pallas-network/src/miniprotocols/localstate/queries_v16/codec.rs @@ -775,6 +775,7 @@ impl<'b, C> minicbor::Decode<'b, C> for CostModels { let mut plutus_v1 = None; let mut plutus_v2 = None; let mut plutus_v3 = None; + let mut plutus_v4 = None; let mut unknown: Vec<(u64, CostModel)> = Vec::new(); for (k, v) in models.iter() { @@ -782,6 +783,7 @@ impl<'b, C> minicbor::Decode<'b, C> for CostModels { 0 => plutus_v1 = Some(v.clone()), 1 => plutus_v2 = Some(v.clone()), 2 => plutus_v3 = Some(v.clone()), + 3 => plutus_v4 = Some(v.clone()), _ => unknown.push((*k, v.clone())), } } @@ -790,11 +792,49 @@ impl<'b, C> minicbor::Decode<'b, C> for CostModels { plutus_v1, plutus_v2, plutus_v3, + plutus_v4, unknown: unknown.into(), }) } } +impl minicbor::Encode for FieldedRewardAccount { + fn encode( + &self, + e: &mut minicbor::Encoder, + _ctx: &mut C, + ) -> Result<(), minicbor::encode::Error> { + let (hash, is_script) = match &self.stake_credential { + StakeCredential::ScriptHash(h) => (h, true), + StakeCredential::AddrKeyhash(h) => (h, false), + }; + + // Network yields 0 (testnet) or 1 (mainnet), always fits in the lower nibble. + let mut prefix: u8 = 0b1110_0000 | u8::from(self.network); + if is_script { + prefix |= 0b0001_0000; + } + + let mut bytes: [u8; 29] = [0u8; 29]; + + bytes[0] = prefix; + bytes[1..].copy_from_slice(hash.as_ref()); + + e.bytes(&bytes)?; + Ok(()) + } +} + +impl<'b, C> minicbor::Decode<'b, C> for FieldedRewardAccount { + fn decode( + d: &mut minicbor::Decoder<'b>, + _ctx: &mut C, + ) -> Result { + let bytes = d.bytes()?; + FieldedRewardAccount::try_from(bytes).map_err(minicbor::decode::Error::message) + } +} + #[cfg(test)] pub mod tests { use pallas_codec::minicbor; @@ -877,4 +917,96 @@ pub mod tests { assert_eq!(hex::encode(bytes), hex::encode(bytes2)); } + + #[test] + fn test_fielded_reward_account_header_bytes() { + use super::{FieldedRewardAccount, StakeCredential}; + use crate::miniprotocols::localtxsubmission::Network; + use pallas_crypto::hash::Hash; + + let zero_hash: Hash<28> = [0u8; 28].into(); + + // Mainnet + KeyHash → 0xE1 + let acc = FieldedRewardAccount { + network: Network::Mainnet, + stake_credential: StakeCredential::AddrKeyhash(zero_hash), + }; + let encoded = minicbor::to_vec(&acc).unwrap(); + // CBOR bytes tag + 29 bytes payload; first payload byte is the header + let payload = &encoded[2..]; // skip CBOR major type + length + assert_eq!(payload[0], 0xE1); + + // Mainnet + ScriptHash → 0xF1 + let acc = FieldedRewardAccount { + network: Network::Mainnet, + stake_credential: StakeCredential::ScriptHash(zero_hash), + }; + let encoded = minicbor::to_vec(&acc).unwrap(); + let payload = &encoded[2..]; + assert_eq!(payload[0], 0xF1); + + // Testnet + KeyHash → 0xE0 + let acc = FieldedRewardAccount { + network: Network::Testnet, + stake_credential: StakeCredential::AddrKeyhash(zero_hash), + }; + let encoded = minicbor::to_vec(&acc).unwrap(); + let payload = &encoded[2..]; + assert_eq!(payload[0], 0xE0); + + // Testnet + ScriptHash → 0xF0 + let acc = FieldedRewardAccount { + network: Network::Testnet, + stake_credential: StakeCredential::ScriptHash(zero_hash), + }; + let encoded = minicbor::to_vec(&acc).unwrap(); + let payload = &encoded[2..]; + assert_eq!(payload[0], 0xF0); + } + + #[test] + fn test_fielded_reward_account_roundtrip() { + use super::{FieldedRewardAccount, StakeCredential}; + use crate::miniprotocols::localtxsubmission::Network; + use pallas_crypto::hash::Hash; + + let hash: Hash<28> = [0xAB; 28].into(); + + for (network, credential) in [ + (Network::Mainnet, StakeCredential::AddrKeyhash(hash)), + (Network::Mainnet, StakeCredential::ScriptHash(hash)), + (Network::Testnet, StakeCredential::AddrKeyhash(hash)), + (Network::Testnet, StakeCredential::ScriptHash(hash)), + ] { + let original = FieldedRewardAccount { + network, + stake_credential: credential, + }; + let encoded = minicbor::to_vec(&original).unwrap(); + let decoded: FieldedRewardAccount = minicbor::decode(&encoded).unwrap(); + assert_eq!(original, decoded); + } + } + + #[test] + fn test_fielded_reward_account_rejects_invalid_header() { + use super::{FieldedRewardAccount, InvalidRewardAccount}; + + // Old buggy encoding: 0x01 (mainnet, no type nibble) + let mut bad_bytes = [0u8; 29]; + bad_bytes[0] = 0x01; + let err = FieldedRewardAccount::try_from(bad_bytes.as_slice()).unwrap_err(); + assert!(matches!(err, InvalidRewardAccount::InvalidHeaderType(_))); + + // Wrong length + let short = [0xE1; 10]; + let err = FieldedRewardAccount::try_from(short.as_slice()).unwrap_err(); + assert!(matches!(err, InvalidRewardAccount::InvalidLength(10))); + + // Shelley payment address type (0x00) should be rejected + let mut shelley_header = [0u8; 29]; + shelley_header[0] = 0x01; // type 0b0000, mainnet + let err = FieldedRewardAccount::try_from(shelley_header.as_slice()).unwrap_err(); + assert!(matches!(err, InvalidRewardAccount::InvalidHeaderType(_))); + } } diff --git a/pallas-network/src/miniprotocols/localstate/queries_v16/mod.rs b/pallas-network/src/miniprotocols/localstate/queries_v16/mod.rs index 9478198cf..bc0cfabe3 100644 --- a/pallas-network/src/miniprotocols/localstate/queries_v16/mod.rs +++ b/pallas-network/src/miniprotocols/localstate/queries_v16/mod.rs @@ -2,6 +2,7 @@ use pallas_crypto::hash::Hash; use std::collections::{BTreeMap, BTreeSet}; +use std::fmt; use std::hash::Hash as StdHash; use std::ops::Deref; // required for derive attrs to work @@ -18,11 +19,11 @@ pub mod primitives; pub use primitives::{PoolMetadata, Relay}; use crate::miniprotocols::localtxsubmission::primitives::{ - CommitteeColdCredential, CommitteeHotCredential, ScriptRef, + CommitteeColdCredential, CommitteeHotCredential, ScriptRef, StakeCredential, }; use crate::miniprotocols::Point; -use crate::miniprotocols::localtxsubmission::SMaybe; +use crate::miniprotocols::localtxsubmission::{Network, SMaybe}; use super::{Client, ClientError}; @@ -77,7 +78,7 @@ pub type Credential = StakeAddr; /// Updates to the protocol params as [in the Haskell sources](https://github.com/IntersectMBO/cardano-ledger/blob/d30a7ae828e802e98277c82e278e570955afc273/libs/cardano-ledger-core/src/Cardano/Ledger/Core/PParams.hs#L151) /// (via [`EraPParams`](https://github.com/IntersectMBO/cardano-ledger/blob/d30a7ae828e802e98277c82e278e570955afc273/libs/cardano-ledger-core/src/Cardano/Ledger/Core/PParams.hs#L255-L258)). -#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone)] +#[derive(Encode, Decode, Debug, PartialEq, PartialOrd, Ord, Eq, Clone)] #[cbor(map)] pub struct PParamsUpdate { #[n(0)] @@ -297,7 +298,7 @@ pub struct ChainBlockNumber { pub block_number: u32, } -#[derive(Debug, PartialEq, Eq, Clone)] +#[derive(Debug, PartialEq, Eq, Clone, PartialOrd, Ord)] pub struct RationalNumber { pub numerator: u64, pub denominator: u64, @@ -312,7 +313,7 @@ pub type ProtocolVersion = (ProtocolVersionMajor, ProtocolVersionMinor); pub type CostModel = Vec; -#[derive(Encode, Debug, PartialEq, Eq, Clone)] +#[derive(Encode, Debug, PartialEq, Eq, Clone, PartialOrd, Ord)] #[cbor(map)] pub struct CostModels { #[n(0)] @@ -324,11 +325,14 @@ pub struct CostModels { #[n(2)] pub plutus_v3: Option, + #[n(3)] + pub plutus_v4: Option, + #[cbor(skip)] pub unknown: KeyValuePairs, } -#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone)] +#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, PartialOrd, Ord)] pub struct ExUnitPrices { #[n(0)] pub mem_price: PositiveInterval, @@ -337,7 +341,7 @@ pub struct ExUnitPrices { pub step_price: PositiveInterval, } -#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone)] +#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, PartialOrd, Ord)] pub struct ExUnits { #[n(0)] pub mem: u64, @@ -345,7 +349,7 @@ pub struct ExUnits { pub steps: u64, } /// Pool voting thresholds as [in the Haskell sources](https://github.com/IntersectMBO/cardano-ledger/blob/d30a7ae828e802e98277c82e278e570955afc273/eras/conway/impl/src/Cardano/Ledger/Conway/PParams.hs#L223-L229). -#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone)] +#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, PartialOrd, Ord)] pub struct PoolVotingThresholds { #[n(0)] pub motion_no_confidence: UnitInterval, @@ -360,7 +364,7 @@ pub struct PoolVotingThresholds { } /// DRrep voting thresholds as [in the Haskell sources](https://github.com/IntersectMBO/cardano-ledger/blob/d30a7ae828e802e98277c82e278e570955afc273/eras/conway/impl/src/Cardano/Ledger/Conway/PParams.hs#L295-L306). -#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone)] +#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, PartialOrd, Ord)] pub struct DRepVotingThresholds { #[n(0)] pub motion_no_confidence: UnitInterval, @@ -386,7 +390,9 @@ pub struct DRepVotingThresholds { /// Conway era protocol parameters, corresponding to [`ConwayPParams`](https://github.com/IntersectMBO/cardano-ledger/blob/d30a7ae828e802e98277c82e278e570955afc273/eras/conway/impl/src/Cardano/Ledger/Conway/PParams.hs#L512-L579) /// in the Haskell sources. -#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone)] +/// @todo: Encoding should be handled manually, Encode derive won't be correct. +#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, PartialOrd, Ord)] +#[cbor(map)] pub struct ProtocolParam { #[n(0)] pub minfee_a: Option, @@ -452,6 +458,72 @@ pub struct ProtocolParam { pub minfee_refscript_cost_per_byte: Option, } +#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, PartialOrd, Ord)] +pub struct CurrentProtocolParam { + #[n(0)] + pub minfee_a: Option, + #[n(1)] + pub minfee_b: Option, + #[n(2)] + pub max_block_body_size: Option, + #[n(3)] + pub max_transaction_size: Option, + #[n(4)] + pub max_block_header_size: Option, + #[n(5)] + pub key_deposit: Option, + #[n(6)] + pub pool_deposit: Option, + #[n(7)] + pub maximum_epoch: Option, + #[n(8)] + pub desired_number_of_stake_pools: Option, + #[n(9)] + pub pool_pledge_influence: Option, + #[n(10)] + pub expansion_rate: Option, + #[n(11)] + pub treasury_growth_rate: Option, + #[n(12)] + pub protocol_version: Option, + #[n(13)] + pub min_pool_cost: Option, + #[n(14)] + pub ada_per_utxo_byte: Option, + #[n(15)] + pub cost_models_for_script_languages: Option, + #[n(16)] + pub execution_costs: Option, + #[n(17)] + pub max_tx_ex_units: Option, + #[n(18)] + pub max_block_ex_units: Option, + #[n(19)] + pub max_value_size: Option, + #[n(20)] + pub collateral_percentage: Option, + #[n(21)] + pub max_collateral_inputs: Option, + #[n(22)] + pub pool_voting_thresholds: Option, + #[n(23)] + pub drep_voting_thresholds: Option, + #[n(24)] + pub min_committee_size: Option, + #[n(25)] + pub committee_term_limit: Option, + #[n(26)] + pub governance_action_validity_period: Option, + #[n(27)] + pub governance_action_deposit: Option, + #[n(28)] + pub drep_deposit: Option, + #[n(29)] + pub drep_inactivity_period: Option, + #[n(30)] + pub minfee_refscript_cost_per_byte: Option, +} + pub type StakeDistribution = KeyValuePairs; /// Tuple struct based on `BTreeSet` which uses the "Set" CBOR tag. @@ -528,7 +600,7 @@ pub struct IndividualPoolStake { pub type PoolDistr = BTreeMap; /// Anchor as [in the Haskell sources](https://github.com/IntersectMBO/cardano-ledger/blob/d30a7ae828e802e98277c82e278e570955afc273/libs/cardano-ledger-core/src/Cardano/Ledger/BaseTypes.hs#L867-L870). -#[derive(Debug, Encode, Decode, PartialEq, Eq, Clone)] +#[derive(Debug, Encode, Decode, PartialEq, Eq, Clone, PartialOrd, Ord)] pub struct Anchor { #[n(0)] pub url: String, @@ -537,7 +609,7 @@ pub struct Anchor { } /// Constitution as defined [in the Haskell sources](https://github.com/IntersectMBO/cardano-ledger/blob/d30a7ae828e802e98277c82e278e570955afc273/eras/conway/impl/src/Cardano/Ledger/Conway/Governance/Procedures.hs#L884-L887). -#[derive(Debug, Encode, Decode, Eq, PartialEq, Clone)] +#[derive(Debug, Encode, Decode, Eq, PartialEq, Clone, PartialOrd, Ord)] pub struct Constitution { #[n(0)] pub anchor: Anchor, @@ -557,16 +629,82 @@ pub enum Vote { Abstain, } -pub type RewardAccount = Bytes; +#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord)] +pub struct FieldedRewardAccount { + pub network: Network, + pub stake_credential: StakeCredential, +} + +#[derive(Debug)] +pub enum InvalidRewardAccount { + InvalidLength(usize), + InvalidHeaderType(u8), +} + +impl fmt::Display for InvalidRewardAccount { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::InvalidLength(len) => { + write!(f, "invalid reward account length: expected 29, got {len}") + } + Self::InvalidHeaderType(header) => { + write!(f, "invalid reward account header type: 0x{header:02x}") + } + } + } +} + +impl std::error::Error for InvalidRewardAccount {} + +impl TryFrom<&[u8]> for FieldedRewardAccount { + type Error = InvalidRewardAccount; + + fn try_from(bytes: &[u8]) -> Result { + if bytes.len() != 29 { + return Err(InvalidRewardAccount::InvalidLength(bytes.len())); + } + + // Header byte layout: [type(4 bits) | network(4 bits)] + // Upper nibble must be 0b1110 (key hash) or 0b1111 (script hash) + // per CIP-19 reward account address format. + let header_type = bytes[0] >> 4; + if !matches!(header_type, 0b1110 | 0b1111) { + return Err(InvalidRewardAccount::InvalidHeaderType(bytes[0])); + } + + let network = if bytes[0] & 0b0000_0001 != 0 { + Network::Mainnet + } else { + Network::Testnet + }; + + let mut hash = [0; 28]; + hash.copy_from_slice(&bytes[1..29]); + let stake_credential = if header_type == 0b1111 { + StakeCredential::ScriptHash(hash.into()) + } else { + StakeCredential::AddrKeyhash(hash.into()) + }; + + Ok(FieldedRewardAccount { + network, + stake_credential, + }) + } +} + pub type ScriptHash = Hash<28>; /// Governance action as defined [in the Haskell sources](https://github.com/IntersectMBO/cardano-ledger/blob/d30a7ae828e802e98277c82e278e570955afc273/eras/conway/impl/src/Cardano/Ledger/Conway/Governance/Procedures.hs#L785-L824). -#[derive(Debug, Eq, PartialEq, Clone)] +#[derive(Debug, Eq, PartialEq, Clone, PartialOrd, Ord)] #[allow(clippy::large_enum_variant)] pub enum GovAction { ParameterChange(Option, PParamsUpdate, Option), HardForkInitiation(Option, ProtocolVersion), - TreasuryWithdrawals(KeyValuePairs, Option), + TreasuryWithdrawals( + KeyValuePairs, + Option, + ), NoConfidence(Option), UpdateCommittee( Option, @@ -579,12 +717,12 @@ pub enum GovAction { } /// Proposal procedure state as defined [in the Haskell sources](https://github.com/IntersectMBO/cardano-ledger/blob/d30a7ae828e802e98277c82e278e570955afc273/eras/conway/impl/src/Cardano/Ledger/Conway/Governance/Procedures.hs#L476-L481 -#[derive(Debug, Encode, Decode, Eq, PartialEq, Clone)] +#[derive(Debug, Encode, Decode, Eq, PartialEq, Clone, PartialOrd, Ord)] pub struct ProposalProcedure { #[n(0)] pub deposit: Coin, #[n(1)] - pub return_addr: RewardAccount, + pub return_addr: FieldedRewardAccount, #[n(2)] pub gov_action: GovAction, #[n(3)] @@ -1203,7 +1341,7 @@ block_query_no_args! { #[doc = "Get the current protocol parameters"] get_current_pparams, GetCurrentPParams, - ProtocolParam, + CurrentProtocolParam, } block_query_no_args! { diff --git a/pallas-network/src/miniprotocols/localtxsubmission/codec.rs b/pallas-network/src/miniprotocols/localtxsubmission/codec.rs index b093df4be..0a9486896 100644 --- a/pallas-network/src/miniprotocols/localtxsubmission/codec.rs +++ b/pallas-network/src/miniprotocols/localtxsubmission/codec.rs @@ -291,6 +291,10 @@ impl<'b, C> Decode<'b, C> for ShelleyPoolPredFailure { d.decode_with(ctx)?, d.decode_with(ctx)?, )), + 6 => Ok(VRFKeyHashAlreadyRegistered( + d.decode_with(ctx)?, + d.decode_with(ctx)?, + )), _ => Err(decode::Error::message(format!( "unknown error tag while decoding ShelleyPoolPredFailure: {tag}" ))), @@ -328,6 +332,36 @@ where } } +/// Mismatch encoded as a CBOR array `[supplied, expected]` (Haskell wraps the +/// pair in an enclosing array for certain variants). +impl<'b, T, C> Decode<'b, C> for super::MismatchArr +where + T: Decode<'b, C>, +{ + fn decode(d: &mut Decoder<'b>, ctx: &mut C) -> Result { + d.array()?; + let t0 = d.decode_with(ctx)?; + let t1 = d.decode_with(ctx)?; + Ok(super::MismatchArr(t0, t1)) + } +} + +impl Encode for super::MismatchArr +where + T: Encode, +{ + fn encode( + &self, + e: &mut Encoder, + ctx: &mut C, + ) -> Result<(), encode::Error> { + e.array(2)?; + e.encode_with(&self.0, ctx)?; + e.encode_with(&self.1, ctx)?; + Ok(()) + } +} + impl<'b, C, K: pallas_codec::minicbor::Decode<'b, C>, V: pallas_codec::minicbor::Decode<'b, C>> Decode<'b, C> for OHashMap { @@ -2501,7 +2535,6 @@ mod tests { buffer.drain(0..pos); Ok(Some(msg)) } - Err(err) if err.is_end_of_input() => Ok(None), Err(err) => Err(Error::Decoding(err.to_string())), } } diff --git a/pallas-network/src/miniprotocols/localtxsubmission/primitives.rs b/pallas-network/src/miniprotocols/localtxsubmission/primitives.rs index ac2fdff40..f916eb783 100644 --- a/pallas-network/src/miniprotocols/localtxsubmission/primitives.rs +++ b/pallas-network/src/miniprotocols/localtxsubmission/primitives.rs @@ -1,8 +1,8 @@ // Material brought from `pallas-primitives` // TODO: Refactor in order to avoid repetition. use crate::miniprotocols::localstate::queries_v16::{ - Anchor, AssetName, Coin, DRep, Epoch, PolicyId, PoolMetadata, Relay, RewardAccount, ScriptHash, - UnitInterval, + Anchor, AssetName, Coin, DRep, Epoch, FieldedRewardAccount, PolicyId, PoolMetadata, Relay, + ScriptHash, UnitInterval, }; pub use pallas_codec::utils::KeyValuePairs; pub use pallas_crypto::hash::Hash; @@ -20,9 +20,9 @@ pub type Mint = Multiasset; #[derive(Debug, Decode, Encode, Clone, Hash, PartialEq, Eq)] #[cbor(flat)] pub enum Credential { - #[n(0)] - ScriptHashObj(#[n(0)] ScriptHash), #[n(1)] + ScriptHashObj(#[n(0)] ScriptHash), + #[n(0)] KeyHashObj(#[n(0)] AddrKeyhash), } @@ -37,7 +37,7 @@ pub enum Certificate { pledge: Coin, cost: Coin, margin: UnitInterval, - reward_account: RewardAccount, + reward_account: FieldedRewardAccount, pool_owners: Set, relays: Vec, pool_metadata: Nullable, @@ -103,6 +103,8 @@ pub enum Language { PlutusV2, #[n(2)] PlutusV3, + #[n(3)] + PlutusV4, } #[derive(Debug, PartialEq, Eq, Clone)] diff --git a/pallas-network/src/miniprotocols/localtxsubmission/protocol.rs b/pallas-network/src/miniprotocols/localtxsubmission/protocol.rs index 949779c37..5979c4bcf 100644 --- a/pallas-network/src/miniprotocols/localtxsubmission/protocol.rs +++ b/pallas-network/src/miniprotocols/localtxsubmission/protocol.rs @@ -2,8 +2,8 @@ use thiserror::Error; use super::primitives::{Certificate, Credential, Language, StakeCredential, Voter}; use crate::miniprotocols::localstate::queries_v16::{ - Anchor, GovAction, GovActionId, PolicyId, ProposalProcedure, ProtocolVersion, ScriptHash, - TransactionInput, TransactionOutput, Value, Vote, + Anchor, BigInt, FieldedRewardAccount, GovAction, GovActionId, PolicyId, ProposalProcedure, + ProtocolVersion, ScriptHash, TransactionInput, TransactionOutput, Value, Vote, }; pub use crate::miniprotocols::localstate::queries_v16::{Coin, ExUnits, TaggedSet}; use crate::multiplexer; @@ -103,7 +103,7 @@ pub type PlutusPurposeItem = PlutusPurpose< TransactionInput, PolicyId, ConwayTxCert, - DisplayRewardAccount, + FieldedRewardAccount, Voter, ProposalProcedure, >; @@ -176,6 +176,8 @@ pub enum ConwayContextError { ProposalProceduresFieldNotSupported(#[n(0)] DisplayOSet), #[n(14)] TreasuryDonationFieldNotSupported(#[n(0)] DisplayCoin), + #[n(15)] + ReferenceInputsNotDisjointFromInputs(#[n(0)] Vec), } #[derive(Debug, Decode, Encode, Clone, Eq, PartialEq)] #[cbor(transparent)] @@ -263,7 +265,7 @@ pub struct Utxo(pub OHashMap); #[derive(Debug, PartialEq, Eq, Clone)] pub struct OHashMap(pub Vec<(K, V)>); -#[derive(Encode, Decode, Debug, Clone, Eq, PartialEq)] +#[derive(Encode, Decode, Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord)] #[cbor(index_only)] pub enum Network { #[n(0)] @@ -272,19 +274,17 @@ pub enum Network { Mainnet, } -#[derive(Debug, Decode, Encode, Clone, Eq, PartialEq)] -#[cbor(transparent)] -pub struct DeltaCoin(#[n(0)] pub i32); - -#[derive(Debug, Decode, Encode, Clone, Eq, PartialEq)] -#[cbor(transparent)] -pub struct DisplayRewardAccount(#[n(0)] pub Bytes); - -impl From<&Bytes> for DisplayRewardAccount { - fn from(bytes: &Bytes) -> Self { - DisplayRewardAccount(bytes.to_owned()) +impl From for u8 { + fn from(value: Network) -> u8 { + match value { + Network::Mainnet => 1, + Network::Testnet => 0, + } } } +#[derive(Debug, Decode, Encode, Clone, Eq, PartialEq)] +#[cbor(transparent)] +pub struct DeltaCoin(#[n(0)] pub BigInt); pub type Slot = u64; @@ -308,7 +308,7 @@ pub enum UtxoFailure { #[n(2)] OutsideValidityIntervalUTxO(#[n(0)] ValidityInterval, #[n(1)] Slot), #[n(3)] - MaxTxSizeUTxO(#[n(0)] i64, #[n(1)] i64), + MaxTxSizeUTxO(#[n(0)] BigInt, #[n(1)] BigInt), #[n(4)] InputSetEmptyUTxO, #[n(5)] @@ -318,7 +318,7 @@ pub enum UtxoFailure { #[n(7)] WrongNetwork(#[n(0)] Network, #[n(1)] Set), #[n(8)] - WrongNetworkWithdrawal(#[n(0)] Network, #[n(1)] Set), + WrongNetworkWithdrawal(#[n(0)] Network, #[n(1)] Set), #[n(9)] OutputTooSmallUTxO(#[n(0)] Array), #[n(10)] @@ -338,7 +338,7 @@ pub enum UtxoFailure { #[n(17)] OutsideForecast(#[n(0)] Slot), #[n(18)] - TooManyCollateralInputs(#[n(0)] u16, #[n(1)] u16), + TooManyCollateralInputs(#[n(0)] u64, #[n(1)] u64), #[n(19)] NoCollateralInputs, #[n(20)] @@ -411,6 +411,8 @@ pub enum ConwayUtxoWPredFailure { MalformedScriptWitnesses(#[n(0)] Set), #[n(17)] MalformedReferenceScripts(#[n(0)] Set), + #[n(18)] + ScriptIntegrityHashMismatch(#[n(0)] MismatchArr>, #[n(1)] SMaybe), } #[derive(Debug, Decode, Encode, Hash, PartialEq, Eq, Clone)] @@ -430,14 +432,17 @@ pub struct DisplayPolicyId(#[n(0)] pub PolicyId); #[derive(Debug, Clone, Eq, PartialEq)] pub struct Mismatch(pub T, pub T); +/// Like `Mismatch` but encoded as a CBOR array `[supplied, expected]` +/// (Haskell wraps the Mismatch pair in an enclosing array for some variants). +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct MismatchArr(pub T, pub T); + #[derive(Debug, Decode, Encode, Clone, Eq, PartialEq)] #[cbor(transparent)] pub struct EpochNo(#[n(0)] pub u64); /// Conway era ledger transaction errors, corresponding to [`ConwayLedgerPredFailure`](https://github.com/IntersectMBO/cardano-ledger/blob/d30a7ae828e802e98277c82e278e570955afc273/eras/conway/impl/src/Cardano/Ledger/Conway/Rules/Ledger.hs#L138-L153) /// in the Haskell sources. -/// -/// The `u8` variant appears for backward compatibility. #[allow(clippy::large_enum_variant)] #[derive(Debug, Decode, Encode, Clone, Eq, PartialEq)] #[cbor(flat)] @@ -457,14 +462,16 @@ pub enum ConwayLedgerFailure { #[n(7)] MempoolFailure(#[n(0)] String), #[n(8)] - U8(#[n(0)] u8), + WithdrawalsMissingAccounts(#[n(0)] OHashMap), + #[n(9)] + IncompleteWithdrawals(#[n(0)] OHashMap>), } // https://github.com/IntersectMBO/cardano-ledger/blob/33e90ea03447b44a389985ca2b158568e5f4ad65/eras/conway/impl/src/Cardano/Ledger/Conway/Rules/Certs.hs#L113 #[derive(Debug, Decode, Encode, Clone, Eq, PartialEq)] #[cbor(flat)] pub enum ConwayCertsPredFailure { #[n(0)] - WithdrawalsNotInRewardsCERTS(#[n(0)] OHashMap), + WithdrawalsNotInRewardsCERTS(#[n(0)] OHashMap), #[n(1)] CertFailure(#[n(0)] ConwayCertPredFailure), } @@ -482,6 +489,7 @@ pub enum ConwayCertPredFailure { } // Reminder, encoding of this enum should be custom, see decoder for info. +// Haskell tags: 0, 1, (skip 2 — removed WrongCertificateTypePOOL), 3, 4, 5, 6 #[derive(Debug, Encode, Clone, Eq, PartialEq)] #[cbor(flat)] pub enum ShelleyPoolPredFailure { @@ -489,12 +497,14 @@ pub enum ShelleyPoolPredFailure { StakePoolNotRegisteredOnKeyPOOL(#[n(0)] KeyHash), #[n(1)] StakePoolRetirementWrongEpochPOOL(#[n(0)] Mismatch, #[n(1)] Mismatch), - #[n(2)] - StakePoolCostTooLowPOOL(#[n(0)] Mismatch), #[n(3)] - WrongNetworkPOOL(#[n(0)] Mismatch, #[n(1)] KeyHash), + StakePoolCostTooLowPOOL(#[n(0)] Mismatch), #[n(4)] + WrongNetworkPOOL(#[n(0)] Mismatch, #[n(1)] KeyHash), + #[n(5)] PoolMedataHashTooBig(#[n(0)] KeyHash, #[n(1)] i64), + #[n(6)] + VRFKeyHashAlreadyRegistered(#[n(0)] KeyHash, #[n(1)] Bytes), } // https://github.com/IntersectMBO/cardano-ledger/blob/33e90ea03447b44a389985ca2b158568e5f4ad65/eras/conway/impl/src/Cardano/Ledger/Conway/Rules/GovCert.hs#L118C6-L118C30 @@ -531,6 +541,10 @@ pub enum ConwayDelegPredFailure { DelegateeDRepNotRegisteredDELEG(#[n(0)] Credential), #[n(6)] DelegateeStakePoolNotRegisteredDELEG(#[n(0)] KeyHash), + #[n(7)] + DepositIncorrectDELEG(#[n(0)] MismatchArr), + #[n(8)] + RefundIncorrectDELEG(#[n(0)] MismatchArr), } // https://github.com/IntersectMBO/cardano-ledger/blob/33e90ea03447b44a389985ca2b158568e5f4ad65/eras/conway/impl/src/Cardano/Ledger/Conway/Rules/Gov.hs#L164 @@ -542,9 +556,9 @@ pub enum ConwayGovPredFailure { #[n(1)] MalformedProposal(#[n(0)] GovAction), #[n(2)] - ProposalProcedureNetworkIdMismatch(#[n(0)] DisplayRewardAccount, #[n(1)] Network), + ProposalProcedureNetworkIdMismatch(#[n(0)] FieldedRewardAccount, #[n(1)] Network), #[n(3)] - TreasuryWithdrawalsNetworkIdMismatch(#[n(0)] Set, #[n(1)] Network), + TreasuryWithdrawalsNetworkIdMismatch(#[n(0)] Set, #[n(1)] Network), #[n(4)] ProposalDepositIncorrect(#[n(0)] DisplayCoin, #[n(1)] DisplayCoin), #[n(5)] @@ -577,9 +591,11 @@ pub enum ConwayGovPredFailure { #[n(15)] ZeroTreasuryWithdrawals(#[n(0)] GovAction), #[n(16)] - ProposalReturnAccountDoesNotExist(#[n(0)] DisplayRewardAccount), + ProposalReturnAccountDoesNotExist(#[n(0)] FieldedRewardAccount), #[n(17)] - TreasuryWithdrawalReturnAccountsDoNotExist(#[n(0)] Vec), + TreasuryWithdrawalReturnAccountsDoNotExist(#[n(0)] Vec), + #[n(18)] + UnelectedCommitteeVoters(#[n(0)] Vec), } /// Reject reason. It can be a pair of an era number and a sequence of errors, diff --git a/pallas-primitives/src/conway/model.rs b/pallas-primitives/src/conway/model.rs index d49d8d980..1ee0bc95d 100644 --- a/pallas-primitives/src/conway/model.rs +++ b/pallas-primitives/src/conway/model.rs @@ -166,6 +166,9 @@ pub struct CostModels { #[n(2)] pub plutus_v3: Option, + #[n(3)] + pub plutus_v4: Option, + #[cbor(skip)] pub unknown: BTreeMap, } @@ -177,6 +180,7 @@ impl<'b, C> minicbor::Decode<'b, C> for CostModels { let mut plutus_v1 = None; let mut plutus_v2 = None; let mut plutus_v3 = None; + let mut plutus_v4 = None; let mut unknown: Vec<(u64, CostModel)> = Vec::new(); for (k, v) in models.iter() { @@ -184,6 +188,7 @@ impl<'b, C> minicbor::Decode<'b, C> for CostModels { 0 => plutus_v1 = Some(v.clone()), 1 => plutus_v2 = Some(v.clone()), 2 => plutus_v3 = Some(v.clone()), + 3 => plutus_v4 = Some(v.clone()), _ => unknown.push((*k, v.clone())), } } @@ -192,6 +197,7 @@ impl<'b, C> minicbor::Decode<'b, C> for CostModels { plutus_v1, plutus_v2, plutus_v3, + plutus_v4, unknown: unknown.into_iter().collect(), }) } @@ -708,7 +714,7 @@ pub struct Block<'b> { #[deprecated(since = "1.0.0-alpha", note = "use `Block` instead")] pub type MintedBlock<'b> = Block<'b>; -#[derive(Clone, Serialize, Deserialize, Encode, Decode, Debug, PartialEq)] +#[derive(Clone, Serialize, Deserialize, Encode, Debug, PartialEq)] pub struct Tx<'b> { #[b(0)] pub transaction_body: KeepRaw<'b, TransactionBody<'b>>, @@ -725,6 +731,37 @@ pub struct Tx<'b> { impl Eq for Tx<'_> {} +impl<'b, C> minicbor::Decode<'b, C> for Tx<'b> +where + KeepRaw<'b, TransactionBody<'b>>: minicbor::Decode<'b, C>, + KeepRaw<'b, WitnessSet<'b>>: minicbor::Decode<'b, C>, + Nullable>: minicbor::Decode<'b, C>, +{ + fn decode(d: &mut minicbor::Decoder<'b>, ctx: &mut C) -> Result { + d.array()?; + + let transaction_body = d.decode_with(ctx)?; + let transaction_witness_set = d.decode_with(ctx)?; + let success = match d.decode_with(ctx) { + Ok(v) => v, + Err(e) if e.is_end_of_input() => true, + Err(e) => return Err(e), + }; + let auxiliary_data = match d.decode_with(ctx) { + Ok(v) => v, + Err(e) if e.is_end_of_input() => Nullable::Null, + Err(e) => return Err(e), + }; + + Ok(Self { + transaction_body, + transaction_witness_set, + success, + auxiliary_data, + }) + } +} + #[deprecated(since = "1.0.0-alpha", note = "use `Tx` instead")] pub type MintedTx<'b> = Tx<'b>; diff --git a/pallas-validate/tests/conway.rs b/pallas-validate/tests/conway.rs index 26c0ec4c8..1ffc3cd0d 100644 --- a/pallas-validate/tests/conway.rs +++ b/pallas-validate/tests/conway.rs @@ -1187,6 +1187,7 @@ mod conway_tests { plutus_v2: None, plutus_v3: None, + plutus_v4: None, unknown: BTreeMap::default(), }, execution_costs: pallas_primitives::ExUnitPrices { @@ -1532,6 +1533,7 @@ mod conway_tests { 281145, 18848, 0, 1, 180194, 159, 1, 1, 158519, 8942, 0, 1, 159378, 8813, 0, 1, 107490, 3298, 1, 106057, 655, 1, 1964219, 24520, 3, ]), + plutus_v4: None, unknown: BTreeMap::default(), }, execution_costs: pallas_primitives::ExUnitPrices { @@ -1715,6 +1717,7 @@ mod conway_tests { 281145, 18848, 0, 1, 180194, 159, 1, 1, 158519, 8942, 0, 1, 159378, 8813, 0, 1, 107490, 3298, 1, 106057, 655, 1, 1964219, 24520, 3, ]), + plutus_v4: None, unknown: BTreeMap::default(), }, execution_costs: pallas_primitives::ExUnitPrices {