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:
parent
87e47de094
commit
7c7a3c25ab
15 changed files with 1881 additions and 134 deletions
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue