feat(applying): add cert and native script validation for ShelleyMA (#510)

BREAKING CHANGE: the `validate` fn signature has changed to support these changes

---------

Co-authored-by: Ale Gadea <ale.gadea@txpipe.io>
This commit is contained in:
Pedro Sánchez Terraf 2024-09-23 13:24:32 -03:00 committed by GitHub
parent 1bec8be109
commit 07b74515a4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 1881 additions and 134 deletions

View file

@ -9,41 +9,78 @@ pub mod utils;
use alonzo::validate_alonzo_tx;
use babbage::validate_babbage_tx;
use byron::validate_byron_tx;
use pallas_primitives::alonzo::TransactionIndex;
use pallas_traverse::{Era, MultiEraTx};
use shelley_ma::validate_shelley_ma_tx;
pub use utils::{
Environment, MultiEraProtocolParameters, UTxOs,
ValidationError::{TxAndProtParamsDiffer, UnknownProtParams},
CertState, Environment, MultiEraProtocolParameters, UTxOs,
ValidationError::{
EnvMissingAccountState, PParamsByronDoesntNeedAccountState, TxAndProtParamsDiffer,
UnknownProtParams,
},
ValidationResult,
};
pub fn validate(metx: &MultiEraTx, utxos: &UTxOs, env: &Environment) -> ValidationResult {
match env.prot_params() {
MultiEraProtocolParameters::Byron(bpp) => match metx {
/// Ledger sequence rule: LEDGERS
pub fn validate_txs(
metxs: &[MultiEraTx],
env: &Environment,
utxos: &UTxOs,
cert_state: &mut CertState,
) -> ValidationResult {
let mut delta_state: CertState = cert_state.clone();
for (txix, metx) in metxs.iter().enumerate() {
validate_tx(
&metx,
txix.try_into().unwrap(),
env,
utxos,
&mut delta_state,
)?;
}
*cert_state = delta_state;
Ok(())
}
/// Ledger inference rule: LEDGER
pub fn validate_tx(
metx: &MultiEraTx,
txix: TransactionIndex,
env: &Environment,
utxos: &UTxOs,
cert_state: &mut CertState,
) -> ValidationResult {
let pp_acnt = (env.prot_params(), env.acnt());
match pp_acnt {
(MultiEraProtocolParameters::Byron(bpp), None) => match metx {
MultiEraTx::Byron(mtxp) => validate_byron_tx(mtxp, utxos, bpp, env.prot_magic()),
_ => Err(TxAndProtParamsDiffer),
},
MultiEraProtocolParameters::Shelley(spp) => match metx {
(MultiEraProtocolParameters::Byron(_), Some(_)) => Err(PParamsByronDoesntNeedAccountState),
(MultiEraProtocolParameters::Shelley(spp), Some(acnt)) => match metx {
MultiEraTx::AlonzoCompatible(mtx, Era::Shelley)
| MultiEraTx::AlonzoCompatible(mtx, Era::Allegra)
| MultiEraTx::AlonzoCompatible(mtx, Era::Mary) => validate_shelley_ma_tx(
mtx,
txix,
utxos,
cert_state,
spp,
&acnt,
env.block_slot(),
env.network_id(),
&metx.era(),
),
_ => Err(TxAndProtParamsDiffer),
},
MultiEraProtocolParameters::Alonzo(app) => match metx {
(MultiEraProtocolParameters::Alonzo(app), _) => match metx {
MultiEraTx::AlonzoCompatible(mtx, Era::Alonzo) => {
validate_alonzo_tx(mtx, utxos, app, env.block_slot(), env.network_id())
}
_ => Err(TxAndProtParamsDiffer),
},
MultiEraProtocolParameters::Babbage(bpp) => match metx {
(MultiEraProtocolParameters::Babbage(bpp), _) => match metx {
MultiEraTx::Babbage(mtx) => validate_babbage_tx(
mtx,
utxos,
@ -54,8 +91,9 @@ pub fn validate(metx: &MultiEraTx, utxos: &UTxOs, env: &Environment) -> Validati
),
_ => Err(TxAndProtParamsDiffer),
},
MultiEraProtocolParameters::Conway(_) => {
(MultiEraProtocolParameters::Conway(_), _) => {
todo!("conway phase-1 validation not yet implemented");
}
(_, None) => Err(EnvMissingAccountState),
}
}

View file

@ -4,6 +4,7 @@ use crate::utils::{
add_minted_value, add_values, aux_data_from_alonzo_minted_tx, empty_value,
get_alonzo_comp_tx_size, get_lovelace_from_alonzo_val, get_payment_part, get_shelley_address,
get_val_size_in_words, mk_alonzo_vk_wits_check_list, values_are_equal, verify_signature,
AccountState, CertPointer, CertState, DState, PState, PoolParam,
ShelleyMAError::*,
ShelleyProtParams, UTxOs,
ValidationError::{self, *},
@ -11,20 +12,32 @@ use crate::utils::{
};
use pallas_addresses::{PaymentKeyHash, ScriptHash, ShelleyAddress, ShelleyPaymentPart};
use pallas_codec::minicbor::encode;
use pallas_crypto::hash::Hasher as PallasHasher;
use pallas_primitives::{
alonzo::{
MintedTx, MintedWitnessSet, NativeScript, PolicyId, TransactionBody, TransactionOutput,
VKeyWitness, Value,
Certificate::{self, *},
Coin, Epoch, GenesisDelegateHash, Genesishash,
InstantaneousRewardSource::*,
InstantaneousRewardTarget::*,
MintedTx, MintedWitnessSet, MoveInstantaneousReward, NativeScript, PolicyId, PoolKeyhash,
StakeCredential::{self},
TransactionBody, TransactionIndex, TransactionOutput, VKeyWitness, Value, VrfKeyhash,
},
byron::TxOut,
};
use pallas_traverse::{ComputeHash, Era, MultiEraInput, MultiEraOutput};
use std::{cmp::max, ops::Deref};
use pallas_traverse::{
time::Slot, wellknown::GenesisValues, ComputeHash, Era, MultiEraInput, MultiEraOutput,
};
use std::{cmp::max, collections::HashMap, ops::Deref}; // TODO: remove when fixed missing args
pub fn validate_shelley_ma_tx(
mtx: &MintedTx,
txix: TransactionIndex,
utxos: &UTxOs,
cert_state: &mut CertState,
prot_pps: &ShelleyProtParams,
acnt: &AccountState,
block_slot: &u64,
network_id: &u8,
era: &Era,
@ -32,12 +45,38 @@ pub fn validate_shelley_ma_tx(
let tx_body: &TransactionBody = &mtx.transaction_body;
let tx_wits: &MintedWitnessSet = &mtx.transaction_witness_set;
let size: u32 = get_alonzo_comp_tx_size(mtx);
let stk_dep_count: &mut u64 = &mut 0; // count of key registrations (for deposits)
let stk_refund_count: &mut u64 = &mut 0; // count of key deregs (for refunds)
let pool_count: &mut u64 = &mut 0; // count of pool regs (for deposits)
let stab_win = 129600; // FIXME: Found as "1.5 days" in unreliable sources.
check_ins_not_empty(tx_body)?;
check_ins_in_utxos(tx_body, utxos)?;
check_ttl(tx_body, block_slot)?;
check_tx_size(&size, prot_pps)?;
check_min_lovelace(tx_body, prot_pps, era)?;
check_preservation_of_value(tx_body, utxos, era)?;
check_certificates(
&tx_body.certificates,
txix,
cert_state,
stk_dep_count,
stk_refund_count,
pool_count,
&acnt,
block_slot,
&stab_win,
prot_pps,
)?;
check_preservation_of_value(
tx_body,
utxos,
stk_dep_count,
stk_refund_count,
pool_count,
era,
prot_pps,
)?;
check_fees(tx_body, &size, prot_pps)?;
check_network_id(tx_body, network_id)?;
check_metadata(tx_body, mtx)?;
@ -115,25 +154,27 @@ fn compute_min_lovelace(output: &TransactionOutput, prot_pps: &ShelleyProtParams
fn check_preservation_of_value(
tx_body: &TransactionBody,
utxos: &UTxOs,
stk_dep_count: &u64,
stk_refund_count: &u64,
pool_count: &u64,
era: &Era,
prot_pps: &ShelleyProtParams,
) -> ValidationResult {
let neg_val_err: ValidationError = ShelleyMA(NegativeValue);
let input: Value = get_consumed(tx_body, utxos, era)?;
let produced: Value = get_produced(tx_body, era)?;
let output: Value = add_values(&produced, &Value::Coin(tx_body.fee), &neg_val_err)?;
if let Some(m) = &tx_body.mint {
add_minted_value(&output, m, &neg_val_err)?;
}
if !values_are_equal(&input, &output) {
return Err(ShelleyMA(PreservationOfValue));
}
let consumed: Value = get_consumed(tx_body, utxos, stk_refund_count, era, prot_pps)?;
let produced: Value = get_produced(tx_body, stk_dep_count, pool_count, era, prot_pps)?;
if !values_are_equal(&consumed, &produced) {
Err(ShelleyMA(PreservationOfValue))
} else {
Ok(())
}
}
fn get_consumed(
tx_body: &TransactionBody,
utxos: &UTxOs,
stk_refund_count: &u64,
era: &Era,
prot_pps: &ShelleyProtParams,
) -> Result<Value, ValidationError> {
let neg_val_err: ValidationError = ShelleyMA(NegativeValue);
let mut res: Value = empty_value();
@ -155,10 +196,26 @@ fn get_consumed(
},
}
}
// TODO: Set right error message below.
// Adding key refunds and minted assets
res = add_values(
&res,
&Value::Coin(prot_pps.key_deposit * *stk_refund_count),
&neg_val_err,
)?;
if let Some(m) = &tx_body.mint {
res = add_minted_value(&res, m, &neg_val_err)?;
}
Ok(res)
}
fn get_produced(tx_body: &TransactionBody, era: &Era) -> Result<Value, ValidationError> {
fn get_produced(
tx_body: &TransactionBody,
stk_dep_count: &u64,
pool_count: &u64,
era: &Era,
prot_pps: &ShelleyProtParams,
) -> Result<Value, ValidationError> {
let neg_val_err: ValidationError = ShelleyMA(NegativeValue);
let mut res: Value = empty_value();
for TransactionOutput { amount, .. } in tx_body.outputs.iter() {
@ -168,6 +225,13 @@ fn get_produced(tx_body: &TransactionBody, era: &Era) -> Result<Value, Validatio
_ => res = add_values(&res, amount, &neg_val_err)?,
}
}
// TODO: Set right error message below.
// Adding fees
res = add_values(&res, &Value::Coin(tx_body.fee), &neg_val_err)?;
// Pool reg deposits and staking key registrations
let total_deposits = prot_pps.pool_deposit * *pool_count +
prot_pps.key_deposit * *stk_dep_count;
res = add_values(&res, &Value::Coin(total_deposits), &neg_val_err)?;
Ok(res)
}
@ -220,6 +284,10 @@ fn check_witnesses(
let vk_wits: &mut Vec<(bool, VKeyWitness)> =
&mut mk_alonzo_vk_wits_check_list(&tx_wits.vkeywitness, ShelleyMA(MissingVKWitness))?;
let tx_hash: &Vec<u8> = &Vec::from(tx_body.compute_hash().as_ref());
let native_scripts: Vec<NativeScript> = match &tx_wits.native_script {
Some(scripts) => scripts.iter().map(|x| x.clone().unwrap()).collect(),
None => Vec::new(),
};
for input in tx_body.inputs.iter() {
match utxos.get(&MultiEraInput::from_alonzo_compatible(input)) {
Some(multi_era_output) => {
@ -243,6 +311,13 @@ fn check_witnesses(
None => return Err(ShelleyMA(InputNotInUTxO)),
}
}
let vkey_wits = &vk_wits.iter().map(|bv| bv.clone().1).collect();
check_native_scripts(
vkey_wits,
&native_scripts,
&tx_body.validity_interval_start,
&tx_body.ttl,
)?;
check_remaining_vk_wits(vk_wits, tx_hash)
}
@ -330,3 +405,339 @@ fn compute_script_hash(script: &NativeScript) -> PolicyId {
payload.insert(0, 0);
pallas_crypto::hash::Hasher::<224>::hash(&payload)
}
// Checks all certificates in order, and counts the relevant ones for computing deposits.
fn check_certificates(
cert_opt: &Option<Vec<Certificate>>,
tx_ix: TransactionIndex,
cert_state: &mut CertState,
stk_dep_count: &mut u64,
stk_refund_count: &mut u64,
pool_count: &mut u64,
acnt: &AccountState,
slot: &Slot,
stab_win: &Slot,
prot_pps: &ShelleyProtParams,
) -> ValidationResult {
if let Some(certs) = cert_opt {
let genesis = &GenesisValues::mainnet();
let cepoch: Epoch = to_epoch(genesis, slot);
let mpc: Coin = prot_pps.min_pool_cost;
let mut ptr = CertPointer {
slot: *slot,
tx_ix,
cert_ix: 0,
};
for (ix, cert) in certs.iter().enumerate() {
match cert {
StakeRegistration(stc) => {
*stk_dep_count += 1;
check_stake_registration(stc, &ptr, &mut cert_state.dstate)?;
}
StakeDeregistration(stc) => {
check_stake_deregistration(stc, &mut cert_state.dstate)?;
*stk_refund_count += 1;
}
StakeDelegation(stc, pk) => {
check_stake_delegation(stc, pk, &mut cert_state.dstate, &cert_state.pstate)?;
}
PoolRegistration {
operator,
vrf_keyhash,
pledge,
cost,
margin,
reward_account,
pool_owners,
relays,
pool_metadata,
} => {
if !cert_state.pstate.pool_params.contains_key(&operator) {
*pool_count += 1;
}
let pool_param = PoolParam {
vrf_keyhash: *vrf_keyhash,
pledge: *pledge,
cost: *cost,
margin: margin.clone(),
reward_account: reward_account.clone(),
pool_owners: pool_owners.clone(),
relays: relays.clone(),
pool_metadata: pool_metadata.clone(),
};
check_pool_reg_or_update(operator, &pool_param, &mpc, &mut cert_state.pstate)?;
}
PoolRetirement(pk, repoch) => {
check_pool_retirement(
pk,
repoch,
&cepoch,
&prot_pps.maximum_epoch,
&mut cert_state.pstate,
)?;
}
GenesisKeyDelegation(gkh, dkh, vrf) => {
check_genesis_key_delegation(
gkh,
dkh,
vrf,
slot,
stab_win,
&mut cert_state.dstate,
)?;
}
MoveInstantaneousRewardsCert(mir) => {
check_mir(mir, slot, stab_win, &mut cert_state.dstate, acnt)?;
}
}
ptr.cert_ix = ix as u32; // FIXME: Careful here, `ix` is `usize`
}
Ok(())
} else {
Ok(())
}
}
fn check_stake_registration(
stc: &StakeCredential,
ptr: &CertPointer,
ds: &mut DState,
) -> ValidationResult {
insert_or_err(
&mut ds.rewards,
stc,
&0_u64,
ShelleyMA(KeyAlreadyRegistered),
)?;
insert_or_err(&mut ds.ptrs, ptr, stc, ShelleyMA(PointerInUse))
}
fn check_stake_deregistration(stc: &StakeCredential, ds: &mut DState) -> ValidationResult {
match ds.rewards.get(stc) {
None => Err(ShelleyMA(KeyNotRegistered)),
Some(0) => {
ds.ptrs.retain(|_, v| v != stc);
ds.delegations.remove(stc);
ds.rewards.remove(stc);
Ok(())
}
Some(_) => Err(ShelleyMA(RewardsNotNull)),
}
}
fn check_stake_delegation(
stc: &StakeCredential,
pk: &PoolKeyhash,
ds: &mut DState,
ps: &PState,
) -> ValidationResult {
if !ps.pool_params.contains_key(pk) {
Err(ShelleyMA(PoolNotRegistered))
} else if ds.rewards.contains_key(stc) {
ds.delegations.insert(stc.clone(), *pk);
Ok(())
} else {
Err(ShelleyMA(KeyNotRegistered))
}
}
// Inserts a key-value pair if the key is not already in use, otherwise return
// the provided error.
fn insert_or_err<K, V, E>(map: &mut HashMap<K, V>, key: &K, value: &V, error: E) -> Result<(), E>
where
K: Eq,
K: std::hash::Hash,
K: Clone,
V: Clone,
{
if map.contains_key(key) {
return Err(error);
} else {
map.insert(key.clone(), value.clone());
Ok(())
}
}
fn check_pool_reg_or_update(
pool_hash: &PoolKeyhash,
pool_param: &PoolParam,
min_pool_cost: &Coin,
ps: &mut PState,
) -> ValidationResult {
if pool_param.cost < *min_pool_cost {
Err(ShelleyMA(PoolCostBelowMin))
} else if ps.pool_params.contains_key(pool_hash) {
// Updating
ps.fut_pool_params
.insert(pool_hash.clone(), (*pool_param).clone());
ps.retiring.remove(&pool_hash);
Ok(())
} else {
// Registering
ps.pool_params
.insert(pool_hash.clone(), (*pool_param).clone());
Ok(())
}
}
fn check_pool_retirement(
pool_hash: &PoolKeyhash,
repoch: &Epoch,
cepoch: &Epoch,
emax: &u32,
ps: &mut PState,
) -> ValidationResult {
if !ps.pool_params.contains_key(&pool_hash) {
return Err(ShelleyMA(PoolNotRegistered));
}
if (*cepoch < *repoch) & (*repoch <= *cepoch + *emax as u64) {
ps.retiring.insert(pool_hash.clone(), *repoch);
Ok(())
} else {
Err(ShelleyMA(PoolNotRegistered))
}
}
fn check_genesis_key_delegation(
gkh: &Genesishash,
dkh: &GenesisDelegateHash, // called `vkh` in specs
vrf: &VrfKeyhash,
slot: &Slot,
stab_win: &Slot,
ds: &mut DState,
) -> ValidationResult {
let cod = ds
.gen_delegs
.iter()
.filter(|kv| kv.0 != gkh)
.map(|kv| kv.1)
.collect::<Vec<_>>();
let fod = ds
.fut_gen_delegs
.iter()
.filter(|kv| kv.0 .1 != *gkh)
.map(|kv| kv.1)
.collect::<Vec<_>>();
let curr_keyhashes = cod.iter().map(|v| v.0.clone()).collect::<Vec<_>>();
let curr_vrfs = cod.iter().map(|v| v.1).collect::<Vec<_>>();
let fut_keyhashes = fod.iter().map(|v| v.0.clone()).collect::<Vec<_>>();
let fut_vrfs = fod.iter().map(|v| v.1).collect::<Vec<_>>();
if curr_keyhashes.contains(dkh)
| fut_keyhashes.contains(dkh)
| curr_vrfs.contains(vrf)
| fut_vrfs.contains(vrf)
{
Err(ShelleyMA(DuplicateGenesisDelegate))
} else if !ds.gen_delegs.contains_key(gkh) {
Err(ShelleyMA(GenesisKeyNotInMapping))
} else {
let gen_slot: Slot = *slot + *stab_win;
ds.fut_gen_delegs
.insert((gen_slot, gkh.clone()), (dkh.clone(), vrf.clone()));
Ok(())
}
}
fn check_mir(
mir: &MoveInstantaneousReward,
slot: &Slot,
stab_win: &Slot,
ds: &mut DState,
acnt: &AccountState,
) -> ValidationResult {
let genesis = &GenesisValues::mainnet();
if !(*slot < first_slot(genesis, &(to_epoch(genesis, slot) + 1)) - *stab_win) {
Err(ShelleyMA(MIRCertificateTooLateinEpoch))
} else {
let (ir_reserves, ir_treasury) = ds.inst_rewards.clone();
let (pot, ir_pot) = match mir.source {
Reserves => (acnt.reserves, ir_reserves.clone()),
Treasury => (acnt.treasury, ir_treasury.clone()),
};
let mut combined: HashMap<StakeCredential, Coin> = HashMap::new();
match &mir.target {
StakeCredentials(kvp) => {
let mut kvv: Vec<(StakeCredential, u64)> = // TODO: Err if the value is negative
kvp.iter().map(|kv| (kv.clone().0, kv.clone().1 as u64)).collect();
kvv.extend(ir_pot);
for (key, value) in kvv {
combined.insert(key, value);
}
}
_ => (),
}
if combined.iter().map(|kv| kv.1).sum::<u64>() > pot {
return Err(ShelleyMA(InsufficientForInstantaneousRewards));
} else {
ds.inst_rewards = match mir.source {
Reserves => (combined, ir_reserves),
Treasury => (ir_treasury, combined),
}
};
Ok(())
}
}
#[inline]
// Called just `epoch` in specs
fn to_epoch(genesis: &GenesisValues, slot: &Slot) -> Epoch {
genesis.absolute_slot_to_relative(*slot).0
}
#[inline]
// CamelCase in specs
fn first_slot(genesis: &GenesisValues, epoch: &Epoch) -> Slot {
genesis.relative_slot_to_absolute(*epoch, 0)
}
fn check_native_scripts(
vkey_wits: &Vec<VKeyWitness>, // changed from alonzo
native_scripts: &Vec<NativeScript>,
low_bnd: &Option<u64>,
upp_bnd: &Option<u64>,
) -> ValidationResult {
for native_script in native_scripts {
if !eval_native_script(vkey_wits, native_script, low_bnd, upp_bnd) {
return Err(ShelleyMA(ScriptDenial));
}
}
Ok(())
}
fn eval_native_script(
vkey_wits: &Vec<VKeyWitness>, // changed from alonzo
native_script: &NativeScript,
low_bnd: &Option<u64>,
upp_bnd: &Option<u64>,
) -> bool {
match native_script {
NativeScript::ScriptAll(scripts) => scripts
.iter()
.all(|scr| eval_native_script(vkey_wits, scr, low_bnd, upp_bnd)),
NativeScript::ScriptAny(scripts) => scripts
.iter()
.any(|scr| eval_native_script(vkey_wits, scr, low_bnd, upp_bnd)),
NativeScript::ScriptPubkey(hash) => vkey_wits
.iter()
.any(|vkey_wit| PallasHasher::<224>::hash(&vkey_wit.vkey.clone()) == *hash),
NativeScript::ScriptNOfK(val, scripts) => {
let count = scripts
.iter()
.map(|scr| eval_native_script(vkey_wits, scr, low_bnd, upp_bnd))
.fold(0, |x, y| x + y as u32);
count >= *val
}
NativeScript::InvalidBefore(val) => {
match low_bnd {
Some(time) => val >= time,
None => false, // as per mary-ledger.pdf, p.20
}
}
NativeScript::InvalidHereafter(val) => {
match upp_bnd {
Some(time) => val <= time,
None => false, // as per mary-ledger.pdf, p.20
}
}
}
}

View file

@ -12,12 +12,14 @@ use pallas_codec::{
use pallas_crypto::key::ed25519::{PublicKey, Signature};
use pallas_primitives::{
alonzo::{
AssetName, AuxiliaryData, Coin, MintedTx as AlonzoMintedTx, Multiasset, NativeScript,
NetworkId, PlutusScript, PolicyId, VKeyWitness, Value,
AddrKeyhash, AssetName, AuxiliaryData, Coin, Epoch, GenesisDelegateHash, Genesishash,
MintedTx as AlonzoMintedTx, Multiasset, NativeScript, NetworkId, PlutusScript, PolicyId,
PoolKeyhash, PoolMetadata, Relay, RewardAccount, StakeCredential, TransactionIndex,
UnitInterval, VKeyWitness, Value, VrfKeyhash,
},
babbage::{MintedTx as BabbageMintedTx, PlutusV2Script},
};
use pallas_traverse::{MultiEraInput, MultiEraOutput};
use pallas_traverse::{time::Slot, MultiEraInput, MultiEraOutput};
use std::collections::HashMap;
use std::ops::Deref;
pub use validation::*;
@ -101,6 +103,10 @@ pub fn lovelace_diff_or_fail(
}
pub fn multi_assets_are_equal(fma: &Multiasset<Coin>, sma: &Multiasset<Coin>) -> bool {
multi_asset_included(fma, sma) && multi_asset_included(sma, fma)
}
pub fn multi_asset_included(fma: &Multiasset<Coin>, sma: &Multiasset<Coin>) -> bool {
for (fpolicy, fassets) in fma.iter() {
match find_policy(sma, fpolicy) {
Some(sassets) => {
@ -158,7 +164,7 @@ fn coerce_to_coin(
err: &ValidationError,
) -> Result<Multiasset<Coin>, ValidationError> {
let mut res: Vec<(PolicyId, KeyValuePairs<AssetName, Coin>)> = Vec::new();
for (policy, assets) in value.clone().to_vec().iter() {
for (policy, assets) in value.iter() {
let mut aa: Vec<(AssetName, Coin)> = Vec::new();
for (asset_name, amount) in assets.clone().to_vec().iter() {
if *amount < 0 {
@ -342,3 +348,58 @@ pub fn compute_plutus_v2_script_hash(script: &PlutusV2Script) -> PolicyId {
payload.insert(0, 2);
pallas_crypto::hash::Hasher::<224>::hash(&payload)
}
pub type CertificateIndex = u32;
#[derive(PartialEq, Eq, Hash, Clone)]
pub struct CertPointer {
pub slot: Slot,
pub tx_ix: TransactionIndex,
pub cert_ix: CertificateIndex,
}
pub type GenesisDelegation = HashMap<Genesishash, (GenesisDelegateHash, VrfKeyhash)>;
pub type FutGenesisDelegation = HashMap<(Slot, Genesishash), (GenesisDelegateHash, VrfKeyhash)>;
pub type InstantaneousRewards = (
HashMap<StakeCredential, Coin>,
HashMap<StakeCredential, Coin>,
);
#[derive(Default, Clone)] // for testing
pub struct DState {
pub rewards: HashMap<StakeCredential, Coin>,
pub delegations: HashMap<StakeCredential, PoolKeyhash>,
pub ptrs: HashMap<CertPointer, StakeCredential>,
pub fut_gen_delegs: FutGenesisDelegation,
pub gen_delegs: GenesisDelegation,
pub inst_rewards: InstantaneousRewards,
}
// Essentially part of the `PoolRegistration` component of `Certificate` at alonzo/src/model.rs
#[derive(Clone, Debug)]
pub struct PoolParam {
pub vrf_keyhash: VrfKeyhash,
pub pledge: Coin,
pub cost: Coin,
pub margin: UnitInterval,
pub reward_account: RewardAccount, // FIXME: Should be a `StakeCredential`, or `Hash<_>`???
pub pool_owners: Vec<AddrKeyhash>,
pub relays: Vec<Relay>,
pub pool_metadata: Nullable<PoolMetadata>,
}
#[derive(Default, Clone)] // for testing
pub struct PState {
pub pool_params: HashMap<PoolKeyhash, PoolParam>,
pub fut_pool_params: HashMap<PoolKeyhash, PoolParam>,
pub retiring: HashMap<PoolKeyhash, Epoch>,
}
// Originally `DPState` in ShelleyMA specs, then updated to
// `CertState` in Haskell sources at Intersect (#3369).
#[non_exhaustive]
#[derive(Default, Clone)] // for testing
pub struct CertState {
pub pstate: PState,
pub dstate: DState,
}

View file

@ -178,12 +178,19 @@ pub struct ConwayProtParams {
pub minfee_refscript_cost_per_byte: UnitInterval,
}
#[derive(Default, Debug)]
pub struct AccountState {
pub treasury: Coin,
pub reserves: Coin,
}
#[derive(Debug)]
pub struct Environment {
pub prot_params: MultiEraProtocolParameters,
pub prot_magic: u32,
pub block_slot: u64,
pub network_id: u8,
pub acnt: Option<AccountState>,
}
impl Environment {
@ -202,4 +209,8 @@ impl Environment {
pub fn network_id(&self) -> &u8 {
&self.network_id
}
pub fn acnt(&self) -> &Option<AccountState> {
&self.acnt
}
}

View file

@ -4,6 +4,8 @@
#[non_exhaustive]
pub enum ValidationError {
TxAndProtParamsDiffer,
PParamsByronDoesntNeedAccountState,
EnvMissingAccountState,
UnknownProtParams,
Byron(ByronError),
ShelleyMA(ShelleyMAError),
@ -49,6 +51,19 @@ pub enum ShelleyMAError {
MissingScriptWitness,
WrongSignature,
MintingLacksPolicy,
KeyAlreadyRegistered,
KeyNotRegistered,
PointerInUse,
RewardsNotNull,
PoolAlreadyRegistered,
PoolNotRegistered,
PoolCostBelowMin,
DuplicateGenesisDelegate,
DuplicateGenesisVRF,
GenesisKeyNotInMapping,
InsufficientForInstantaneousRewards,
MIRCertificateTooLateinEpoch,
ScriptDenial,
}
#[derive(Debug, Clone)]

View file

@ -28,8 +28,22 @@ Note that, since phase-1 validations do not include the execution of native scri
List of positive unit tests:
- **successful_mainnet_shelley_tx** ([here](https://cexplorer.io/tx/50eba65e73c8c5f7b09f4ea28cf15dce169f3d1c322ca3deff03725f51518bb2) to see on Cardano explorer) is a simple Shelley transaction, with no native scripts or metadata.
- **successful_mainnet_shelley_tx_with_script** ([here](https://cexplorer.io/tx/4a3f86762383f1d228542d383ae7ac89cf75cf7ff84dec8148558ea92b0b92d0) to see on Cardano explorer) is a Shelley transaction with a native script and no metadata.
- **successful_mainnet_shelley_tx_with_changed_script** is the same as the
previous transaction but the script is modified from requiring all signatures
to requiring only one of them, and with one key-witness pair removed.
- **successful_mainnet_shelley_tx_with_metadata** ([here](https://cexplorer.io/tx/c220e20cc480df9ce7cd871df491d7390c6a004b9252cf20f45fc3c968535b4a) to see on Cardano Explorer) is a Shelley transaction with metadata and no native scripts.
- **successful_mainnet_mary_tx_with_minting** ([here](https://cexplorer.io/tx/b7b1046d1787ac6917f5bb5841e73b3f4bef8f0a6bf692d05ef18e1db9c3f519) to see on Cardano Explorer) is a Mary transaction that mints assets and has, therefore, a native script. It has no metadata.
- **successful_mainnet_mary_tx_with_pool_reg**
([here](https://cexplorer.io/tx/ce8ba608357e31695ce7be1a4a9875f43b3fd264f106e455e870714f149af925)
to see on Cardano explorer) is a Mary transaction with a pool registration.
- **successful_mainnet_mary_tx_with_stk_deleg**
([here](https://cexplorer.io/tx/cc6a92cc0f4ea326439bac6b18bc7b424470c508a99b9aebc8fafc027d906465)
to see on Cardano explorer) is a Mary transaction with a staking key
registration and delegation to the pool above.
- **successful_mainnet_allegra_tx_with_mir**
([here](https://cexplorer.io/tx/99f621beaacefc14ad8912b777422600e707f75bf619b2af20e918b0fe53f882)
to see on Cardano explorer) is a Mary transaction moving instantaneous
rewards, drawn from the Treasury.
List of negative unit tests:
- **empty_ins** takes successful_mainnet_shelley_tx and removes its input.
@ -45,6 +59,16 @@ List of negative unit tests:
- **missing_vk_witness** takes successful_mainnet_shelley_tx and removes the verification-key witness associated to one of its inputs.
- **vk_witness_changed** takes successful_mainnet_shelley_tx and modifies the verification-key witness associated to one of its inputs.
- **missing_native_script_witness** takes successful_mainnet_shelley_tx_with_script and removes the native script associated to one of its inputs.
- **missing_signature_native_script** takes successful_mainnet_shelley_tx but
one verification-key witness is removed (the same one of
successful_mainnet_shelley_tx_with_changed_script).
- **unregistered_pool** takes successful_mainnet_mary_tx_with_stk_deleg,
but the pool to which the delegation occurs is not registered.
- **delegation_before_registration** takes
successful_mainnet_mary_tx_with_stk_deleg and flips the order of the
certificates (stake registration and delegation).
- **too_late_for_mir** takes successful_mainnet_allegra_tx_with_mir but the slot
is advanced to a later moment.
### Alonzo
*pallas-applying/tests/alonzo.rs* contains multiple unit tests for validation in the Alonzo era.

View file

@ -5,9 +5,10 @@ use common::*;
use pallas_addresses::{Address, Network, ShelleyAddress, ShelleyPaymentPart};
use pallas_applying::{
utils::{
AlonzoError, AlonzoProtParams, Environment, MultiEraProtocolParameters, ValidationError::*,
AccountState, AlonzoError, AlonzoProtParams, Environment, MultiEraProtocolParameters,
ValidationError::*,
},
validate, UTxOs,
validate_txs, CertState, UTxOs,
};
use pallas_codec::{
minicbor::{
@ -43,13 +44,21 @@ mod alonzo_tests {
None,
)],
);
let acnt = AccountState {
treasury: 261_254_564_000_000,
reserves: 0,
};
let env: Environment = Environment {
prot_params: MultiEraProtocolParameters::Alonzo(mk_params_epoch_334()),
prot_magic: 764824073,
block_slot: 44237276,
network_id: 1,
acnt: Some(acnt),
};
match validate(&metx, &utxos, &env) {
let mut cert_state: CertState = CertState::default();
match validate_txs(&[metx], &env, &utxos, &mut cert_state) {
Ok(()) => (),
Err(err) => panic!("Unexpected error ({:?})", err),
}
@ -143,13 +152,21 @@ mod alonzo_tests {
None,
)],
);
let acnt = AccountState {
treasury: 261_254_564_000_000,
reserves: 0,
};
let env: Environment = Environment {
prot_params: MultiEraProtocolParameters::Alonzo(mk_params_epoch_300()),
prot_magic: 764824073,
block_slot: 58924928,
network_id: 1,
acnt: Some(acnt),
};
match validate(&metx, &utxos, &env) {
let mut cert_state: CertState = CertState::default();
match validate_txs(&[metx], &env, &utxos, &mut cert_state) {
Ok(()) => (),
Err(err) => panic!("Unexpected error ({:?})", err),
}
@ -170,13 +187,21 @@ mod alonzo_tests {
None,
)],
);
let acnt = AccountState {
treasury: 261_254_564_000_000,
reserves: 0,
};
let env: Environment = Environment {
prot_params: MultiEraProtocolParameters::Alonzo(mk_params_epoch_300()),
prot_magic: 764824073,
block_slot: 6447035,
network_id: 1,
acnt: Some(acnt),
};
match validate(&metx, &utxos, &env) {
let mut cert_state: CertState = CertState::default();
match validate_txs(&[metx], &env, &utxos, &mut cert_state) {
Ok(()) => (),
Err(err) => panic!("Unexpected error ({:?})", err),
}
@ -197,13 +222,21 @@ mod alonzo_tests {
None,
)],
);
let acnt = AccountState {
treasury: 261_254_564_000_000,
reserves: 0,
};
let env: Environment = Environment {
prot_params: MultiEraProtocolParameters::Alonzo(mk_params_epoch_300()),
prot_magic: 764824073,
block_slot: 6447038,
network_id: 1,
acnt: Some(acnt),
};
match validate(&metx, &utxos, &env) {
let mut cert_state: CertState = CertState::default();
match validate_txs(&[metx], &env, &utxos, &mut cert_state) {
Ok(()) => (),
Err(err) => panic!("Unexpected error ({:?})", err),
}
@ -229,13 +262,21 @@ mod alonzo_tests {
mtx.transaction_body =
Decode::decode(&mut Decoder::new(tx_buf.as_slice()), &mut ()).unwrap();
let metx: MultiEraTx = MultiEraTx::from_alonzo_compatible(&mtx, Era::Alonzo);
let acnt = AccountState {
treasury: 261_254_564_000_000,
reserves: 0,
};
let env: Environment = Environment {
prot_params: MultiEraProtocolParameters::Alonzo(mk_params_epoch_334()),
prot_magic: 764824073,
block_slot: 44237276,
network_id: 1,
acnt: Some(acnt),
};
match validate(&metx, &utxos, &env) {
let mut cert_state: CertState = CertState::default();
match validate_txs(&[metx], &env, &utxos, &mut cert_state) {
Ok(()) => panic!("Inputs set should not be empty"),
Err(err) => match err {
Alonzo(AlonzoError::TxInsEmpty) => (),
@ -252,13 +293,21 @@ mod alonzo_tests {
let mtx: MintedTx = minted_tx_from_cbor(&cbor_bytes);
let utxos: UTxOs = UTxOs::new();
let metx: MultiEraTx = MultiEraTx::from_alonzo_compatible(&mtx, Era::Alonzo);
let acnt = AccountState {
treasury: 261_254_564_000_000,
reserves: 0,
};
let env: Environment = Environment {
prot_params: MultiEraProtocolParameters::Alonzo(mk_params_epoch_334()),
prot_magic: 764824073,
block_slot: 44237276,
network_id: 1,
acnt: Some(acnt),
};
match validate(&metx, &utxos, &env) {
let mut cert_state: CertState = CertState::default();
match validate_txs(&[metx], &env, &utxos, &mut cert_state) {
Ok(()) => panic!("All inputs should be within the UTxO set"),
Err(err) => match err {
Alonzo(AlonzoError::InputNotInUTxO) => (),
@ -288,13 +337,21 @@ mod alonzo_tests {
mtx.transaction_body =
Decode::decode(&mut Decoder::new(tx_buf.as_slice()), &mut ()).unwrap();
let metx: MultiEraTx = MultiEraTx::from_alonzo_compatible(&mtx, Era::Alonzo);
let acnt = AccountState {
treasury: 261_254_564_000_000,
reserves: 0,
};
let env: Environment = Environment {
prot_params: MultiEraProtocolParameters::Alonzo(mk_params_epoch_334()),
prot_magic: 764824073,
block_slot: 44237276,
network_id: 1,
acnt: Some(acnt),
};
match validate(&metx, &utxos, &env) {
let mut cert_state: CertState = CertState::default();
match validate_txs(&[metx], &env, &utxos, &mut cert_state) {
Ok(()) => panic!("Validity interval lower bound should have been reached"),
Err(err) => match err {
Alonzo(AlonzoError::BlockPrecedesValInt) => (),
@ -324,13 +381,21 @@ mod alonzo_tests {
mtx.transaction_body =
Decode::decode(&mut Decoder::new(tx_buf.as_slice()), &mut ()).unwrap();
let metx: MultiEraTx = MultiEraTx::from_alonzo_compatible(&mtx, Era::Alonzo);
let acnt = AccountState {
treasury: 261_254_564_000_000,
reserves: 0,
};
let env: Environment = Environment {
prot_params: MultiEraProtocolParameters::Alonzo(mk_params_epoch_334()),
prot_magic: 764824073,
block_slot: 44237276,
network_id: 1,
acnt: Some(acnt),
};
match validate(&metx, &utxos, &env) {
let mut cert_state: CertState = CertState::default();
match validate_txs(&[metx], &env, &utxos, &mut cert_state) {
Ok(()) => panic!("Validity interval upper bound should not have been surpassed"),
Err(err) => match err {
Alonzo(AlonzoError::BlockExceedsValInt) => (),
@ -356,13 +421,21 @@ mod alonzo_tests {
);
let mut alonzo_prot_params: AlonzoProtParams = mk_params_epoch_334();
alonzo_prot_params.minfee_a = 79; // This value was 44 during Alonzo on mainnet
let acnt = AccountState {
treasury: 261_254_564_000_000,
reserves: 0,
};
let env: Environment = Environment {
prot_params: MultiEraProtocolParameters::Alonzo(alonzo_prot_params),
prot_magic: 764824073,
block_slot: 44237276,
network_id: 1,
acnt: Some(acnt),
};
match validate(&metx, &utxos, &env) {
let mut cert_state: CertState = CertState::default();
match validate_txs(&[metx], &env, &utxos, &mut cert_state) {
Ok(()) => panic!("Fee should not be below minimum"),
Err(err) => match err {
Alonzo(AlonzoError::FeeBelowMin) => (),
@ -465,13 +538,21 @@ mod alonzo_tests {
mtx.transaction_body =
Decode::decode(&mut Decoder::new(tx_buf.as_slice()), &mut ()).unwrap();
let metx: MultiEraTx = MultiEraTx::from_alonzo_compatible(&mtx, Era::Alonzo);
let acnt = AccountState {
treasury: 261_254_564_000_000,
reserves: 0,
};
let env: Environment = Environment {
prot_params: MultiEraProtocolParameters::Alonzo(mk_params_epoch_300()),
prot_magic: 764824073,
block_slot: 58924928,
network_id: 1,
acnt: Some(acnt),
};
match validate(&metx, &utxos, &env) {
let mut cert_state: CertState = CertState::default();
match validate_txs(&[metx], &env, &utxos, &mut cert_state) {
Ok(()) => panic!("No collateral inputs"),
Err(err) => match err {
Alonzo(AlonzoError::CollateralMissing) => (),
@ -571,13 +652,20 @@ mod alonzo_tests {
);
let mut alonzo_prot_params: AlonzoProtParams = mk_params_epoch_300();
alonzo_prot_params.max_collateral_inputs = 0; // This value was 3 during Alonzo on mainnet
let acnt = AccountState {
treasury: 261_254_564_000_000,
reserves: 0,
};
let env: Environment = Environment {
prot_params: MultiEraProtocolParameters::Alonzo(alonzo_prot_params),
prot_magic: 764824073,
block_slot: 58924928,
network_id: 1,
acnt: Some(acnt),
};
match validate(&metx, &utxos, &env) {
let mut cert_state: CertState = CertState::default();
match validate_txs(&[metx], &env, &utxos, &mut cert_state) {
Ok(()) => panic!("Number of collateral inputs should be within limits"),
Err(err) => match err {
Alonzo(AlonzoError::TooManyCollaterals) => (),
@ -696,13 +784,20 @@ mod alonzo_tests {
);
utxos.insert(multi_era_in, multi_era_out);
let metx: MultiEraTx = MultiEraTx::from_alonzo_compatible(&mtx, Era::Alonzo);
let acnt = AccountState {
treasury: 261_254_564_000_000,
reserves: 0,
};
let env: Environment = Environment {
prot_params: MultiEraProtocolParameters::Alonzo(mk_params_epoch_300()),
prot_magic: 764824073,
block_slot: 58924928,
network_id: 1,
acnt: Some(acnt),
};
match validate(&metx, &utxos, &env) {
let mut cert_state: CertState = CertState::default();
match validate_txs(&[metx], &env, &utxos, &mut cert_state) {
Ok(()) => panic!("Collateral inputs should be verification-key locked"),
Err(err) => match err {
Alonzo(AlonzoError::CollateralNotVKeyLocked) => (),
@ -810,13 +905,20 @@ mod alonzo_tests {
None,
)],
);
let acnt = AccountState {
treasury: 261_254_564_000_000,
reserves: 0,
};
let env: Environment = Environment {
prot_params: MultiEraProtocolParameters::Alonzo(mk_params_epoch_300()),
prot_magic: 764824073,
block_slot: 58924928,
network_id: 1,
acnt: Some(acnt),
};
match validate(&metx, &utxos, &env) {
let mut cert_state: CertState = CertState::default();
match validate_txs(&[metx], &env, &utxos, &mut cert_state) {
Ok(()) => panic!("Collateral inputs should contain only lovelace"),
Err(err) => match err {
Alonzo(AlonzoError::NonLovelaceCollateral) => (),
@ -915,13 +1017,20 @@ mod alonzo_tests {
);
let mut alonzo_prot_params: AlonzoProtParams = mk_params_epoch_300();
alonzo_prot_params.collateral_percentage = 700; // This was 150 during Alonzo on mainnet.
let acnt = AccountState {
treasury: 261_254_564_000_000,
reserves: 0,
};
let env: Environment = Environment {
prot_params: MultiEraProtocolParameters::Alonzo(alonzo_prot_params),
prot_magic: 764824073,
block_slot: 58924928,
network_id: 1,
acnt: Some(acnt),
};
match validate(&metx, &utxos, &env) {
let mut cert_state: CertState = CertState::default();
match validate_txs(&[metx], &env, &utxos, &mut cert_state) {
Ok(()) => panic!("Collateral inputs should contain the minimum lovelace"),
Err(err) => match err {
Alonzo(AlonzoError::CollateralMinLovelace) => (),
@ -951,13 +1060,20 @@ mod alonzo_tests {
mtx.transaction_body =
Decode::decode(&mut Decoder::new(tx_buf.as_slice()), &mut ()).unwrap();
let metx: MultiEraTx = MultiEraTx::from_alonzo_compatible(&mtx, Era::Alonzo);
let acnt = AccountState {
treasury: 261_254_564_000_000,
reserves: 0,
};
let env: Environment = Environment {
prot_params: MultiEraProtocolParameters::Alonzo(mk_params_epoch_334()),
prot_magic: 764824073,
block_slot: 44237276,
network_id: 1,
acnt: Some(acnt),
};
match validate(&metx, &utxos, &env) {
let mut cert_state: CertState = CertState::default();
match validate_txs(&[metx], &env, &utxos, &mut cert_state) {
Ok(()) => panic!("Preservation of value does not hold"),
Err(err) => match err {
Alonzo(AlonzoError::PreservationOfValue) => (),
@ -1007,13 +1123,20 @@ mod alonzo_tests {
None,
)],
);
let acnt = AccountState {
treasury: 261_254_564_000_000,
reserves: 0,
};
let env: Environment = Environment {
prot_params: MultiEraProtocolParameters::Alonzo(mk_params_epoch_334()),
prot_magic: 764824073,
block_slot: 44237276,
network_id: 1,
acnt: Some(acnt),
};
match validate(&metx, &utxos, &env) {
let mut cert_state: CertState = CertState::default();
match validate_txs(&[metx], &env, &utxos, &mut cert_state) {
Ok(()) => panic!("Output network ID should match environment network ID"),
Err(err) => match err {
Alonzo(AlonzoError::OutputWrongNetworkID) => (),
@ -1045,13 +1168,20 @@ mod alonzo_tests {
None,
)],
);
let acnt = AccountState {
treasury: 261_254_564_000_000,
reserves: 0,
};
let env: Environment = Environment {
prot_params: MultiEraProtocolParameters::Alonzo(mk_params_epoch_334()),
prot_magic: 764824073,
block_slot: 44237276,
network_id: 1,
acnt: Some(acnt),
};
match validate(&metx, &utxos, &env) {
let mut cert_state: CertState = CertState::default();
match validate_txs(&[metx], &env, &utxos, &mut cert_state) {
Ok(()) => panic!("Transaction network ID should match environment network ID"),
Err(err) => match err {
Alonzo(AlonzoError::TxWrongNetworkID) => (),
@ -1151,13 +1281,20 @@ mod alonzo_tests {
let mut alonzo_prot_params: AlonzoProtParams = mk_params_epoch_300();
alonzo_prot_params.max_tx_ex_units.mem = 4649575; // This is 1 lower than that of the transaction
alonzo_prot_params.max_tx_ex_units.steps = 1765246503; // This is 1 lower than that of the transaction
let acnt = AccountState {
treasury: 261_254_564_000_000,
reserves: 0,
};
let env: Environment = Environment {
prot_params: MultiEraProtocolParameters::Alonzo(alonzo_prot_params),
prot_magic: 764824073,
block_slot: 58924928,
network_id: 1,
acnt: Some(acnt),
};
match validate(&metx, &utxos, &env) {
let mut cert_state: CertState = CertState::default();
match validate_txs(&[metx], &env, &utxos, &mut cert_state) {
Ok(()) => panic!("Transaction ex units should be below maximum"),
Err(err) => match err {
Alonzo(AlonzoError::TxExUnitsExceeded) => (),
@ -1184,13 +1321,20 @@ mod alonzo_tests {
);
let mut alonzo_prot_params: AlonzoProtParams = mk_params_epoch_334();
alonzo_prot_params.max_transaction_size = 158; // 1 byte less than the size of the tx
let acnt = AccountState {
treasury: 261_254_564_000_000,
reserves: 0,
};
let env: Environment = Environment {
prot_params: MultiEraProtocolParameters::Alonzo(alonzo_prot_params),
prot_magic: 764824073,
block_slot: 44237276,
network_id: 1,
acnt: Some(acnt),
};
match validate(&metx, &utxos, &env) {
let mut cert_state: CertState = CertState::default();
match validate_txs(&[metx], &env, &utxos, &mut cert_state) {
Ok(()) => panic!(
"Transaction size should not exceed the maximum allowed by the protocol parameter"
),
@ -1301,13 +1445,20 @@ mod alonzo_tests {
mtx.transaction_body =
Decode::decode(&mut Decoder::new(tx_buf.as_slice()), &mut ()).unwrap();
let metx: MultiEraTx = MultiEraTx::from_alonzo_compatible(&mtx, Era::Alonzo);
let acnt = AccountState {
treasury: 261_254_564_000_000,
reserves: 0,
};
let env: Environment = Environment {
prot_params: MultiEraProtocolParameters::Alonzo(mk_params_epoch_300()),
prot_magic: 764824073,
block_slot: 58924928,
network_id: 1,
acnt: Some(acnt),
};
match validate(&metx, &utxos, &env) {
let mut cert_state: CertState = CertState::default();
match validate_txs(&[metx], &env, &utxos, &mut cert_state) {
Ok(()) => panic!("All required signers should have signed the transaction"),
Err(err) => match err {
Alonzo(AlonzoError::ReqSignerMissing) => (),
@ -1337,13 +1488,20 @@ mod alonzo_tests {
mtx.transaction_witness_set =
Decode::decode(&mut Decoder::new(tx_buf.as_slice()), &mut ()).unwrap();
let metx: MultiEraTx = MultiEraTx::from_alonzo_compatible(&mtx, Era::Alonzo);
let acnt = AccountState {
treasury: 261_254_564_000_000,
reserves: 0,
};
let env: Environment = Environment {
prot_params: MultiEraProtocolParameters::Alonzo(mk_params_epoch_334()),
prot_magic: 764824073,
block_slot: 44237276,
network_id: 1,
acnt: Some(acnt),
};
match validate(&metx, &utxos, &env) {
let mut cert_state: CertState = CertState::default();
match validate_txs(&[metx], &env, &utxos, &mut cert_state) {
Ok(()) => panic!("Missing verification key witness"),
Err(err) => match err {
Alonzo(AlonzoError::VKWitnessMissing) => (),
@ -1380,13 +1538,20 @@ mod alonzo_tests {
mtx.transaction_witness_set =
Decode::decode(&mut Decoder::new(tx_buf.as_slice()), &mut ()).unwrap();
let metx: MultiEraTx = MultiEraTx::from_alonzo_compatible(&mtx, Era::Alonzo);
let acnt = AccountState {
treasury: 261_254_564_000_000,
reserves: 0,
};
let env: Environment = Environment {
prot_params: MultiEraProtocolParameters::Alonzo(mk_params_epoch_334()),
prot_magic: 764824073,
block_slot: 44237276,
network_id: 1,
acnt: Some(acnt),
};
match validate(&metx, &utxos, &env) {
let mut cert_state: CertState = CertState::default();
match validate_txs(&[metx], &env, &utxos, &mut cert_state) {
Ok(()) => panic!("Witness signature should verify the transaction"),
Err(err) => match err {
Alonzo(AlonzoError::VKWrongSignature) => (),
@ -1489,13 +1654,20 @@ mod alonzo_tests {
mtx.transaction_witness_set =
Decode::decode(&mut Decoder::new(tx_buf.as_slice()), &mut ()).unwrap();
let metx: MultiEraTx = MultiEraTx::from_alonzo_compatible(&mtx, Era::Alonzo);
let acnt = AccountState {
treasury: 261_254_564_000_000,
reserves: 0,
};
let env: Environment = Environment {
prot_params: MultiEraProtocolParameters::Alonzo(mk_params_epoch_300()),
prot_magic: 764824073,
block_slot: 58924928,
network_id: 1,
acnt: Some(acnt),
};
match validate(&metx, &utxos, &env) {
let mut cert_state: CertState = CertState::default();
match validate_txs(&[metx], &env, &utxos, &mut cert_state) {
Ok(()) => panic!("Missing Plutus script"),
Err(err) => match err {
Alonzo(AlonzoError::ScriptWitnessMissing) => (),
@ -1606,13 +1778,20 @@ mod alonzo_tests {
mtx.transaction_witness_set =
Decode::decode(&mut Decoder::new(tx_buf.as_slice()), &mut ()).unwrap();
let metx: MultiEraTx = MultiEraTx::from_alonzo_compatible(&mtx, Era::Alonzo);
let acnt = AccountState {
treasury: 261_254_564_000_000,
reserves: 0,
};
let env: Environment = Environment {
prot_params: MultiEraProtocolParameters::Alonzo(mk_params_epoch_300()),
prot_magic: 764824073,
block_slot: 58924928,
network_id: 1,
acnt: Some(acnt),
};
match validate(&metx, &utxos, &env) {
let mut cert_state: CertState = CertState::default();
match validate_txs(&[metx], &env, &utxos, &mut cert_state) {
Ok(()) => panic!("Unneeded Plutus script"),
Err(err) => match err {
Alonzo(AlonzoError::UnneededNativeScript) => (),
@ -1642,13 +1821,20 @@ mod alonzo_tests {
mtx.transaction_witness_set =
Decode::decode(&mut Decoder::new(tx_buf.as_slice()), &mut ()).unwrap();
let metx: MultiEraTx = MultiEraTx::from_alonzo_compatible(&mtx, Era::Alonzo);
let acnt = AccountState {
treasury: 261_254_564_000_000,
reserves: 0,
};
let env: Environment = Environment {
prot_params: MultiEraProtocolParameters::Alonzo(mk_params_epoch_300()),
prot_magic: 764824073,
block_slot: 6447035,
network_id: 1,
acnt: Some(acnt),
};
match validate(&metx, &utxos, &env) {
let mut cert_state: CertState = CertState::default();
match validate_txs(&[metx], &env, &utxos, &mut cert_state) {
Ok(()) => panic!("Minting policy is not supported by a matching native script"),
Err(err) => match err {
Alonzo(AlonzoError::MintingLacksPolicy) => (),
@ -1751,13 +1937,20 @@ mod alonzo_tests {
mtx.transaction_witness_set =
Decode::decode(&mut Decoder::new(tx_buf.as_slice()), &mut ()).unwrap();
let metx: MultiEraTx = MultiEraTx::from_alonzo_compatible(&mtx, Era::Alonzo);
let acnt = AccountState {
treasury: 261_254_564_000_000,
reserves: 0,
};
let env: Environment = Environment {
prot_params: MultiEraProtocolParameters::Alonzo(mk_params_epoch_300()),
prot_magic: 764824073,
block_slot: 58924928,
network_id: 1,
acnt: Some(acnt),
};
match validate(&metx, &utxos, &env) {
let mut cert_state: CertState = CertState::default();
match validate_txs(&[metx], &env, &utxos, &mut cert_state) {
Ok(()) => panic!("Missing datum"),
Err(err) => match err {
Alonzo(AlonzoError::DatumMissing) => (),
@ -1866,13 +2059,20 @@ mod alonzo_tests {
mtx.transaction_witness_set =
Decode::decode(&mut Decoder::new(tx_buf.as_slice()), &mut ()).unwrap();
let metx: MultiEraTx = MultiEraTx::from_alonzo_compatible(&mtx, Era::Alonzo);
let acnt = AccountState {
treasury: 261_254_564_000_000,
reserves: 0,
};
let env: Environment = Environment {
prot_params: MultiEraProtocolParameters::Alonzo(mk_params_epoch_300()),
prot_magic: 764824073,
block_slot: 58924928,
network_id: 1,
acnt: Some(acnt),
};
match validate(&metx, &utxos, &env) {
let mut cert_state: CertState = CertState::default();
match validate_txs(&[metx], &env, &utxos, &mut cert_state) {
Ok(()) => panic!("Unneeded datum"),
Err(err) => match err {
Alonzo(AlonzoError::UnneededDatum) => (),
@ -1982,13 +2182,20 @@ mod alonzo_tests {
mtx.transaction_witness_set =
Decode::decode(&mut Decoder::new(tx_buf.as_slice()), &mut ()).unwrap();
let metx: MultiEraTx = MultiEraTx::from_alonzo_compatible(&mtx, Era::Alonzo);
let acnt = AccountState {
treasury: 261_254_564_000_000,
reserves: 0,
};
let env: Environment = Environment {
prot_params: MultiEraProtocolParameters::Alonzo(mk_params_epoch_300()),
prot_magic: 764824073,
block_slot: 58924928,
network_id: 1,
acnt: Some(acnt),
};
match validate(&metx, &utxos, &env) {
let mut cert_state: CertState = CertState::default();
match validate_txs(&[metx], &env, &utxos, &mut cert_state) {
Ok(()) => panic!("Unneeded redeemer"),
Err(err) => match err {
Alonzo(AlonzoError::UnneededRedeemer) => (),
@ -2091,13 +2298,20 @@ mod alonzo_tests {
mtx.transaction_witness_set =
Decode::decode(&mut Decoder::new(tx_buf.as_slice()), &mut ()).unwrap();
let metx: MultiEraTx = MultiEraTx::from_alonzo_compatible(&mtx, Era::Alonzo);
let acnt = AccountState {
treasury: 261_254_564_000_000,
reserves: 0,
};
let env: Environment = Environment {
prot_params: MultiEraProtocolParameters::Alonzo(mk_params_epoch_300()),
prot_magic: 764824073,
block_slot: 58924928,
network_id: 1,
acnt: Some(acnt),
};
match validate(&metx, &utxos, &env) {
let mut cert_state: CertState = CertState::default();
match validate_txs(&[metx], &env, &utxos, &mut cert_state) {
Ok(()) => panic!("Redeemer missing"),
Err(err) => match err {
Alonzo(AlonzoError::RedeemerMissing) => (),
@ -2122,13 +2336,20 @@ mod alonzo_tests {
None,
)],
);
let acnt = AccountState {
treasury: 261_254_564_000_000,
reserves: 0,
};
let env: Environment = Environment {
prot_params: MultiEraProtocolParameters::Alonzo(mk_params_epoch_300()),
prot_magic: 764824073,
block_slot: 6447038,
network_id: 1,
acnt: Some(acnt),
};
match validate(&metx, &utxos, &env) {
let mut cert_state: CertState = CertState::default();
match validate_txs(&[metx], &env, &utxos, &mut cert_state) {
Ok(()) => panic!("Transaction auxiliary data removed"),
Err(err) => match err {
Alonzo(AlonzoError::MetadataHash) => (),
@ -2154,13 +2375,20 @@ mod alonzo_tests {
);
let mut alonzo_prot_params: AlonzoProtParams = mk_params_epoch_334();
alonzo_prot_params.ada_per_utxo_byte = 10000000; // This was 34482 during Alonzo on mainnet.
let acnt = AccountState {
treasury: 261_254_564_000_000,
reserves: 0,
};
let env: Environment = Environment {
prot_params: MultiEraProtocolParameters::Alonzo(alonzo_prot_params),
prot_magic: 764824073,
block_slot: 44237276,
network_id: 1,
acnt: Some(acnt),
};
match validate(&metx, &utxos, &env) {
let mut cert_state: CertState = CertState::default();
match validate_txs(&[metx], &env, &utxos, &mut cert_state) {
Ok(()) => panic!("Output minimum lovelace is unreached"),
Err(err) => match err {
Alonzo(AlonzoError::MinLovelaceUnreached) => (),
@ -2186,13 +2414,20 @@ mod alonzo_tests {
);
let mut alonzo_prot_params: AlonzoProtParams = mk_params_epoch_334();
alonzo_prot_params.max_value_size = 0; // This was 5000 during Alonzo on mainnet
let acnt = AccountState {
treasury: 261_254_564_000_000,
reserves: 0,
};
let env: Environment = Environment {
prot_params: MultiEraProtocolParameters::Alonzo(alonzo_prot_params),
prot_magic: 764824073,
block_slot: 44237276,
network_id: 1,
acnt: Some(acnt),
};
match validate(&metx, &utxos, &env) {
let mut cert_state: CertState = CertState::default();
match validate_txs(&[metx], &env, &utxos, &mut cert_state) {
Ok(()) => panic!("Max value size exceeded"),
Err(err) => match err {
Alonzo(AlonzoError::MaxValSizeExceeded) => (),
@ -2299,13 +2534,20 @@ mod alonzo_tests {
mtx.transaction_witness_set =
Decode::decode(&mut Decoder::new(tx_witness_set_buf.as_slice()), &mut ()).unwrap();
let metx: MultiEraTx = MultiEraTx::from_alonzo_compatible(&mtx, Era::Alonzo);
let acnt = AccountState {
treasury: 261_254_564_000_000,
reserves: 0,
};
let env: Environment = Environment {
prot_params: MultiEraProtocolParameters::Alonzo(mk_params_epoch_300()),
prot_magic: 764824073,
block_slot: 58924928,
network_id: 1,
acnt: Some(acnt),
};
match validate(&metx, &utxos, &env) {
let mut cert_state: CertState = CertState::default();
match validate_txs(&[metx], &env, &utxos, &mut cert_state) {
Ok(()) => panic!("Wrong script integrity hash"),
Err(err) => match err {
Alonzo(AlonzoError::ScriptIntegrityHash) => (),

File diff suppressed because one or more lines are too long

View file

@ -3,9 +3,10 @@ pub mod common;
use common::{cbor_to_bytes, minted_tx_payload_from_cbor, mk_utxo_for_byron_tx};
use pallas_applying::{
utils::{
ByronError, ByronProtParams, Environment, MultiEraProtocolParameters, ValidationError::*,
ByronError, ByronProtParams, CertState, Environment, MultiEraProtocolParameters,
ValidationError::*,
},
validate, UTxOs,
validate_txs, UTxOs,
};
use pallas_codec::{
@ -59,8 +60,10 @@ mod byron_tests {
prot_magic: 764824073,
block_slot: 6341,
network_id: 1,
acnt: None,
};
match validate(&metx, &utxos, &env) {
let mut cert_state: CertState = CertState::default();
match validate_txs(&[metx], &env, &utxos, &mut cert_state) {
Ok(()) => (),
Err(err) => panic!("Unexpected error ({:?})", err),
}
@ -102,8 +105,10 @@ mod byron_tests {
prot_magic: 764824073,
block_slot: 3241381,
network_id: 1,
acnt: None,
};
match validate(&metx, &utxos, &env) {
let mut cert_state: CertState = CertState::default();
match validate_txs(&[metx], &env, &utxos, &mut cert_state) {
Ok(()) => (),
Err(err) => panic!("Unexpected error ({:?})", err),
}
@ -153,8 +158,10 @@ mod byron_tests {
prot_magic: 764824073,
block_slot: 3241381,
network_id: 1,
acnt: None,
};
match validate(&metx, &utxos, &env) {
let mut cert_state: CertState = CertState::default();
match validate_txs(&[metx], &env, &utxos, &mut cert_state) {
Ok(()) => panic!("Inputs set should not be empty"),
Err(err) => match err {
Byron(ByronError::TxInsEmpty) => (),
@ -207,8 +214,10 @@ mod byron_tests {
prot_magic: 764824073,
block_slot: 3241381,
network_id: 1,
acnt: None,
};
match validate(&metx, &utxos, &env) {
let mut cert_state: CertState = CertState::default();
match validate_txs(&[metx], &env, &utxos, &mut cert_state) {
Ok(()) => panic!("Outputs set should not be empty"),
Err(err) => match err {
Byron(ByronError::TxOutsEmpty) => (),
@ -246,8 +255,10 @@ mod byron_tests {
prot_magic: 764824073,
block_slot: 3241381,
network_id: 1,
acnt: None,
};
match validate(&metx, &utxos, &env) {
let mut cert_state: CertState = CertState::default();
match validate_txs(&[metx], &env, &utxos, &mut cert_state) {
Ok(()) => panic!("All inputs must be within the UTxO set"),
Err(err) => match err {
Byron(ByronError::InputNotInUTxO) => (),
@ -306,8 +317,10 @@ mod byron_tests {
prot_magic: 764824073,
block_slot: 3241381,
network_id: 1,
acnt: None,
};
match validate(&metx, &utxos, &env) {
let mut cert_state: CertState = CertState::default();
match validate_txs(&[metx], &env, &utxos, &mut cert_state) {
Ok(()) => panic!("All outputs must contain lovelace"),
Err(err) => match err {
Byron(ByronError::OutputWithoutLovelace) => (),
@ -351,8 +364,10 @@ mod byron_tests {
prot_magic: 764824073,
block_slot: 3241381,
network_id: 1,
acnt: None,
};
match validate(&metx, &utxos, &env) {
let mut cert_state: CertState = CertState::default();
match validate_txs(&[metx], &env, &utxos, &mut cert_state) {
Ok(()) => panic!("Fees should not be below minimum"),
Err(err) => match err {
Byron(ByronError::FeesBelowMin) => (),
@ -396,8 +411,10 @@ mod byron_tests {
prot_magic: 764824073,
block_slot: 3241381,
network_id: 1,
acnt: None,
};
match validate(&metx, &utxos, &env) {
let mut cert_state: CertState = CertState::default();
match validate_txs(&[metx], &env, &utxos, &mut cert_state) {
Ok(()) => panic!("Transaction size cannot exceed protocol limit"),
Err(err) => match err {
Byron(ByronError::MaxTxSizeExceeded) => (),
@ -449,8 +466,10 @@ mod byron_tests {
prot_magic: 764824073,
block_slot: 3241381,
network_id: 1,
acnt: None,
};
match validate(&metx, &utxos, &env) {
let mut cert_state: CertState = CertState::default();
match validate_txs(&[metx], &env, &utxos, &mut cert_state) {
Ok(()) => panic!("All inputs must have a witness signature"),
Err(err) => match err {
Byron(ByronError::MissingWitness) => (),
@ -511,8 +530,10 @@ mod byron_tests {
prot_magic: 764824073,
block_slot: 3241381,
network_id: 1,
acnt: None,
};
match validate(&metx, &utxos, &env) {
let mut cert_state: CertState = CertState::default();
match validate_txs(&[metx], &env, &utxos, &mut cert_state) {
Ok(()) => panic!("Witness signature should verify the transaction"),
Err(err) => match err {
Byron(ByronError::WrongSignature) => (),

File diff suppressed because it is too large Load diff

View file

@ -413,7 +413,7 @@ pub type UnitInterval = RationalNumber;
pub type PositiveInterval = RationalNumber;
#[derive(Serialize, Deserialize, Debug, PartialEq, PartialOrd, Eq, Ord, Clone)]
#[derive(Serialize, Deserialize, Debug, PartialEq, PartialOrd, Eq, Ord, Clone, Hash)]
pub enum StakeCredential {
AddrKeyhash(AddrKeyhash),
Scripthash(Scripthash),

1
test_data/allegra1.tx Normal file

File diff suppressed because one or more lines are too long

1
test_data/mary2.tx Normal file
View file

@ -0,0 +1 @@
84a500818258201dd22b2976374f9b8e6aa045ded141742fa5adc5184a505410fb9f343d14e407000181825839018e8f7a7073b8a95a4c1f1cf412b1042fca4945b89eb11754b3481b29fb2b631db76384f64dd94b47f97fc8c2a206764c17a1de7da2f70e831a3c0f17fa021a0002fce9031a0191f62f04828a03581c59ebe72ae96462018fbe04633100f90b3066688d85f00f3bd254707f58201efb798f239b9b02deb4636a3ab1962af43512595fcb82276e11971e684e49b71a3b9aca001a1443fd00d81e82011864581de1fb2b631db76384f64dd94b47f97fc8c2a206764c17a1de7da2f70e8381581cfb2b631db76384f64dd94b47f97fc8c2a206764c17a1de7da2f70e83818400190bb944c22614bbf682781c68747470733a2f2f6361726461706f6f6c2e636f6d2f612e6a736f6e582001f708549816c9a075ff96e9682c11a5f5c7f4e147862a663bdeece0716ab76e83028200581cfb2b631db76384f64dd94b47f97fc8c2a206764c17a1de7da2f70e83581c59ebe72ae96462018fbe04633100f90b3066688d85f00f3bd254707fa100838258205ac72fb72ac603eb4aa1be5b98af76f193e24924b4a7c877f9e9c401fb9cda385840bdbd394d90106cd033224caffbba57fc071a0e00087bc26aa07174ec42d26060259cb865cbd8ce0981897c1fb0ebc38678420b52731827e9cb00f5209649850b825820af5d32c73a976fcd7abed9fa9c85e128862bd870c8ebc51f602a053343fab8dc584032bb6013443435846521d42e6cf076f0769766ed366661d286fda0788622f603339afa20649123a04cc4266581e328a5f02f9eca866fa44e971a6d8ab7bb5c04825820bc08576470a74179757177311dd8bdae9e56a637d58ed5d50a737d2b03dd9e685840cf586f9bc2ef1684c396e047d9923ab94fa616aed013d5f7c7d3c730ec75f0cd6021a51bd9bdb6f91711d92e332b21fef2221809659b56d538debaaf0bc02b0ef5f6

1
test_data/mary3.tx Normal file
View file

@ -0,0 +1 @@
84a50081825820f8cad2b3e5a3744e096cbb3040ff6057560d3ec8d5444fda1efc669f9c2c7d30060181825839015d74deb638695cc12d11b7dfa50b2023b626287371674fecaa569b8039cf0461807b986a6477205e376dac280d7f150eb497025f67c497571a2549ae4f021a0002a8b1031a01bb2735048282008200581c39cf0461807b986a6477205e376dac280d7f150eb497025f67c4975783028200581c39cf0461807b986a6477205e376dac280d7f150eb497025f67c49757581c59ebe72ae96462018fbe04633100f90b3066688d85f00f3bd254707fa100828258208f9010e31a25735302c168f71416bae8d5cc8333067e06cc5d3c3e0b9d1d262058403719305623d25de0648e17cf041a30f28483d4e0354cdd1d12a3e9c13210327c736754049686e6f7315bb9c4b5bcc50cf140290452362e8a2beb66efcd1b2a02825820e7101958033ebca48dc8d71dee85d1a10c1e94460ec43275b1e82e2638a096b05840b6c16dadb85ae630aa0376b6fd26d103b33085a8b12011e515fa265d548431e65a8eb2c8091ed0bc3b364551eaba47ced495bdce4de758509539481ec123d70af5f6

1
test_data/shelley4.tx Normal file
View file

@ -0,0 +1 @@
84a40081825820e7db1f809fcc21d3dd108ced6218bf0f0cbb6a0f679f848ff1790b68d3a35872000181825839010c57a4aa08aaa7c42b45e4e9490151e2665dbb7d374e795ad5be5e4960562a0d213c675c2b84ee0e34eb377d4abbe82a4c256a0708baac251a0016e360021a0007a120031a010c59f8a200838258205df1be8b0071123c982a94c19c0c06485dbe9271e4381e8cf4fc2ed554ac133f5840c271a9d652ae95e6a8a5117c5026369235182da7e4fe040b02465089e5e2caf05063cf8b61601e6f6074c03f700bacaadcd62483c48d66a5f8d418a7c27c4b01825820403171966fadb1ce9b26852cb74018a04bc031a4aee92be39702b18efd75e058584039395858906ec9ab7540e79022b25a1f3bdfece09f9e2f36254eb5abe625b72dbd8179ece4c9fc1d537afce95b67d8095d29e1f3c50de4ecc30fd67e1ba440048258206311da054c5dfa3ac53c9fc3be859bd322f0712f7e093596fa5f6de031d95acd58403d7deba60f80a03f5bf172c1699f07ecef557d9509551cbe8d2b9000e903c3e3f60bb127c9c0b4cd5df84c01791b98e10fe19209088d0b085c74702b25914a0d01818202838200581ca96da581c39549aeda81f539ac3940ac0cb53657e774ca7e68f15ed98200581cccfcb3fed004562be1354c837a4a4b9f4b1c2b6705229efeedd12d4d8200581c74fcd61aecebe36aa6b6cd4314027282fa4b41c3ce8af17d9b77d0d1f5f6