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 87e47de094
commit 7c7a3c25ab
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)?;
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(())
}
if !values_are_equal(&input, &output) {
return Err(ShelleyMA(PreservationOfValue));
}
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)]