feat(applying): implement Alonzo phase-1 validations (#380)
This commit is contained in:
parent
0b1e5f0231
commit
da3e636759
33 changed files with 4900 additions and 704 deletions
944
pallas-applying/src/alonzo.rs
Normal file
944
pallas-applying/src/alonzo.rs
Normal file
|
|
@ -0,0 +1,944 @@
|
|||
//! Utilities required for Shelley-era transaction validation.
|
||||
|
||||
use crate::utils::{
|
||||
add_minted_value, add_values, empty_value, extract_auxiliary_data, get_alonzo_comp_tx_size,
|
||||
get_lovelace_from_alonzo_val, get_network_id_value, get_payment_part, get_shelley_address,
|
||||
get_val_size_in_words, mk_alonzo_vk_wits_check_list, values_are_equal, verify_signature,
|
||||
AlonzoError::*,
|
||||
AlonzoProtParams, FeePolicy, UTxOs,
|
||||
ValidationError::{self, *},
|
||||
ValidationResult,
|
||||
};
|
||||
use hex;
|
||||
use pallas_addresses::{ScriptHash, ShelleyAddress, ShelleyPaymentPart};
|
||||
use pallas_codec::{
|
||||
minicbor::{encode, Encoder},
|
||||
utils::{Bytes, KeepRaw},
|
||||
};
|
||||
use pallas_crypto::hash::Hash;
|
||||
use pallas_primitives::{
|
||||
alonzo::{
|
||||
AddrKeyhash, Mint, MintedTx, MintedWitnessSet, NativeScript, PlutusData, PlutusScript,
|
||||
PolicyId, Redeemer, RedeemerPointer, RedeemerTag, RequiredSigners, TransactionBody,
|
||||
TransactionInput, TransactionOutput, VKeyWitness, Value,
|
||||
},
|
||||
byron::TxOut,
|
||||
};
|
||||
use pallas_traverse::{MultiEraInput, MultiEraOutput, OriginalHash};
|
||||
use std::ops::Deref;
|
||||
|
||||
pub fn validate_alonzo_tx(
|
||||
mtx: &MintedTx,
|
||||
utxos: &UTxOs,
|
||||
prot_pps: &AlonzoProtParams,
|
||||
block_slot: &u64,
|
||||
network_id: &u8,
|
||||
) -> ValidationResult {
|
||||
let tx_body: &TransactionBody = &mtx.transaction_body;
|
||||
let size: &u64 = &get_alonzo_comp_tx_size(tx_body).ok_or(Alonzo(UnknownTxSize))?;
|
||||
check_ins_not_empty(tx_body)?;
|
||||
check_ins_and_collateral_in_utxos(tx_body, utxos)?;
|
||||
check_tx_validity_interval(tx_body, mtx, block_slot)?;
|
||||
check_fee(tx_body, size, mtx, utxos, prot_pps)?;
|
||||
check_preservation_of_value(tx_body, utxos)?;
|
||||
check_min_lovelace(tx_body, prot_pps)?;
|
||||
check_output_val_size(tx_body, prot_pps)?;
|
||||
check_network_id(tx_body, network_id)?;
|
||||
check_tx_size(size, prot_pps)?;
|
||||
check_tx_ex_units(mtx, prot_pps)?;
|
||||
check_witness_set(mtx, utxos)?;
|
||||
check_languages(mtx, prot_pps)?;
|
||||
check_metadata(tx_body, mtx)?;
|
||||
check_script_data_hash(tx_body, mtx)?;
|
||||
check_minting(tx_body, mtx)
|
||||
}
|
||||
|
||||
// The set of transaction inputs is not empty.
|
||||
fn check_ins_not_empty(tx_body: &TransactionBody) -> ValidationResult {
|
||||
if tx_body.inputs.is_empty() {
|
||||
return Err(Alonzo(TxInsEmpty));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// All transaction inputs and collateral inputs are in the set of (yet) unspent
|
||||
// transaction outputs.
|
||||
fn check_ins_and_collateral_in_utxos(tx_body: &TransactionBody, utxos: &UTxOs) -> ValidationResult {
|
||||
for input in tx_body.inputs.iter() {
|
||||
if !(utxos.contains_key(&MultiEraInput::from_alonzo_compatible(input))) {
|
||||
return Err(Alonzo(InputNotInUTxO));
|
||||
}
|
||||
}
|
||||
match &tx_body.collateral {
|
||||
None => Ok(()),
|
||||
Some(collaterals) => {
|
||||
for collateral in collaterals {
|
||||
if !(utxos.contains_key(&MultiEraInput::from_alonzo_compatible(collateral))) {
|
||||
return Err(Alonzo(CollateralNotInUTxO));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The block slot is contained in the transaction validity interval, and the
|
||||
// upper bound is translatable to UTC time.
|
||||
fn check_tx_validity_interval(
|
||||
tx_body: &TransactionBody,
|
||||
mtx: &MintedTx,
|
||||
block_slot: &u64,
|
||||
) -> ValidationResult {
|
||||
check_lower_bound(tx_body, block_slot)?;
|
||||
check_upper_bound(tx_body, mtx, block_slot)
|
||||
}
|
||||
|
||||
// If defined, the lower bound of the validity time interval does not exceed the
|
||||
// block slot.
|
||||
fn check_lower_bound(tx_body: &TransactionBody, block_slot: &u64) -> ValidationResult {
|
||||
match tx_body.validity_interval_start {
|
||||
Some(lower_bound) => {
|
||||
if *block_slot < lower_bound {
|
||||
Err(Alonzo(BlockPrecedesValInt))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
None => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
// If defined, the upper bound of the validity time interval is not exceeded by
|
||||
// the block slot, and it is translatable to UTC time.
|
||||
fn check_upper_bound(
|
||||
tx_body: &TransactionBody,
|
||||
_mtx: &MintedTx,
|
||||
block_slot: &u64,
|
||||
) -> ValidationResult {
|
||||
match tx_body.ttl {
|
||||
Some(upper_bound) => {
|
||||
if upper_bound < *block_slot {
|
||||
Err(Alonzo(BlockExceedsValInt))
|
||||
} else {
|
||||
// TODO: check that `upper_bound` is translatable to UTC time.
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
None => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
fn check_fee(
|
||||
tx_body: &TransactionBody,
|
||||
size: &u64,
|
||||
mtx: &MintedTx,
|
||||
utxos: &UTxOs,
|
||||
prot_pps: &AlonzoProtParams,
|
||||
) -> ValidationResult {
|
||||
check_min_fee(tx_body, size, prot_pps)?;
|
||||
if presence_of_plutus_scripts(mtx) {
|
||||
check_collaterals(tx_body, utxos, prot_pps)?
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// The fee paid by the transaction should be greater than or equal to the
|
||||
// minimum fee.
|
||||
fn check_min_fee(
|
||||
tx_body: &TransactionBody,
|
||||
size: &u64,
|
||||
prot_pps: &AlonzoProtParams,
|
||||
) -> ValidationResult {
|
||||
let fee_policy: &FeePolicy = &prot_pps.fee_policy;
|
||||
if tx_body.fee < fee_policy.summand + fee_policy.multiplier * size {
|
||||
return Err(Alonzo(FeeBelowMin));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn presence_of_plutus_scripts(mtx: &MintedTx) -> bool {
|
||||
let minted_witness_set: &MintedWitnessSet = &mtx.transaction_witness_set;
|
||||
match &minted_witness_set.plutus_script {
|
||||
Some(plutus_scripts) => !plutus_scripts.is_empty(),
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn check_collaterals(
|
||||
tx_body: &TransactionBody,
|
||||
utxos: &UTxOs,
|
||||
prot_pps: &AlonzoProtParams,
|
||||
) -> ValidationResult {
|
||||
let collaterals: &Vec<TransactionInput> = &tx_body
|
||||
.collateral
|
||||
.clone()
|
||||
.ok_or(Alonzo(CollateralMissing))?;
|
||||
check_collaterals_number(collaterals, prot_pps)?;
|
||||
check_collaterals_address(collaterals, utxos)?;
|
||||
check_collaterals_assets(tx_body, utxos, prot_pps)
|
||||
}
|
||||
|
||||
// The set of collateral inputs is not empty.
|
||||
// The number of collateral inputs is below maximum allowed by protocol.
|
||||
fn check_collaterals_number(
|
||||
collaterals: &[TransactionInput],
|
||||
prot_pps: &AlonzoProtParams,
|
||||
) -> ValidationResult {
|
||||
let number_collateral: u64 = collaterals.len() as u64;
|
||||
if number_collateral == 0 {
|
||||
Err(Alonzo(CollateralMissing))
|
||||
} else if number_collateral > prot_pps.max_collateral_inputs {
|
||||
Err(Alonzo(TooManyCollaterals))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// Each collateral input refers to a verification-key address.
|
||||
fn check_collaterals_address(collaterals: &[TransactionInput], utxos: &UTxOs) -> ValidationResult {
|
||||
for collateral in collaterals {
|
||||
match utxos.get(&MultiEraInput::from_alonzo_compatible(collateral)) {
|
||||
Some(multi_era_output) => {
|
||||
if let Some(alonzo_comp_output) = MultiEraOutput::as_alonzo(multi_era_output) {
|
||||
if let ShelleyPaymentPart::Script(_) =
|
||||
get_payment_part(alonzo_comp_output).ok_or(Alonzo(InputDecoding))?
|
||||
{
|
||||
return Err(Alonzo(CollateralNotVKeyLocked));
|
||||
}
|
||||
}
|
||||
}
|
||||
None => return Err(Alonzo(CollateralNotInUTxO)),
|
||||
};
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Collateral inputs contain only lovelace, and in a number not lower than the
|
||||
// minimum allowed.
|
||||
fn check_collaterals_assets(
|
||||
tx_body: &TransactionBody,
|
||||
utxos: &UTxOs,
|
||||
prot_pps: &AlonzoProtParams,
|
||||
) -> ValidationResult {
|
||||
let fee_percentage: u64 = tx_body.fee * prot_pps.collateral_percent;
|
||||
match &tx_body.collateral {
|
||||
Some(collaterals) => {
|
||||
for collateral in collaterals {
|
||||
match utxos.get(&MultiEraInput::from_alonzo_compatible(collateral)) {
|
||||
Some(multi_era_output) => match MultiEraOutput::as_alonzo(multi_era_output) {
|
||||
Some(TransactionOutput {
|
||||
amount: Value::Coin(n),
|
||||
..
|
||||
}) => {
|
||||
if *n * 100 < fee_percentage {
|
||||
return Err(Alonzo(CollateralMinLovelace));
|
||||
}
|
||||
}
|
||||
Some(TransactionOutput {
|
||||
amount: Value::Multiasset(n, multi_assets),
|
||||
..
|
||||
}) => {
|
||||
if *n * 100 < fee_percentage {
|
||||
return Err(Alonzo(CollateralMinLovelace));
|
||||
}
|
||||
if !multi_assets.is_empty() {
|
||||
return Err(Alonzo(NonLovelaceCollateral));
|
||||
}
|
||||
}
|
||||
None => (),
|
||||
},
|
||||
None => return Err(Alonzo(CollateralNotInUTxO)),
|
||||
}
|
||||
}
|
||||
}
|
||||
None => return Err(Alonzo(CollateralMissing)),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// The preservation of value property holds.
|
||||
fn check_preservation_of_value(tx_body: &TransactionBody, utxos: &UTxOs) -> ValidationResult {
|
||||
let neg_val_err: ValidationError = Alonzo(NegativeValue);
|
||||
let input: Value = get_consumed(tx_body, utxos)?;
|
||||
let produced: Value = get_produced(tx_body)?;
|
||||
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(Alonzo(PreservationOfValue));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_consumed(tx_body: &TransactionBody, utxos: &UTxOs) -> Result<Value, ValidationError> {
|
||||
let neg_val_err: ValidationError = Alonzo(NegativeValue);
|
||||
let mut res: Value = empty_value();
|
||||
for input in tx_body.inputs.iter() {
|
||||
let utxo_value: &MultiEraOutput = utxos
|
||||
.get(&MultiEraInput::from_alonzo_compatible(input))
|
||||
.ok_or(Alonzo(InputNotInUTxO))?;
|
||||
match MultiEraOutput::as_alonzo(utxo_value) {
|
||||
Some(TransactionOutput { amount, .. }) => res = add_values(&res, amount, &neg_val_err)?,
|
||||
None => match MultiEraOutput::as_byron(utxo_value) {
|
||||
Some(TxOut { amount, .. }) => {
|
||||
res = add_values(&res, &Value::Coin(*amount), &neg_val_err)?
|
||||
}
|
||||
_ => return Err(Alonzo(InputNotInUTxO)),
|
||||
},
|
||||
}
|
||||
}
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
fn get_produced(tx_body: &TransactionBody) -> Result<Value, ValidationError> {
|
||||
let neg_val_err: ValidationError = Alonzo(NegativeValue);
|
||||
let mut res: Value = empty_value();
|
||||
for TransactionOutput { amount, .. } in tx_body.outputs.iter() {
|
||||
res = add_values(&res, amount, &neg_val_err)?;
|
||||
}
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
// All transaction outputs should contain at least the minimum lovelace.
|
||||
fn check_min_lovelace(tx_body: &TransactionBody, prot_pps: &AlonzoProtParams) -> ValidationResult {
|
||||
for output in tx_body.outputs.iter() {
|
||||
if get_lovelace_from_alonzo_val(&output.amount) < compute_min_lovelace(output, prot_pps) {
|
||||
return Err(Alonzo(MinLovelaceUnreached));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn compute_min_lovelace(output: &TransactionOutput, prot_pps: &AlonzoProtParams) -> u64 {
|
||||
let utxo_entry_size: u64 = get_val_size_in_words(&output.amount)
|
||||
+ match output.datum_hash {
|
||||
Some(_) => 37, // utxoEntrySizeWithoutVal (27) + dataHashSize (10)
|
||||
None => 27, // utxoEntrySizeWithoutVal
|
||||
};
|
||||
prot_pps.coins_per_utxo_word * utxo_entry_size
|
||||
}
|
||||
|
||||
// The size of the value in each of the outputs should not be greater than the
|
||||
// maximum allowed.
|
||||
fn check_output_val_size(
|
||||
tx_body: &TransactionBody,
|
||||
prot_pps: &AlonzoProtParams,
|
||||
) -> ValidationResult {
|
||||
for output in tx_body.outputs.iter() {
|
||||
if get_val_size_in_words(&output.amount) > prot_pps.max_val_size {
|
||||
return Err(Alonzo(MaxValSizeExceeded));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// The network ID of the transaction and its output addresses is correct.
|
||||
fn check_network_id(tx_body: &TransactionBody, network_id: &u8) -> ValidationResult {
|
||||
check_tx_outs_network_id(tx_body, network_id)?;
|
||||
check_tx_network_id(tx_body, network_id)
|
||||
}
|
||||
|
||||
// The network ID of each output matches the global network ID.
|
||||
fn check_tx_outs_network_id(tx_body: &TransactionBody, network_id: &u8) -> ValidationResult {
|
||||
for output in tx_body.outputs.iter() {
|
||||
let addr: ShelleyAddress =
|
||||
get_shelley_address(Bytes::deref(&output.address)).ok_or(Alonzo(AddressDecoding))?;
|
||||
if addr.network().value() != *network_id {
|
||||
return Err(Alonzo(OutputWrongNetworkID));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// The network ID of the transaction body is either undefined or equal to the
|
||||
// global network ID.
|
||||
fn check_tx_network_id(tx_body: &TransactionBody, network_id: &u8) -> ValidationResult {
|
||||
if let Some(tx_network_id) = tx_body.network_id {
|
||||
if get_network_id_value(tx_network_id) != *network_id {
|
||||
return Err(Alonzo(TxWrongNetworkID));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// The transaction size does not exceed the protocol limit.
|
||||
fn check_tx_size(size: &u64, prot_pps: &AlonzoProtParams) -> ValidationResult {
|
||||
if *size > prot_pps.max_tx_size {
|
||||
return Err(Alonzo(MaxTxSizeExceeded));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// The number of execution units of the transaction should not exceed the
|
||||
// maximum allowed.
|
||||
fn check_tx_ex_units(mtx: &MintedTx, prot_pps: &AlonzoProtParams) -> ValidationResult {
|
||||
let tx_wits: &MintedWitnessSet = &mtx.transaction_witness_set;
|
||||
if presence_of_plutus_scripts(mtx) {
|
||||
match &tx_wits.redeemer {
|
||||
Some(redeemers_vec) => {
|
||||
let mut steps: u64 = 0;
|
||||
let mut mem: u32 = 0;
|
||||
for Redeemer { ex_units, .. } in redeemers_vec {
|
||||
mem += ex_units.mem;
|
||||
steps += ex_units.steps;
|
||||
}
|
||||
if mem > prot_pps.max_tx_ex_mem || steps > prot_pps.max_tx_ex_steps {
|
||||
return Err(Alonzo(TxExUnitsExceeded));
|
||||
}
|
||||
}
|
||||
None => return Err(Alonzo(RedeemerMissing)),
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn check_witness_set(mtx: &MintedTx, utxos: &UTxOs) -> ValidationResult {
|
||||
let tx_hash: &Vec<u8> = &Vec::from(mtx.transaction_body.original_hash().as_ref());
|
||||
let tx_body: &TransactionBody = &mtx.transaction_body;
|
||||
let tx_wits: &MintedWitnessSet = &mtx.transaction_witness_set;
|
||||
let vkey_wits: &Option<Vec<VKeyWitness>> = &tx_wits.vkeywitness;
|
||||
let native_scripts: Vec<NativeScript> = match &tx_wits.native_script {
|
||||
Some(scripts) => scripts.clone().iter().map(|x| x.clone().unwrap()).collect(),
|
||||
None => Vec::new(),
|
||||
};
|
||||
let plutus_scripts: Vec<PlutusScript> = match &tx_wits.plutus_script {
|
||||
Some(scripts) => scripts.clone(),
|
||||
None => Vec::new(),
|
||||
};
|
||||
check_needed_scripts_are_included(tx_body, utxos, &native_scripts, &plutus_scripts)?;
|
||||
check_datums(tx_body, utxos, &tx_wits.plutus_data)?;
|
||||
check_redeemers(tx_body, tx_wits, utxos)?;
|
||||
check_required_signers(&tx_body.required_signers, vkey_wits, tx_hash)?;
|
||||
check_vkey_input_wits(mtx, &tx_wits.vkeywitness, utxos)
|
||||
}
|
||||
|
||||
// The set of needed scripts (minting policies, native scripts and Plutus
|
||||
// scripts needed to validate the transaction) equals the set of scripts
|
||||
// contained in the transaction witnesses set.
|
||||
fn check_needed_scripts_are_included(
|
||||
tx_body: &TransactionBody,
|
||||
utxos: &UTxOs,
|
||||
native_scripts: &[NativeScript],
|
||||
plutus_scripts: &[PlutusScript],
|
||||
) -> ValidationResult {
|
||||
let mut native_scripts: Vec<(bool, NativeScript)> =
|
||||
native_scripts.iter().map(|x| (false, x.clone())).collect();
|
||||
let mut plutus_scripts: Vec<(bool, PlutusScript)> =
|
||||
plutus_scripts.iter().map(|x| (false, x.clone())).collect();
|
||||
check_script_inputs(tx_body, &mut native_scripts, &mut plutus_scripts, utxos)?;
|
||||
check_minting_policies(tx_body, &mut native_scripts, &mut plutus_scripts)?;
|
||||
for (native_script_covered, _) in native_scripts.iter() {
|
||||
if !native_script_covered {
|
||||
return Err(Alonzo(UnneededNativeScript));
|
||||
}
|
||||
}
|
||||
for (plutus_script_covered, _) in native_scripts.iter() {
|
||||
if !plutus_script_covered {
|
||||
return Err(Alonzo(UnneededPlutusScript));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn check_datums(
|
||||
tx_body: &TransactionBody,
|
||||
utxos: &UTxOs,
|
||||
option_plutus_data: &Option<Vec<KeepRaw<PlutusData>>>,
|
||||
) -> ValidationResult {
|
||||
let mut plutus_data: Vec<(bool, &KeepRaw<PlutusData>)> = match option_plutus_data {
|
||||
Some(plutus_data) => plutus_data.iter().map(|x| (false, x)).collect(),
|
||||
None => Vec::new(),
|
||||
};
|
||||
check_input_datum_hash_in_witness_set(tx_body, utxos, &mut plutus_data)?;
|
||||
check_datums_from_witness_set_in_inputs_or_outputs(tx_body, &plutus_data)
|
||||
}
|
||||
|
||||
// Each datum hash in a Plutus script input matches the hash of a datum in the
|
||||
// transaction witness set.
|
||||
fn check_input_datum_hash_in_witness_set(
|
||||
tx_body: &TransactionBody,
|
||||
utxos: &UTxOs,
|
||||
plutus_data: &mut [(bool, &KeepRaw<PlutusData>)],
|
||||
) -> ValidationResult {
|
||||
for input in &tx_body.inputs {
|
||||
match utxos
|
||||
.get(&MultiEraInput::from_alonzo_compatible(input))
|
||||
.and_then(MultiEraOutput::as_alonzo)
|
||||
{
|
||||
Some(output) => {
|
||||
if let Some(datum_hash) = output.datum_hash {
|
||||
find_datum_hash(datum_hash, plutus_data)?
|
||||
}
|
||||
}
|
||||
None => return Err(Alonzo(InputNotInUTxO)),
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn find_datum_hash(
|
||||
datum_hash: Hash<32>,
|
||||
plutus_data: &mut [(bool, &KeepRaw<PlutusData>)],
|
||||
) -> ValidationResult {
|
||||
for (found, datum) in plutus_data {
|
||||
let computed_datum_hash = pallas_crypto::hash::Hasher::<256>::hash(datum.raw_cbor());
|
||||
if datum_hash == computed_datum_hash {
|
||||
*found = true;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
Err(Alonzo(DatumMissing))
|
||||
}
|
||||
|
||||
// Each datum object in the transaction witness set corresponds either to an
|
||||
// output datum hash or to the datum hash of a Plutus script input.
|
||||
fn check_datums_from_witness_set_in_inputs_or_outputs(
|
||||
tx_body: &TransactionBody,
|
||||
plutus_data: &[(bool, &KeepRaw<PlutusData>)],
|
||||
) -> ValidationResult {
|
||||
for (found, datum) in plutus_data {
|
||||
if !found {
|
||||
find_datum(datum, &tx_body.outputs)?
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn find_datum(datum: &KeepRaw<PlutusData>, outputs: &[TransactionOutput]) -> ValidationResult {
|
||||
for output in outputs {
|
||||
if let Some(hash) = output.datum_hash {
|
||||
if pallas_crypto::hash::Hasher::<256>::hash(datum.raw_cbor()) == hash {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(Alonzo(UnneededDatum))
|
||||
}
|
||||
|
||||
// The set of redeemers in the transaction witness set should match the set of
|
||||
// Plutus scripts needed to validate the transaction.
|
||||
fn check_redeemers(
|
||||
tx_body: &TransactionBody,
|
||||
tx_wits: &MintedWitnessSet,
|
||||
utxos: &UTxOs,
|
||||
) -> ValidationResult {
|
||||
let redeemer_pointers: Vec<RedeemerPointer> = match &tx_wits.redeemer {
|
||||
Some(redeemers) => redeemers
|
||||
.iter()
|
||||
.map(|x| RedeemerPointer {
|
||||
tag: x.tag.clone(),
|
||||
index: x.index,
|
||||
})
|
||||
.collect(),
|
||||
None => Vec::new(),
|
||||
};
|
||||
let plutus_scripts: Vec<RedeemerPointer> =
|
||||
mk_plutus_script_redeemer_pointers(tx_body, tx_wits, utxos);
|
||||
redeemer_pointers_coincide(&redeemer_pointers, &plutus_scripts)
|
||||
}
|
||||
|
||||
fn mk_plutus_script_redeemer_pointers(
|
||||
tx_body: &TransactionBody,
|
||||
tx_wits: &MintedWitnessSet,
|
||||
utxos: &UTxOs,
|
||||
) -> Vec<RedeemerPointer> {
|
||||
match &tx_wits.plutus_script {
|
||||
Some(plutus_scripts) => {
|
||||
let sorted_inputs: Vec<TransactionInput> = sort_inputs(&tx_body.inputs);
|
||||
let mut res: Vec<RedeemerPointer> = Vec::new();
|
||||
for (index, input) in sorted_inputs.iter().enumerate() {
|
||||
if let Some(script_hash) = get_script_hash_from_input(input, utxos) {
|
||||
for plutus_script in plutus_scripts.iter() {
|
||||
let hashed_script: PolicyId = compute_plutus_script_hash(plutus_script);
|
||||
if script_hash == hashed_script {
|
||||
res.push(RedeemerPointer {
|
||||
tag: RedeemerTag::Spend,
|
||||
index: index as u32,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
match &tx_body.mint {
|
||||
Some(minted_value) => {
|
||||
let sorted_policies: Vec<PolicyId> = sort_policies(minted_value);
|
||||
for (index, policy) in sorted_policies.iter().enumerate() {
|
||||
for plutus_script in plutus_scripts.iter() {
|
||||
let hashed_script: PolicyId = compute_plutus_script_hash(plutus_script);
|
||||
if *policy == hashed_script {
|
||||
res.push(RedeemerPointer {
|
||||
tag: RedeemerTag::Mint,
|
||||
index: index as u32,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None => (),
|
||||
}
|
||||
res
|
||||
}
|
||||
None => Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
// Lexicographical sorting for inputs.
|
||||
fn sort_inputs(unsorted_inputs: &[TransactionInput]) -> Vec<TransactionInput> {
|
||||
let mut res: Vec<TransactionInput> = unsorted_inputs.to_owned();
|
||||
res.sort();
|
||||
res
|
||||
}
|
||||
|
||||
// Lexicographical sorting for PolicyID's.
|
||||
fn sort_policies(mint: &Mint) -> Vec<PolicyId> {
|
||||
let mut res: Vec<PolicyId> = mint
|
||||
.clone()
|
||||
.to_vec()
|
||||
.iter()
|
||||
.map(|(policy_id, _)| *policy_id)
|
||||
.collect();
|
||||
res.sort();
|
||||
res
|
||||
}
|
||||
|
||||
fn redeemer_pointers_coincide(
|
||||
redeemers: &[RedeemerPointer],
|
||||
plutus_scripts: &[RedeemerPointer],
|
||||
) -> ValidationResult {
|
||||
for redeemer_pointer in redeemers {
|
||||
if plutus_scripts.iter().all(|x| x != redeemer_pointer) {
|
||||
return Err(Alonzo(UnneededRedeemer));
|
||||
}
|
||||
}
|
||||
for ps_redeemer_pointer in plutus_scripts {
|
||||
if redeemers.iter().all(|x| x != ps_redeemer_pointer) {
|
||||
return Err(Alonzo(RedeemerMissing));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn check_script_inputs(
|
||||
tx_body: &TransactionBody,
|
||||
native_scripts: &mut [(bool, NativeScript)],
|
||||
plutus_scripts: &mut [(bool, PlutusScript)],
|
||||
utxos: &UTxOs,
|
||||
) -> ValidationResult {
|
||||
let mut inputs: Vec<(bool, ScriptHash)> = get_script_hashes(tx_body, utxos);
|
||||
for (input_script_covered, input_script_hash) in &mut inputs {
|
||||
for (native_script_covered, native_script) in native_scripts.iter_mut() {
|
||||
let hashed_script: PolicyId = compute_native_script_hash(native_script);
|
||||
if *input_script_hash == hashed_script {
|
||||
*input_script_covered = true;
|
||||
*native_script_covered = true;
|
||||
}
|
||||
}
|
||||
for (plutus_script_covered, plutus_script) in plutus_scripts.iter_mut() {
|
||||
let hashed_script: PolicyId = compute_plutus_script_hash(plutus_script);
|
||||
if *input_script_hash == hashed_script {
|
||||
*input_script_covered = true;
|
||||
*plutus_script_covered = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (input_script_covered, _) in inputs {
|
||||
if !input_script_covered {
|
||||
return Err(Alonzo(ScriptWitnessMissing));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_script_hashes(tx_body: &TransactionBody, utxos: &UTxOs) -> Vec<(bool, ScriptHash)> {
|
||||
let mut res: Vec<(bool, ScriptHash)> = Vec::new();
|
||||
for input in tx_body.inputs.iter() {
|
||||
if let Some(script_hash) = get_script_hash_from_input(input, utxos) {
|
||||
res.push((false, script_hash))
|
||||
}
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
fn get_script_hash_from_input(input: &TransactionInput, utxos: &UTxOs) -> Option<ScriptHash> {
|
||||
utxos
|
||||
.get(&MultiEraInput::from_alonzo_compatible(input))
|
||||
.and_then(MultiEraOutput::as_alonzo)
|
||||
.and_then(get_payment_part)
|
||||
.and_then(|payment_part| match payment_part {
|
||||
ShelleyPaymentPart::Script(script_hash) => Some(script_hash),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
|
||||
fn check_minting_policies(
|
||||
tx_body: &TransactionBody,
|
||||
native_scripts: &mut [(bool, NativeScript)],
|
||||
plutus_scripts: &mut [(bool, PlutusScript)],
|
||||
) -> ValidationResult {
|
||||
match &tx_body.mint {
|
||||
None => Ok(()),
|
||||
Some(minted_value) => {
|
||||
let mut minting_policies: Vec<(bool, PolicyId)> =
|
||||
minted_value.iter().map(|(pol, _)| (false, *pol)).collect();
|
||||
for (policy_covered, policy) in &mut minting_policies {
|
||||
for (native_script_covered, native_script) in native_scripts.iter_mut() {
|
||||
let hashed_script: PolicyId = compute_native_script_hash(native_script);
|
||||
if *policy == hashed_script {
|
||||
*policy_covered = true;
|
||||
*native_script_covered = true;
|
||||
}
|
||||
}
|
||||
for (plutus_script_covered, plutus_script) in plutus_scripts.iter_mut() {
|
||||
let hashed_script: PolicyId = compute_plutus_script_hash(plutus_script);
|
||||
if *policy == hashed_script {
|
||||
*policy_covered = true;
|
||||
*plutus_script_covered = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (policy_covered, _) in minting_policies {
|
||||
if !policy_covered {
|
||||
return Err(Alonzo(MintingLacksPolicy));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn compute_native_script_hash(script: &NativeScript) -> PolicyId {
|
||||
let mut payload = Vec::new();
|
||||
let _ = encode(script, &mut payload);
|
||||
payload.insert(0, 0);
|
||||
pallas_crypto::hash::Hasher::<224>::hash(&payload)
|
||||
}
|
||||
|
||||
fn compute_plutus_script_hash(script: &PlutusScript) -> PolicyId {
|
||||
let mut payload: Vec<u8> = Vec::from(script.as_ref());
|
||||
payload.insert(0, 1);
|
||||
pallas_crypto::hash::Hasher::<224>::hash(&payload)
|
||||
}
|
||||
|
||||
// The owner of each transaction input and each collateral input should have
|
||||
// signed the transaction.
|
||||
fn check_vkey_input_wits(
|
||||
mtx: &MintedTx,
|
||||
vkey_wits: &Option<Vec<VKeyWitness>>,
|
||||
utxos: &UTxOs,
|
||||
) -> ValidationResult {
|
||||
let tx_body: &TransactionBody = &mtx.transaction_body;
|
||||
let vk_wits: &mut Vec<(bool, VKeyWitness)> =
|
||||
&mut mk_alonzo_vk_wits_check_list(vkey_wits, Alonzo(VKWitnessMissing))?;
|
||||
let tx_hash: &Vec<u8> = &Vec::from(mtx.transaction_body.original_hash().as_ref());
|
||||
let mut inputs_and_collaterals: Vec<TransactionInput> = Vec::new();
|
||||
inputs_and_collaterals.extend(tx_body.inputs.clone());
|
||||
match &tx_body.collateral {
|
||||
Some(collaterals) => inputs_and_collaterals.extend(collaterals.clone()),
|
||||
None => (),
|
||||
}
|
||||
for input in inputs_and_collaterals.iter() {
|
||||
match utxos.get(&MultiEraInput::from_alonzo_compatible(input)) {
|
||||
Some(multi_era_output) => {
|
||||
if let Some(alonzo_comp_output) = MultiEraOutput::as_alonzo(multi_era_output) {
|
||||
match get_payment_part(alonzo_comp_output).ok_or(Alonzo(InputDecoding))? {
|
||||
ShelleyPaymentPart::Key(payment_key_hash) => {
|
||||
check_vk_wit(&payment_key_hash, vk_wits, tx_hash)?
|
||||
}
|
||||
ShelleyPaymentPart::Script(_) => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
None => return Err(Alonzo(InputNotInUTxO)),
|
||||
}
|
||||
}
|
||||
check_remaining_vk_wits(vk_wits, tx_hash) // required for native scripts
|
||||
}
|
||||
|
||||
fn check_vk_wit(
|
||||
payment_key_hash: &AddrKeyhash,
|
||||
wits: &mut [(bool, VKeyWitness)],
|
||||
data_to_verify: &[u8],
|
||||
) -> ValidationResult {
|
||||
for (vkey_wit_covered, vkey_wit) in wits {
|
||||
if pallas_crypto::hash::Hasher::<224>::hash(&vkey_wit.vkey.clone()) == *payment_key_hash {
|
||||
if !verify_signature(vkey_wit, data_to_verify) {
|
||||
return Err(Alonzo(VKWrongSignature));
|
||||
} else {
|
||||
*vkey_wit_covered = true;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(Alonzo(VKWitnessMissing))
|
||||
}
|
||||
|
||||
fn check_remaining_vk_wits(
|
||||
wits: &mut [(bool, VKeyWitness)],
|
||||
data_to_verify: &[u8],
|
||||
) -> ValidationResult {
|
||||
for (covered, vkey_wit) in wits {
|
||||
if !*covered {
|
||||
if verify_signature(vkey_wit, data_to_verify) {
|
||||
return Ok(());
|
||||
} else {
|
||||
return Err(Alonzo(VKWrongSignature));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// All required signers (needed by a Plutus script) have a corresponding match
|
||||
// in the transaction witness set.
|
||||
fn check_required_signers(
|
||||
required_signers: &Option<RequiredSigners>,
|
||||
vkey_wits: &Option<Vec<VKeyWitness>>,
|
||||
data_to_verify: &[u8],
|
||||
) -> ValidationResult {
|
||||
if let Some(req_signers) = &required_signers {
|
||||
match &vkey_wits {
|
||||
Some(vkey_wits) => {
|
||||
for req_signer in req_signers {
|
||||
find_and_check_req_signer(req_signer, vkey_wits, data_to_verify)?
|
||||
}
|
||||
}
|
||||
None => return Err(Alonzo(ReqSignerMissing)),
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Try to find the verification key in the witnesses, and verify the signature.
|
||||
fn find_and_check_req_signer(
|
||||
vkey_hash: &AddrKeyhash,
|
||||
vkey_wits: &[VKeyWitness],
|
||||
data_to_verify: &[u8],
|
||||
) -> ValidationResult {
|
||||
for vkey_wit in vkey_wits {
|
||||
if pallas_crypto::hash::Hasher::<224>::hash(&vkey_wit.vkey.clone()) == *vkey_hash {
|
||||
if !verify_signature(vkey_wit, data_to_verify) {
|
||||
return Err(Alonzo(ReqSignerWrongSig));
|
||||
} else {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(Alonzo(ReqSignerMissing))
|
||||
}
|
||||
|
||||
// The required script languages are included in the protocol parameters.
|
||||
fn check_languages(_mtx: &MintedTx, _prot_pps: &AlonzoProtParams) -> ValidationResult {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// The metadata of the transaction is valid.
|
||||
fn check_metadata(tx_body: &TransactionBody, mtx: &MintedTx) -> ValidationResult {
|
||||
match (&tx_body.auxiliary_data_hash, extract_auxiliary_data(mtx)) {
|
||||
(Some(metadata_hash), Some(metadata)) => {
|
||||
if metadata_hash.as_slice()
|
||||
== pallas_crypto::hash::Hasher::<256>::hash(metadata).as_ref()
|
||||
{
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Alonzo(MetadataHash))
|
||||
}
|
||||
}
|
||||
(None, None) => Ok(()),
|
||||
_ => Err(Alonzo(MetadataHash)),
|
||||
}
|
||||
}
|
||||
|
||||
// The script data integrity hash matches the hash of the redeemers, languages
|
||||
// and datums of the transaction witness set.
|
||||
fn check_script_data_hash(tx_body: &TransactionBody, mtx: &MintedTx) -> ValidationResult {
|
||||
match tx_body.script_data_hash {
|
||||
Some(script_data_hash) => match (
|
||||
&mtx.transaction_witness_set.plutus_data,
|
||||
&mtx.transaction_witness_set.redeemer,
|
||||
) {
|
||||
(Some(plutus_data), Some(redeemer)) => {
|
||||
let plutus_data: Vec<PlutusData> = plutus_data
|
||||
.iter()
|
||||
.map(|x| KeepRaw::unwrap(x.clone()))
|
||||
.collect();
|
||||
if script_data_hash == compute_script_integrity_hash(&plutus_data, redeemer) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Alonzo(ScriptIntegrityHash))
|
||||
}
|
||||
}
|
||||
(_, _) => Err(Alonzo(ScriptIntegrityHash)),
|
||||
},
|
||||
None => {
|
||||
if option_vec_is_empty(&mtx.transaction_witness_set.plutus_data)
|
||||
&& option_vec_is_empty(&mtx.transaction_witness_set.redeemer)
|
||||
{
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Alonzo(ScriptIntegrityHash))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn compute_script_integrity_hash(plutus_data: &[PlutusData], redeemer: &[Redeemer]) -> Hash<32> {
|
||||
let mut value_to_hash: Vec<u8> = Vec::new();
|
||||
// First, the Redeemer.
|
||||
let _ = encode(redeemer, &mut value_to_hash);
|
||||
// Next, the PlutusData.
|
||||
let mut plutus_data_encoder: Encoder<Vec<u8>> = Encoder::new(Vec::new());
|
||||
let _ = plutus_data_encoder.begin_array();
|
||||
for single_plutus_data in plutus_data.iter() {
|
||||
let _ = plutus_data_encoder.encode(single_plutus_data);
|
||||
}
|
||||
let _ = plutus_data_encoder.end();
|
||||
value_to_hash.extend(plutus_data_encoder.writer().clone());
|
||||
// Finally, the cost model.
|
||||
value_to_hash.extend(cost_model_cbor());
|
||||
pallas_crypto::hash::Hasher::<256>::hash(&value_to_hash)
|
||||
}
|
||||
|
||||
fn cost_model_cbor() -> Vec<u8> {
|
||||
hex::decode(
|
||||
"a141005901d59f1a000302590001011a00060bc719026d00011a000249f01903e800011a000249f018201a0025cea81971f70419744d186419744d186419744d186419744d186419744d186419744d18641864186419744d18641a000249f018201a000249f018201a000249f018201a000249f01903e800011a000249f018201a000249f01903e800081a000242201a00067e2318760001011a000249f01903e800081a000249f01a0001b79818f7011a000249f0192710011a0002155e19052e011903e81a000249f01903e8011a000249f018201a000249f018201a000249f0182001011a000249f0011a000249f0041a000194af18f8011a000194af18f8011a0002377c190556011a0002bdea1901f1011a000249f018201a000249f018201a000249f018201a000249f018201a000249f018201a000249f018201a000242201a00067e23187600010119f04c192bd200011a000249f018201a000242201a00067e2318760001011a000242201a00067e2318760001011a0025cea81971f704001a000141bb041a000249f019138800011a000249f018201a000302590001011a000249f018201a000249f018201a000249f018201a000249f018201a000249f018201a000249f018201a000249f018201a00330da70101ff"
|
||||
).unwrap()
|
||||
}
|
||||
|
||||
fn option_vec_is_empty<T>(option_vec: &Option<Vec<T>>) -> bool {
|
||||
match option_vec {
|
||||
Some(vec) => vec.is_empty(),
|
||||
None => true,
|
||||
}
|
||||
}
|
||||
|
||||
// Each minted / burned asset is paired with an appropriate native script or
|
||||
// Plutus script.
|
||||
fn check_minting(tx_body: &TransactionBody, mtx: &MintedTx) -> ValidationResult {
|
||||
match &tx_body.mint {
|
||||
Some(minted_value) => {
|
||||
let native_script_wits: Vec<NativeScript> =
|
||||
match &mtx.transaction_witness_set.native_script {
|
||||
None => Vec::new(),
|
||||
Some(keep_raw_native_script_wits) => keep_raw_native_script_wits
|
||||
.iter()
|
||||
.map(|x| x.clone().unwrap())
|
||||
.collect(),
|
||||
};
|
||||
let plutus_script_wits: Vec<PlutusScript> = Vec::new();
|
||||
for (policy, _) in minted_value.iter() {
|
||||
if native_script_wits
|
||||
.iter()
|
||||
.all(|native_script| compute_native_script_hash(native_script) != *policy)
|
||||
&& plutus_script_wits
|
||||
.iter()
|
||||
.all(|plutus_script| compute_plutus_script_hash(plutus_script) != *policy)
|
||||
{
|
||||
return Err(Alonzo(MintingLacksPolicy));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
None => Ok(()),
|
||||
}
|
||||
}
|
||||
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
use std::borrow::Cow;
|
||||
|
||||
use crate::types::{
|
||||
use crate::utils::{
|
||||
ByronError::*,
|
||||
ByronProtParams, MultiEraInput, MultiEraOutput, SigningTag, UTxOs,
|
||||
ByronProtParams, UTxOs,
|
||||
ValidationError::{self, *},
|
||||
ValidationResult,
|
||||
};
|
||||
|
|
@ -23,7 +23,7 @@ use pallas_crypto::{
|
|||
use pallas_primitives::byron::{
|
||||
Address, MintedTxPayload, PubKey, Signature as ByronSignature, Twit, Tx, TxIn, TxOut,
|
||||
};
|
||||
use pallas_traverse::OriginalHash;
|
||||
use pallas_traverse::{MultiEraInput, MultiEraOutput, OriginalHash};
|
||||
|
||||
pub fn validate_byron_tx(
|
||||
mtxp: &MintedTxPayload,
|
||||
|
|
@ -251,11 +251,11 @@ fn get_data_to_verify(
|
|||
let mut enc: Encoder<&mut Vec<u8>> = Encoder::new(buff);
|
||||
match sign {
|
||||
TaggedSignature::PkWitness(_) => {
|
||||
enc.encode(SigningTag::Tx as u64)
|
||||
enc.encode(1u64)
|
||||
.map_err(|_| Byron(UnableToProcessWitness))?;
|
||||
}
|
||||
TaggedSignature::RedeemWitness(_) => {
|
||||
enc.encode(SigningTag::RedeemTx as u64)
|
||||
enc.encode(2u64)
|
||||
.map_err(|_| Byron(UnableToProcessWitness))?;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,40 +1,43 @@
|
|||
//! Logic for validating and applying new blocks and txs to the chain state
|
||||
|
||||
pub mod alonzo;
|
||||
pub mod byron;
|
||||
pub mod shelley_ma;
|
||||
pub mod types;
|
||||
pub mod utils;
|
||||
|
||||
use alonzo::validate_alonzo_tx;
|
||||
use byron::validate_byron_tx;
|
||||
use pallas_traverse::{Era, MultiEraTx};
|
||||
use shelley_ma::validate_shelley_ma_tx;
|
||||
|
||||
pub use types::{
|
||||
pub use utils::{
|
||||
Environment, MultiEraProtParams, UTxOs, ValidationError::TxAndProtParamsDiffer,
|
||||
ValidationResult,
|
||||
};
|
||||
|
||||
pub fn validate(metx: &MultiEraTx, utxos: &UTxOs, env: &Environment) -> ValidationResult {
|
||||
match env {
|
||||
Environment {
|
||||
prot_params: MultiEraProtParams::Byron(bpp),
|
||||
prot_magic,
|
||||
..
|
||||
} => match metx {
|
||||
MultiEraTx::Byron(mtxp) => validate_byron_tx(mtxp, utxos, bpp, prot_magic),
|
||||
match env.prot_params() {
|
||||
MultiEraProtParams::Byron(bpp) => match metx {
|
||||
MultiEraTx::Byron(mtxp) => validate_byron_tx(mtxp, utxos, bpp, env.prot_magic()),
|
||||
_ => Err(TxAndProtParamsDiffer),
|
||||
},
|
||||
Environment {
|
||||
prot_params: MultiEraProtParams::Shelley(spp),
|
||||
block_slot,
|
||||
network_id,
|
||||
..
|
||||
} => match metx.era() {
|
||||
Era::Shelley | Era::Allegra | Era::Mary => match metx.as_alonzo() {
|
||||
Some(mtx) => {
|
||||
validate_shelley_ma_tx(mtx, utxos, spp, block_slot, network_id, &metx.era())
|
||||
}
|
||||
None => Err(TxAndProtParamsDiffer),
|
||||
},
|
||||
MultiEraProtParams::Shelley(spp) => match metx {
|
||||
MultiEraTx::AlonzoCompatible(mtx, Era::Shelley)
|
||||
| MultiEraTx::AlonzoCompatible(mtx, Era::Allegra)
|
||||
| MultiEraTx::AlonzoCompatible(mtx, Era::Mary) => validate_shelley_ma_tx(
|
||||
mtx,
|
||||
utxos,
|
||||
spp,
|
||||
env.block_slot(),
|
||||
env.network_id(),
|
||||
&metx.era(),
|
||||
),
|
||||
_ => Err(TxAndProtParamsDiffer),
|
||||
},
|
||||
MultiEraProtParams::Alonzo(app) => match metx {
|
||||
MultiEraTx::AlonzoCompatible(mtx, Era::Alonzo) => {
|
||||
validate_alonzo_tx(mtx, utxos, app, env.block_slot(), env.network_id())
|
||||
}
|
||||
_ => Err(TxAndProtParamsDiffer),
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,29 +1,26 @@
|
|||
//! Utilities required for Shelley-era transaction validation.
|
||||
//! Utilities required for ShelleyMA-era transaction validation.
|
||||
|
||||
use crate::types::{
|
||||
FeePolicy,
|
||||
use crate::utils::{
|
||||
add_minted_value, add_values, empty_value, extract_auxiliary_data, 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, FeePolicy,
|
||||
ShelleyMAError::*,
|
||||
ShelleyProtParams, UTxOs,
|
||||
ValidationError::{self, *},
|
||||
ValidationResult,
|
||||
};
|
||||
use pallas_addresses::{Address, PaymentKeyHash, ScriptHash, ShelleyAddress, ShelleyPaymentPart};
|
||||
use pallas_codec::{
|
||||
minicbor::encode,
|
||||
utils::{Bytes, KeepRaw, KeyValuePairs},
|
||||
};
|
||||
use pallas_crypto::key::ed25519::{PublicKey, Signature};
|
||||
use pallas_addresses::{PaymentKeyHash, ScriptHash, ShelleyAddress, ShelleyPaymentPart};
|
||||
use pallas_codec::minicbor::encode;
|
||||
use pallas_primitives::{
|
||||
alonzo::{
|
||||
AssetName, AuxiliaryData, Coin, MintedTx, MintedWitnessSet, Multiasset, NativeScript,
|
||||
PolicyId, TransactionBody, TransactionOutput, VKeyWitness, Value,
|
||||
MintedTx, MintedWitnessSet, NativeScript, PolicyId, TransactionBody, TransactionOutput,
|
||||
VKeyWitness, Value,
|
||||
},
|
||||
byron::TxOut,
|
||||
};
|
||||
use pallas_traverse::{ComputeHash, Era, MultiEraInput, MultiEraOutput};
|
||||
use std::{collections::HashMap, ops::Deref};
|
||||
use std::{cmp::max, ops::Deref};
|
||||
|
||||
// TODO: implement each of the validation rules.
|
||||
pub fn validate_shelley_ma_tx(
|
||||
mtx: &MintedTx,
|
||||
utxos: &UTxOs,
|
||||
|
|
@ -34,45 +31,23 @@ pub fn validate_shelley_ma_tx(
|
|||
) -> ValidationResult {
|
||||
let tx_body: &TransactionBody = &mtx.transaction_body;
|
||||
let tx_wits: &MintedWitnessSet = &mtx.transaction_witness_set;
|
||||
let size: &u64 = &get_tx_size(tx_body)?;
|
||||
let auxiliary_data_hash: &Option<Bytes> = &tx_body.auxiliary_data_hash;
|
||||
let auxiliary_data: &Option<&[u8]> = &extract_auxiliary_data(mtx);
|
||||
let minted_value: &Option<Multiasset<i64>> = &tx_body.mint;
|
||||
let native_script_wits: &Option<Vec<NativeScript>> = &mtx
|
||||
.transaction_witness_set
|
||||
.native_script
|
||||
.as_ref()
|
||||
.map(|x| x.iter().map(|y| y.deref().clone()).collect());
|
||||
let size: &u64 = &get_alonzo_comp_tx_size(tx_body).ok_or(ShelleyMA(UnknownTxSize))?;
|
||||
check_ins_not_empty(tx_body)?;
|
||||
check_ins_in_utxos(tx_body, utxos)?;
|
||||
check_ttl(tx_body, block_slot)?;
|
||||
check_size(size, prot_pps)?;
|
||||
check_tx_size(size, prot_pps)?;
|
||||
check_min_lovelace(tx_body, prot_pps, era)?;
|
||||
check_preservation_of_value(tx_body, utxos, era)?;
|
||||
check_fees(tx_body, size, &prot_pps.fee_policy)?;
|
||||
check_fees(tx_body, size, prot_pps)?;
|
||||
check_network_id(tx_body, network_id)?;
|
||||
check_metadata(auxiliary_data_hash, auxiliary_data)?;
|
||||
check_witnesses(tx_body, utxos, tx_wits)?;
|
||||
check_minting(minted_value, native_script_wits)
|
||||
}
|
||||
|
||||
fn get_tx_size(tx_body: &TransactionBody) -> Result<u64, ValidationError> {
|
||||
let mut buff: Vec<u8> = Vec::new();
|
||||
match encode(tx_body, &mut buff) {
|
||||
Ok(()) => Ok(buff.len() as u64),
|
||||
Err(_) => Err(Shelley(UnknownTxSize)),
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_auxiliary_data<'a>(mtx: &'a MintedTx) -> Option<&'a [u8]> {
|
||||
Option::<KeepRaw<AuxiliaryData>>::from((mtx.auxiliary_data).clone())
|
||||
.as_ref()
|
||||
.map(KeepRaw::raw_cbor)
|
||||
check_metadata(tx_body, mtx)?;
|
||||
check_witnesses(tx_body, tx_wits, utxos)?;
|
||||
check_minting(tx_body, mtx)
|
||||
}
|
||||
|
||||
fn check_ins_not_empty(tx_body: &TransactionBody) -> ValidationResult {
|
||||
if tx_body.inputs.is_empty() {
|
||||
return Err(Shelley(TxInsEmpty));
|
||||
return Err(ShelleyMA(TxInsEmpty));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -80,7 +55,7 @@ fn check_ins_not_empty(tx_body: &TransactionBody) -> ValidationResult {
|
|||
fn check_ins_in_utxos(tx_body: &TransactionBody, utxos: &UTxOs) -> ValidationResult {
|
||||
for input in tx_body.inputs.iter() {
|
||||
if !(utxos.contains_key(&MultiEraInput::from_alonzo_compatible(input))) {
|
||||
return Err(Shelley(InputNotInUTxO));
|
||||
return Err(ShelleyMA(InputNotInUTxO));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
|
@ -90,18 +65,18 @@ fn check_ttl(tx_body: &TransactionBody, block_slot: &u64) -> ValidationResult {
|
|||
match tx_body.ttl {
|
||||
Some(ttl) => {
|
||||
if ttl < *block_slot {
|
||||
Err(Shelley(TTLExceeded))
|
||||
Err(ShelleyMA(TTLExceeded))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
None => Err(Shelley(AlonzoCompNotShelley)),
|
||||
None => Err(ShelleyMA(AlonzoCompNotShelley)),
|
||||
}
|
||||
}
|
||||
|
||||
fn check_size(size: &u64, prot_pps: &ShelleyProtParams) -> ValidationResult {
|
||||
fn check_tx_size(size: &u64, prot_pps: &ShelleyProtParams) -> ValidationResult {
|
||||
if *size > prot_pps.max_tx_size {
|
||||
return Err(Shelley(MaxTxSizeExceeded));
|
||||
return Err(ShelleyMA(MaxTxSizeExceeded));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -111,34 +86,46 @@ fn check_min_lovelace(
|
|||
prot_pps: &ShelleyProtParams,
|
||||
era: &Era,
|
||||
) -> ValidationResult {
|
||||
for TransactionOutput { amount, .. } in &tx_body.outputs {
|
||||
match (era, amount) {
|
||||
(Era::Shelley, Value::Coin(lovelace))
|
||||
| (Era::Allegra, Value::Coin(lovelace))
|
||||
| (Era::Mary, Value::Multiasset(lovelace, _)) => {
|
||||
if *lovelace < prot_pps.min_lovelace {
|
||||
return Err(Shelley(MinLovelaceUnreached));
|
||||
for output in &tx_body.outputs {
|
||||
match era {
|
||||
Era::Shelley | Era::Allegra | Era::Mary => {
|
||||
if get_lovelace_from_alonzo_val(&output.amount)
|
||||
< compute_min_lovelace(output, prot_pps)
|
||||
{
|
||||
return Err(ShelleyMA(MinLovelaceUnreached));
|
||||
}
|
||||
}
|
||||
_ => return Err(Shelley(ValueNotShelley)),
|
||||
_ => return Err(ShelleyMA(ValueNotShelley)),
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn compute_min_lovelace(output: &TransactionOutput, prot_pps: &ShelleyProtParams) -> u64 {
|
||||
match &output.amount {
|
||||
Value::Coin(_) => prot_pps.min_lovelace,
|
||||
Value::Multiasset(lovelace, _) => {
|
||||
let utxo_entry_size: u64 = 27 + get_val_size_in_words(&output.amount);
|
||||
let coins_per_utxo_word: u64 = prot_pps.min_lovelace / 27;
|
||||
max(*lovelace, utxo_entry_size * coins_per_utxo_word)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_preservation_of_value(
|
||||
tx_body: &TransactionBody,
|
||||
utxos: &UTxOs,
|
||||
era: &Era,
|
||||
) -> 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))?;
|
||||
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)?;
|
||||
add_minted_value(&output, m, &neg_val_err)?;
|
||||
}
|
||||
if !values_are_equal(&input, &output) {
|
||||
return Err(Shelley(PreservationOfValue));
|
||||
return Err(ShelleyMA(PreservationOfValue));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -148,20 +135,23 @@ fn get_consumed(
|
|||
utxos: &UTxOs,
|
||||
era: &Era,
|
||||
) -> Result<Value, ValidationError> {
|
||||
let neg_val_err: ValidationError = ShelleyMA(NegativeValue);
|
||||
let mut res: Value = empty_value();
|
||||
for input in tx_body.inputs.iter() {
|
||||
let utxo_value: &MultiEraOutput = utxos
|
||||
.get(&MultiEraInput::from_alonzo_compatible(input))
|
||||
.ok_or(Shelley(InputNotInUTxO))?;
|
||||
.ok_or(ShelleyMA(InputNotInUTxO))?;
|
||||
match MultiEraOutput::as_alonzo(utxo_value) {
|
||||
Some(TransactionOutput { amount, .. }) => match (amount, era) {
|
||||
(Value::Coin(..), _) => res = add_values(&res, amount)?,
|
||||
(Value::Multiasset(..), Era::Shelley) => return Err(Shelley(ValueNotShelley)),
|
||||
_ => res = add_values(&res, amount)?,
|
||||
(Value::Coin(..), _) => res = add_values(&res, amount, &neg_val_err)?,
|
||||
(Value::Multiasset(..), Era::Shelley) => return Err(ShelleyMA(ValueNotShelley)),
|
||||
_ => res = add_values(&res, amount, &neg_val_err)?,
|
||||
},
|
||||
None => match MultiEraOutput::as_byron(utxo_value) {
|
||||
Some(TxOut { amount, .. }) => res = add_values(&res, &Value::Coin(*amount))?,
|
||||
_ => return Err(Shelley(InputNotInUTxO)),
|
||||
Some(TxOut { amount, .. }) => {
|
||||
res = add_values(&res, &Value::Coin(*amount), &neg_val_err)?
|
||||
}
|
||||
_ => return Err(ShelleyMA(InputNotInUTxO)),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
@ -169,237 +159,72 @@ fn get_consumed(
|
|||
}
|
||||
|
||||
fn get_produced(tx_body: &TransactionBody, era: &Era) -> Result<Value, ValidationError> {
|
||||
let neg_val_err: ValidationError = ShelleyMA(NegativeValue);
|
||||
let mut res: Value = empty_value();
|
||||
for TransactionOutput { amount, .. } in tx_body.outputs.iter() {
|
||||
match (amount, era) {
|
||||
(Value::Coin(..), _) => res = add_values(&res, amount)?,
|
||||
(Value::Multiasset(..), Era::Shelley) => return Err(Shelley(WrongEraOutput)),
|
||||
_ => res = add_values(&res, amount)?,
|
||||
(Value::Coin(..), _) => res = add_values(&res, amount, &neg_val_err)?,
|
||||
(Value::Multiasset(..), Era::Shelley) => return Err(ShelleyMA(WrongEraOutput)),
|
||||
_ => res = add_values(&res, amount, &neg_val_err)?,
|
||||
}
|
||||
}
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
fn empty_value() -> Value {
|
||||
Value::Multiasset(0, Multiasset::<Coin>::from(Vec::new()))
|
||||
}
|
||||
|
||||
fn add_values(first: &Value, second: &Value) -> Result<Value, ValidationError> {
|
||||
match (first, second) {
|
||||
(Value::Coin(f), Value::Coin(s)) => Ok(Value::Coin(f + s)),
|
||||
(Value::Multiasset(f, fma), Value::Coin(s)) => Ok(Value::Multiasset(f + s, fma.clone())),
|
||||
(Value::Coin(f), Value::Multiasset(s, sma)) => Ok(Value::Multiasset(f + s, sma.clone())),
|
||||
(Value::Multiasset(f, fma), Value::Multiasset(s, sma)) => Ok(Value::Multiasset(
|
||||
f + s,
|
||||
coerce_to_coin(&add_multiasset_values(
|
||||
&coerce_to_i64(fma),
|
||||
&coerce_to_i64(sma),
|
||||
))?,
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
fn add_minted_value(
|
||||
base_value: &Value,
|
||||
minted_value: &Multiasset<i64>,
|
||||
) -> Result<Value, ValidationError> {
|
||||
match base_value {
|
||||
Value::Coin(n) => Ok(Value::Multiasset(*n, coerce_to_coin(minted_value)?)),
|
||||
Value::Multiasset(n, mary_base_value) => Ok(Value::Multiasset(
|
||||
*n,
|
||||
coerce_to_coin(&add_multiasset_values(
|
||||
&coerce_to_i64(mary_base_value),
|
||||
minted_value,
|
||||
))?,
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
fn coerce_to_i64(value: &Multiasset<Coin>) -> Multiasset<i64> {
|
||||
let mut res: Vec<(PolicyId, KeyValuePairs<AssetName, i64>)> = Vec::new();
|
||||
for (policy, assets) in value.clone().to_vec().iter() {
|
||||
let mut aa: Vec<(AssetName, i64)> = Vec::new();
|
||||
for (asset_name, amount) in assets.clone().to_vec().iter() {
|
||||
aa.push((asset_name.clone(), *amount as i64));
|
||||
}
|
||||
res.push((*policy, KeyValuePairs::<AssetName, i64>::from(aa)));
|
||||
}
|
||||
KeyValuePairs::<PolicyId, KeyValuePairs<AssetName, i64>>::from(res)
|
||||
}
|
||||
|
||||
fn coerce_to_coin(value: &Multiasset<i64>) -> Result<Multiasset<Coin>, ValidationError> {
|
||||
let mut res: Vec<(PolicyId, KeyValuePairs<AssetName, Coin>)> = Vec::new();
|
||||
for (policy, assets) in value.clone().to_vec().iter() {
|
||||
let mut aa: Vec<(AssetName, Coin)> = Vec::new();
|
||||
for (asset_name, amount) in assets.clone().to_vec().iter() {
|
||||
if *amount < 0 {
|
||||
return Err(Shelley(NegativeValue));
|
||||
}
|
||||
aa.push((asset_name.clone(), *amount as u64));
|
||||
}
|
||||
res.push((*policy, KeyValuePairs::<AssetName, Coin>::from(aa)));
|
||||
}
|
||||
Ok(KeyValuePairs::<PolicyId, KeyValuePairs<AssetName, Coin>>::from(res))
|
||||
}
|
||||
|
||||
fn add_multiasset_values(first: &Multiasset<i64>, second: &Multiasset<i64>) -> Multiasset<i64> {
|
||||
let mut res: HashMap<PolicyId, HashMap<AssetName, i64>> = HashMap::new();
|
||||
for (policy, new_assets) in first.iter() {
|
||||
match res.get(policy) {
|
||||
Some(old_assets) => res.insert(*policy, add_same_policy_assets(old_assets, new_assets)),
|
||||
None => res.insert(*policy, add_same_policy_assets(&HashMap::new(), new_assets)),
|
||||
};
|
||||
}
|
||||
for (policy, new_assets) in second.iter() {
|
||||
match res.get(policy) {
|
||||
Some(old_assets) => res.insert(*policy, add_same_policy_assets(old_assets, new_assets)),
|
||||
None => res.insert(*policy, add_same_policy_assets(&HashMap::new(), new_assets)),
|
||||
};
|
||||
}
|
||||
wrap_multiasset(res)
|
||||
}
|
||||
|
||||
fn add_same_policy_assets(
|
||||
old_assets: &HashMap<AssetName, i64>,
|
||||
new_assets: &KeyValuePairs<AssetName, i64>,
|
||||
) -> HashMap<AssetName, i64> {
|
||||
let mut res: HashMap<AssetName, i64> = old_assets.clone();
|
||||
for (asset_name, new_amount) in new_assets.iter() {
|
||||
match res.get(asset_name) {
|
||||
Some(old_amount) => res.insert(asset_name.clone(), old_amount + *new_amount),
|
||||
None => res.insert(asset_name.clone(), *new_amount),
|
||||
};
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
fn wrap_multiasset(input: HashMap<PolicyId, HashMap<AssetName, i64>>) -> Multiasset<i64> {
|
||||
Multiasset::<i64>::from(
|
||||
input
|
||||
.into_iter()
|
||||
.map(|(policy, assets)| {
|
||||
(
|
||||
policy,
|
||||
KeyValuePairs::<AssetName, i64>::from(
|
||||
assets.into_iter().collect::<Vec<(AssetName, i64)>>(),
|
||||
),
|
||||
)
|
||||
})
|
||||
.collect::<Vec<(PolicyId, KeyValuePairs<AssetName, i64>)>>(),
|
||||
)
|
||||
}
|
||||
|
||||
fn values_are_equal(first: &Value, second: &Value) -> bool {
|
||||
match (first, second) {
|
||||
(Value::Coin(f), Value::Coin(s)) => f == s,
|
||||
(Value::Multiasset(..), Value::Coin(..)) => false,
|
||||
(Value::Coin(..), Value::Multiasset(..)) => false,
|
||||
(Value::Multiasset(f, fma), Value::Multiasset(s, sma)) => {
|
||||
if f != s {
|
||||
false
|
||||
} else {
|
||||
for (fpolicy, fassets) in fma.iter() {
|
||||
match find_policy(sma, fpolicy) {
|
||||
Some(sassets) => {
|
||||
for (fasset_name, famount) in fassets.iter() {
|
||||
match find_assets(&sassets, fasset_name) {
|
||||
Some(samount) => {
|
||||
if *famount != samount {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
None => return false,
|
||||
};
|
||||
}
|
||||
}
|
||||
None => return false,
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn find_policy(
|
||||
mary_value: &Multiasset<Coin>,
|
||||
search_policy: &PolicyId,
|
||||
) -> Option<KeyValuePairs<AssetName, Coin>> {
|
||||
for (policy, assets) in mary_value.clone().to_vec().iter() {
|
||||
if policy == search_policy {
|
||||
return Some(assets.clone());
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn find_assets(assets: &KeyValuePairs<AssetName, Coin>, asset_name: &AssetName) -> Option<Coin> {
|
||||
for (an, amount) in assets.clone().to_vec().iter() {
|
||||
if an == asset_name {
|
||||
return Some(*amount);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn check_fees(tx_body: &TransactionBody, size: &u64, fee_policy: &FeePolicy) -> ValidationResult {
|
||||
fn check_fees(
|
||||
tx_body: &TransactionBody,
|
||||
size: &u64,
|
||||
prot_pps: &ShelleyProtParams,
|
||||
) -> ValidationResult {
|
||||
let fee_policy: &FeePolicy = &prot_pps.fee_policy;
|
||||
if tx_body.fee < fee_policy.summand + fee_policy.multiplier * size {
|
||||
return Err(Shelley(FeesBelowMin));
|
||||
return Err(ShelleyMA(FeesBelowMin));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn check_network_id(tx_body: &TransactionBody, network_id: &u8) -> ValidationResult {
|
||||
for output in tx_body.outputs.iter() {
|
||||
let addr: ShelleyAddress = get_shelley_address(Vec::<u8>::from(output.address.clone()))?;
|
||||
let addr: ShelleyAddress =
|
||||
get_shelley_address(&output.address).ok_or(ShelleyMA(AddressDecoding))?;
|
||||
if addr.network().value() != *network_id {
|
||||
return Err(Shelley(WrongNetworkID));
|
||||
return Err(ShelleyMA(WrongNetworkID));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_shelley_address(address: Vec<u8>) -> Result<ShelleyAddress, ValidationError> {
|
||||
match Address::from_bytes(&address) {
|
||||
Ok(Address::Shelley(sa)) => Ok(sa),
|
||||
Ok(_) => Err(Shelley(WrongEraOutput)),
|
||||
Err(_) => Err(Shelley(AddressDecoding)),
|
||||
}
|
||||
}
|
||||
|
||||
fn check_metadata(
|
||||
auxiliary_data_hash: &Option<Bytes>,
|
||||
auxiliary_data_cbor: &Option<&[u8]>,
|
||||
) -> ValidationResult {
|
||||
match (auxiliary_data_hash, auxiliary_data_cbor) {
|
||||
fn check_metadata(tx_body: &TransactionBody, mtx: &MintedTx) -> ValidationResult {
|
||||
match (&tx_body.auxiliary_data_hash, extract_auxiliary_data(mtx)) {
|
||||
(Some(metadata_hash), Some(metadata)) => {
|
||||
if metadata_hash.as_slice()
|
||||
== pallas_crypto::hash::Hasher::<256>::hash(metadata).as_ref()
|
||||
{
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Shelley(MetadataHash))
|
||||
Err(ShelleyMA(MetadataHash))
|
||||
}
|
||||
}
|
||||
(None, None) => Ok(()),
|
||||
_ => Err(Shelley(MetadataHash)),
|
||||
_ => Err(ShelleyMA(MetadataHash)),
|
||||
}
|
||||
}
|
||||
|
||||
fn check_witnesses(
|
||||
tx_body: &TransactionBody,
|
||||
utxos: &UTxOs,
|
||||
tx_wits: &MintedWitnessSet,
|
||||
utxos: &UTxOs,
|
||||
) -> ValidationResult {
|
||||
let wits: &mut Vec<(bool, VKeyWitness)> = &mut mk_vkwitness_check_list(&tx_wits.vkeywitness)?;
|
||||
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());
|
||||
for input in tx_body.inputs.iter() {
|
||||
match utxos.get(&MultiEraInput::from_alonzo_compatible(input)) {
|
||||
Some(multi_era_output) => {
|
||||
if let Some(alonzo_comp_output) = MultiEraOutput::as_alonzo(multi_era_output) {
|
||||
match get_payment_part(alonzo_comp_output)? {
|
||||
match get_payment_part(alonzo_comp_output).ok_or(ShelleyMA(AddressDecoding))? {
|
||||
ShelleyPaymentPart::Key(payment_key_hash) => {
|
||||
check_verification_key_witness(&payment_key_hash, tx_hash, wits)?
|
||||
check_vk_wit(&payment_key_hash, tx_hash, vk_wits)?
|
||||
}
|
||||
ShelleyPaymentPart::Script(script_hash) => check_native_script_witness(
|
||||
&script_hash,
|
||||
|
|
@ -411,50 +236,28 @@ fn check_witnesses(
|
|||
}
|
||||
}
|
||||
}
|
||||
None => return Err(Shelley(InputNotInUTxO)),
|
||||
None => return Err(ShelleyMA(InputNotInUTxO)),
|
||||
}
|
||||
}
|
||||
check_remaining_verification_key_witnesses(wits, tx_hash)
|
||||
check_remaining_vk_wits(vk_wits, tx_hash)
|
||||
}
|
||||
|
||||
fn mk_vkwitness_check_list(
|
||||
wits: &Option<Vec<VKeyWitness>>,
|
||||
) -> Result<Vec<(bool, VKeyWitness)>, ValidationError> {
|
||||
Ok(wits
|
||||
.clone()
|
||||
.ok_or(Shelley(MissingVKWitness))?
|
||||
.iter()
|
||||
.map(|x| (false, x.clone()))
|
||||
.collect::<Vec<(bool, VKeyWitness)>>())
|
||||
}
|
||||
|
||||
fn get_payment_part(tx_out: &TransactionOutput) -> Result<ShelleyPaymentPart, ValidationError> {
|
||||
let addr: ShelleyAddress = get_shelley_address(Vec::<u8>::from(tx_out.address.clone()))?;
|
||||
Ok(addr.payment().clone())
|
||||
}
|
||||
|
||||
fn check_verification_key_witness(
|
||||
fn check_vk_wit(
|
||||
payment_key_hash: &PaymentKeyHash,
|
||||
data_to_verify: &Vec<u8>,
|
||||
wits: &mut Vec<(bool, VKeyWitness)>,
|
||||
data_to_verify: &[u8],
|
||||
wits: &mut [(bool, VKeyWitness)],
|
||||
) -> ValidationResult {
|
||||
for (found, VKeyWitness { vkey, signature }) in wits {
|
||||
if pallas_crypto::hash::Hasher::<224>::hash(vkey) == *payment_key_hash {
|
||||
let mut public_key_source: [u8; PublicKey::SIZE] = [0; PublicKey::SIZE];
|
||||
public_key_source.copy_from_slice(vkey.as_slice());
|
||||
let public_key: PublicKey = From::<[u8; PublicKey::SIZE]>::from(public_key_source);
|
||||
let mut signature_source: [u8; Signature::SIZE] = [0; Signature::SIZE];
|
||||
signature_source.copy_from_slice(signature.as_slice());
|
||||
let sig: Signature = From::<[u8; Signature::SIZE]>::from(signature_source);
|
||||
if public_key.verify(data_to_verify, &sig) {
|
||||
for (found, vkey_wit) in wits {
|
||||
if pallas_crypto::hash::Hasher::<224>::hash(&vkey_wit.vkey.clone()) == *payment_key_hash {
|
||||
if verify_signature(vkey_wit, data_to_verify) {
|
||||
*found = true;
|
||||
return Ok(());
|
||||
} else {
|
||||
return Err(Shelley(WrongSignature));
|
||||
return Err(ShelleyMA(WrongSignature));
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(Shelley(MissingVKWitness))
|
||||
Err(ShelleyMA(MissingVKWitness))
|
||||
}
|
||||
|
||||
fn check_native_script_witness(
|
||||
|
|
@ -470,60 +273,53 @@ fn check_native_script_witness(
|
|||
return Ok(());
|
||||
}
|
||||
}
|
||||
Err(Shelley(MissingScriptWitness))
|
||||
Err(ShelleyMA(MissingScriptWitness))
|
||||
}
|
||||
None => Err(Shelley(MissingScriptWitness)),
|
||||
None => Err(ShelleyMA(MissingScriptWitness)),
|
||||
}
|
||||
}
|
||||
|
||||
fn check_remaining_verification_key_witnesses(
|
||||
fn check_remaining_vk_wits(
|
||||
wits: &mut Vec<(bool, VKeyWitness)>,
|
||||
data_to_verify: &Vec<u8>,
|
||||
data_to_verify: &[u8],
|
||||
) -> ValidationResult {
|
||||
for (covered, VKeyWitness { vkey, signature }) in wits {
|
||||
for (covered, vkey_wit) in wits {
|
||||
if !*covered {
|
||||
let mut public_key_source: [u8; PublicKey::SIZE] = [0; PublicKey::SIZE];
|
||||
public_key_source.copy_from_slice(vkey.as_slice());
|
||||
let public_key: PublicKey = From::<[u8; PublicKey::SIZE]>::from(public_key_source);
|
||||
let mut signature_source: [u8; Signature::SIZE] = [0; Signature::SIZE];
|
||||
signature_source.copy_from_slice(signature.as_slice());
|
||||
let sig: Signature = From::<[u8; Signature::SIZE]>::from(signature_source);
|
||||
if !public_key.verify(data_to_verify, &sig) {
|
||||
return Err(Shelley(WrongSignature));
|
||||
if verify_signature(vkey_wit, data_to_verify) {
|
||||
return Ok(());
|
||||
} else {
|
||||
return Err(ShelleyMA(WrongSignature));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn check_minting(
|
||||
values: &Option<Multiasset<i64>>,
|
||||
scripts: &Option<Vec<NativeScript>>,
|
||||
) -> ValidationResult {
|
||||
match (values, scripts) {
|
||||
(None, _) => Ok(()),
|
||||
(Some(_), None) => Err(Shelley(MintingLacksPolicy)),
|
||||
(Some(minted_value), Some(native_script_wits)) => {
|
||||
fn check_minting(tx_body: &TransactionBody, mtx: &MintedTx) -> ValidationResult {
|
||||
match &tx_body.mint {
|
||||
Some(minted_value) => {
|
||||
let native_script_wits: Vec<NativeScript> =
|
||||
match &mtx.transaction_witness_set.native_script {
|
||||
None => Vec::new(),
|
||||
Some(keep_raw_native_script_wits) => keep_raw_native_script_wits
|
||||
.iter()
|
||||
.map(|x| x.clone().unwrap())
|
||||
.collect(),
|
||||
};
|
||||
for (policy, _) in minted_value.iter() {
|
||||
if check_policy(policy, native_script_wits) {
|
||||
return Ok(());
|
||||
if native_script_wits
|
||||
.iter()
|
||||
.all(|script| compute_script_hash(script) != *policy)
|
||||
{
|
||||
return Err(ShelleyMA(MintingLacksPolicy));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
None => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
fn check_policy(policy: &PolicyId, native_script_wits: &[NativeScript]) -> bool {
|
||||
for script in native_script_wits.iter() {
|
||||
let hashed_script: PolicyId = compute_script_hash(script);
|
||||
if *policy == hashed_script {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn compute_script_hash(script: &NativeScript) -> PolicyId {
|
||||
let mut payload = Vec::new();
|
||||
let _ = encode(script, &mut payload);
|
||||
|
|
|
|||
|
|
@ -1,98 +0,0 @@
|
|||
//! Base types used for validating transactions in each era.
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub use pallas_traverse::{MultiEraInput, MultiEraOutput};
|
||||
|
||||
pub type UTxOs<'b> = HashMap<MultiEraInput<'b>, MultiEraOutput<'b>>;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ByronProtParams {
|
||||
pub fee_policy: FeePolicy,
|
||||
pub max_tx_size: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ShelleyProtParams {
|
||||
pub fee_policy: FeePolicy,
|
||||
pub max_tx_size: u64,
|
||||
pub min_lovelace: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FeePolicy {
|
||||
pub summand: u64,
|
||||
pub multiplier: u64,
|
||||
}
|
||||
|
||||
// TODO: add variants for the other eras.
|
||||
#[derive(Debug)]
|
||||
#[non_exhaustive]
|
||||
pub enum MultiEraProtParams {
|
||||
Byron(ByronProtParams),
|
||||
Shelley(ShelleyProtParams),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Environment {
|
||||
pub prot_params: MultiEraProtParams,
|
||||
pub prot_magic: u32,
|
||||
pub block_slot: u64,
|
||||
pub network_id: u8,
|
||||
}
|
||||
|
||||
#[non_exhaustive]
|
||||
pub enum SigningTag {
|
||||
Tx = 0x01,
|
||||
RedeemTx = 0x02,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[non_exhaustive]
|
||||
pub enum ValidationError {
|
||||
TxAndProtParamsDiffer,
|
||||
Byron(ByronError),
|
||||
Shelley(ShelleyMAError),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[non_exhaustive]
|
||||
pub enum ByronError {
|
||||
TxInsEmpty,
|
||||
TxOutsEmpty,
|
||||
InputNotInUTxO,
|
||||
OutputWithoutLovelace,
|
||||
UnknownTxSize,
|
||||
UnableToComputeFees,
|
||||
FeesBelowMin,
|
||||
MaxTxSizeExceeded,
|
||||
UnableToProcessWitness,
|
||||
MissingWitness,
|
||||
WrongSignature,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[non_exhaustive]
|
||||
pub enum ShelleyMAError {
|
||||
TxInsEmpty,
|
||||
InputNotInUTxO,
|
||||
TTLExceeded,
|
||||
AlonzoCompNotShelley,
|
||||
UnknownTxSize,
|
||||
MaxTxSizeExceeded,
|
||||
ValueNotShelley,
|
||||
MinLovelaceUnreached,
|
||||
PreservationOfValue,
|
||||
NegativeValue,
|
||||
FeesBelowMin,
|
||||
WrongEraOutput,
|
||||
AddressDecoding,
|
||||
WrongNetworkID,
|
||||
MetadataHash,
|
||||
MissingVKWitness,
|
||||
MissingScriptWitness,
|
||||
WrongSignature,
|
||||
MintingLacksPolicy,
|
||||
}
|
||||
|
||||
pub type ValidationResult = Result<(), ValidationError>;
|
||||
260
pallas-applying/src/utils.rs
Normal file
260
pallas-applying/src/utils.rs
Normal file
|
|
@ -0,0 +1,260 @@
|
|||
//! Base types used for validating transactions in each era.
|
||||
|
||||
pub mod environment;
|
||||
pub mod validation;
|
||||
|
||||
pub use environment::*;
|
||||
use pallas_addresses::{Address, ShelleyAddress, ShelleyPaymentPart};
|
||||
use pallas_codec::{
|
||||
minicbor::encode,
|
||||
utils::{Bytes, KeepRaw, KeyValuePairs},
|
||||
};
|
||||
use pallas_crypto::key::ed25519::{PublicKey, Signature};
|
||||
use pallas_primitives::alonzo::{
|
||||
AssetName, AuxiliaryData, Coin, MintedTx, Multiasset, NetworkId, PolicyId, TransactionBody,
|
||||
TransactionOutput, VKeyWitness, Value,
|
||||
};
|
||||
use pallas_traverse::{MultiEraInput, MultiEraOutput};
|
||||
use std::collections::HashMap;
|
||||
use std::ops::Deref;
|
||||
pub use validation::*;
|
||||
|
||||
pub type UTxOs<'b> = HashMap<MultiEraInput<'b>, MultiEraOutput<'b>>;
|
||||
|
||||
pub fn get_alonzo_comp_tx_size(tx_body: &TransactionBody) -> Option<u64> {
|
||||
let mut buff: Vec<u8> = Vec::new();
|
||||
match encode(tx_body, &mut buff) {
|
||||
Ok(()) => Some(buff.len() as u64),
|
||||
Err(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn empty_value() -> Value {
|
||||
Value::Multiasset(0, Multiasset::<Coin>::from(Vec::new()))
|
||||
}
|
||||
|
||||
pub fn add_values(
|
||||
first: &Value,
|
||||
second: &Value,
|
||||
err: &ValidationError,
|
||||
) -> Result<Value, ValidationError> {
|
||||
match (first, second) {
|
||||
(Value::Coin(f), Value::Coin(s)) => Ok(Value::Coin(f + s)),
|
||||
(Value::Multiasset(f, fma), Value::Coin(s)) => Ok(Value::Multiasset(f + s, fma.clone())),
|
||||
(Value::Coin(f), Value::Multiasset(s, sma)) => Ok(Value::Multiasset(f + s, sma.clone())),
|
||||
(Value::Multiasset(f, fma), Value::Multiasset(s, sma)) => Ok(Value::Multiasset(
|
||||
f + s,
|
||||
coerce_to_coin(
|
||||
&add_multiasset_values(&coerce_to_i64(fma), &coerce_to_i64(sma)),
|
||||
err,
|
||||
)?,
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_minted_value(
|
||||
base_value: &Value,
|
||||
minted_value: &Multiasset<i64>,
|
||||
err: &ValidationError,
|
||||
) -> Result<Value, ValidationError> {
|
||||
match base_value {
|
||||
Value::Coin(n) => Ok(Value::Multiasset(*n, coerce_to_coin(minted_value, err)?)),
|
||||
Value::Multiasset(n, mary_base_value) => Ok(Value::Multiasset(
|
||||
*n,
|
||||
coerce_to_coin(
|
||||
&add_multiasset_values(&coerce_to_i64(mary_base_value), minted_value),
|
||||
err,
|
||||
)?,
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
fn coerce_to_i64(value: &Multiasset<Coin>) -> Multiasset<i64> {
|
||||
let mut res: Vec<(PolicyId, KeyValuePairs<AssetName, i64>)> = Vec::new();
|
||||
for (policy, assets) in value.clone().to_vec().iter() {
|
||||
let mut aa: Vec<(AssetName, i64)> = Vec::new();
|
||||
for (asset_name, amount) in assets.clone().to_vec().iter() {
|
||||
aa.push((asset_name.clone(), *amount as i64));
|
||||
}
|
||||
res.push((*policy, KeyValuePairs::<AssetName, i64>::from(aa)));
|
||||
}
|
||||
KeyValuePairs::<PolicyId, KeyValuePairs<AssetName, i64>>::from(res)
|
||||
}
|
||||
|
||||
fn coerce_to_coin(
|
||||
value: &Multiasset<i64>,
|
||||
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() {
|
||||
let mut aa: Vec<(AssetName, Coin)> = Vec::new();
|
||||
for (asset_name, amount) in assets.clone().to_vec().iter() {
|
||||
if *amount < 0 {
|
||||
return Err(err.clone());
|
||||
}
|
||||
aa.push((asset_name.clone(), *amount as u64));
|
||||
}
|
||||
res.push((*policy, KeyValuePairs::<AssetName, Coin>::from(aa)));
|
||||
}
|
||||
Ok(KeyValuePairs::<PolicyId, KeyValuePairs<AssetName, Coin>>::from(res))
|
||||
}
|
||||
|
||||
fn add_multiasset_values(first: &Multiasset<i64>, second: &Multiasset<i64>) -> Multiasset<i64> {
|
||||
let mut res: HashMap<PolicyId, HashMap<AssetName, i64>> = HashMap::new();
|
||||
for (policy, new_assets) in first.iter() {
|
||||
match res.get(policy) {
|
||||
Some(old_assets) => res.insert(*policy, add_same_policy_assets(old_assets, new_assets)),
|
||||
None => res.insert(*policy, add_same_policy_assets(&HashMap::new(), new_assets)),
|
||||
};
|
||||
}
|
||||
for (policy, new_assets) in second.iter() {
|
||||
match res.get(policy) {
|
||||
Some(old_assets) => res.insert(*policy, add_same_policy_assets(old_assets, new_assets)),
|
||||
None => res.insert(*policy, add_same_policy_assets(&HashMap::new(), new_assets)),
|
||||
};
|
||||
}
|
||||
wrap_multiasset(res)
|
||||
}
|
||||
|
||||
fn add_same_policy_assets(
|
||||
old_assets: &HashMap<AssetName, i64>,
|
||||
new_assets: &KeyValuePairs<AssetName, i64>,
|
||||
) -> HashMap<AssetName, i64> {
|
||||
let mut res: HashMap<AssetName, i64> = old_assets.clone();
|
||||
for (asset_name, new_amount) in new_assets.iter() {
|
||||
match res.get(asset_name) {
|
||||
Some(old_amount) => res.insert(asset_name.clone(), old_amount + *new_amount),
|
||||
None => res.insert(asset_name.clone(), *new_amount),
|
||||
};
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
fn wrap_multiasset(input: HashMap<PolicyId, HashMap<AssetName, i64>>) -> Multiasset<i64> {
|
||||
Multiasset::<i64>::from(
|
||||
input
|
||||
.into_iter()
|
||||
.map(|(policy, assets)| {
|
||||
(
|
||||
policy,
|
||||
KeyValuePairs::<AssetName, i64>::from(
|
||||
assets.into_iter().collect::<Vec<(AssetName, i64)>>(),
|
||||
),
|
||||
)
|
||||
})
|
||||
.collect::<Vec<(PolicyId, KeyValuePairs<AssetName, i64>)>>(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn values_are_equal(first: &Value, second: &Value) -> bool {
|
||||
match (first, second) {
|
||||
(Value::Coin(f), Value::Coin(s)) => f == s,
|
||||
(Value::Multiasset(..), Value::Coin(..)) => false,
|
||||
(Value::Coin(..), Value::Multiasset(..)) => false,
|
||||
(Value::Multiasset(f, fma), Value::Multiasset(s, sma)) => {
|
||||
if f != s {
|
||||
false
|
||||
} else {
|
||||
for (fpolicy, fassets) in fma.iter() {
|
||||
match find_policy(sma, fpolicy) {
|
||||
Some(sassets) => {
|
||||
for (fasset_name, famount) in fassets.iter() {
|
||||
match find_assets(&sassets, fasset_name) {
|
||||
Some(samount) => {
|
||||
if *famount != samount {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
None => return false,
|
||||
};
|
||||
}
|
||||
}
|
||||
None => return false,
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn find_policy(
|
||||
mary_value: &Multiasset<Coin>,
|
||||
search_policy: &PolicyId,
|
||||
) -> Option<KeyValuePairs<AssetName, Coin>> {
|
||||
for (policy, assets) in mary_value.clone().to_vec().iter() {
|
||||
if policy == search_policy {
|
||||
return Some(assets.clone());
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn find_assets(assets: &KeyValuePairs<AssetName, Coin>, asset_name: &AssetName) -> Option<Coin> {
|
||||
for (an, amount) in assets.clone().to_vec().iter() {
|
||||
if an == asset_name {
|
||||
return Some(*amount);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn get_lovelace_from_alonzo_val(val: &Value) -> Coin {
|
||||
match val {
|
||||
Value::Coin(res) => *res,
|
||||
Value::Multiasset(res, _) => *res,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_network_id_value(network_id: NetworkId) -> u8 {
|
||||
match network_id {
|
||||
NetworkId::One => 1,
|
||||
NetworkId::Two => 2,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mk_alonzo_vk_wits_check_list(
|
||||
wits: &Option<Vec<VKeyWitness>>,
|
||||
err: ValidationError,
|
||||
) -> Result<Vec<(bool, VKeyWitness)>, ValidationError> {
|
||||
Ok(wits
|
||||
.clone()
|
||||
.ok_or(err)?
|
||||
.iter()
|
||||
.map(|x| (false, x.clone()))
|
||||
.collect::<Vec<(bool, VKeyWitness)>>())
|
||||
}
|
||||
|
||||
pub fn verify_signature(vk_wit: &VKeyWitness, data_to_verify: &[u8]) -> bool {
|
||||
let mut public_key_source: [u8; PublicKey::SIZE] = [0; PublicKey::SIZE];
|
||||
public_key_source.copy_from_slice(vk_wit.vkey.as_slice());
|
||||
let public_key: PublicKey = From::<[u8; PublicKey::SIZE]>::from(public_key_source);
|
||||
let mut signature_source: [u8; Signature::SIZE] = [0; Signature::SIZE];
|
||||
signature_source.copy_from_slice(vk_wit.signature.as_slice());
|
||||
let sig: Signature = From::<[u8; Signature::SIZE]>::from(signature_source);
|
||||
public_key.verify(data_to_verify, &sig)
|
||||
}
|
||||
|
||||
pub fn get_payment_part(tx_out: &TransactionOutput) -> Option<ShelleyPaymentPart> {
|
||||
let addr: ShelleyAddress = get_shelley_address(Bytes::deref(&tx_out.address))?;
|
||||
Some(addr.payment().clone())
|
||||
}
|
||||
|
||||
pub fn get_shelley_address(address: &[u8]) -> Option<ShelleyAddress> {
|
||||
match Address::from_bytes(address) {
|
||||
Ok(Address::Shelley(sa)) => Some(sa),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn extract_auxiliary_data<'a>(mtx: &'a MintedTx) -> Option<&'a [u8]> {
|
||||
Option::<KeepRaw<AuxiliaryData>>::from((mtx.auxiliary_data).clone())
|
||||
.as_ref()
|
||||
.map(KeepRaw::raw_cbor)
|
||||
}
|
||||
|
||||
pub fn get_val_size_in_words(val: &Value) -> u64 {
|
||||
let mut tx_buf: Vec<u8> = Vec::new();
|
||||
let _ = encode(val, &mut tx_buf);
|
||||
(tx_buf.len() as u64 + 7) / 8 // ceiling of the result of dividing
|
||||
}
|
||||
70
pallas-applying/src/utils/environment.rs
Normal file
70
pallas-applying/src/utils/environment.rs
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
//! Types used for representing the environment required for validation in each
|
||||
//! era.
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Environment {
|
||||
pub prot_params: MultiEraProtParams,
|
||||
pub prot_magic: u32,
|
||||
pub block_slot: u64,
|
||||
pub network_id: u8,
|
||||
}
|
||||
|
||||
// TODO: add variants for the other eras.
|
||||
#[derive(Debug)]
|
||||
#[non_exhaustive]
|
||||
pub enum MultiEraProtParams {
|
||||
Byron(ByronProtParams),
|
||||
Shelley(ShelleyProtParams),
|
||||
Alonzo(AlonzoProtParams),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ByronProtParams {
|
||||
pub fee_policy: FeePolicy,
|
||||
pub max_tx_size: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ShelleyProtParams {
|
||||
pub fee_policy: FeePolicy,
|
||||
pub max_tx_size: u64,
|
||||
pub min_lovelace: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FeePolicy {
|
||||
pub summand: u64,
|
||||
pub multiplier: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AlonzoProtParams {
|
||||
pub fee_policy: FeePolicy,
|
||||
pub max_tx_size: u64,
|
||||
pub max_block_ex_mem: u64,
|
||||
pub max_block_ex_steps: u64,
|
||||
pub max_tx_ex_mem: u32,
|
||||
pub max_tx_ex_steps: u64,
|
||||
pub max_val_size: u64,
|
||||
pub collateral_percent: u64,
|
||||
pub max_collateral_inputs: u64,
|
||||
pub coins_per_utxo_word: u64,
|
||||
}
|
||||
|
||||
impl Environment {
|
||||
pub fn prot_params(&self) -> &MultiEraProtParams {
|
||||
&self.prot_params
|
||||
}
|
||||
|
||||
pub fn prot_magic(&self) -> &u32 {
|
||||
&self.prot_magic
|
||||
}
|
||||
|
||||
pub fn block_slot(&self) -> &u64 {
|
||||
&self.block_slot
|
||||
}
|
||||
|
||||
pub fn network_id(&self) -> &u8 {
|
||||
&self.network_id
|
||||
}
|
||||
}
|
||||
94
pallas-applying/src/utils/validation.rs
Normal file
94
pallas-applying/src/utils/validation.rs
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
//! Types for validating transactions in each era.
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[non_exhaustive]
|
||||
pub enum ValidationError {
|
||||
TxAndProtParamsDiffer,
|
||||
Byron(ByronError),
|
||||
ShelleyMA(ShelleyMAError),
|
||||
Alonzo(AlonzoError),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[non_exhaustive]
|
||||
pub enum ByronError {
|
||||
TxInsEmpty,
|
||||
TxOutsEmpty,
|
||||
InputNotInUTxO,
|
||||
OutputWithoutLovelace,
|
||||
UnknownTxSize,
|
||||
UnableToComputeFees,
|
||||
FeesBelowMin,
|
||||
MaxTxSizeExceeded,
|
||||
UnableToProcessWitness,
|
||||
MissingWitness,
|
||||
WrongSignature,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[non_exhaustive]
|
||||
pub enum ShelleyMAError {
|
||||
TxInsEmpty,
|
||||
InputNotInUTxO,
|
||||
TTLExceeded,
|
||||
AlonzoCompNotShelley,
|
||||
UnknownTxSize,
|
||||
MaxTxSizeExceeded,
|
||||
ValueNotShelley,
|
||||
MinLovelaceUnreached,
|
||||
PreservationOfValue,
|
||||
NegativeValue,
|
||||
FeesBelowMin,
|
||||
WrongEraOutput,
|
||||
AddressDecoding,
|
||||
WrongNetworkID,
|
||||
MetadataHash,
|
||||
MissingVKWitness,
|
||||
MissingScriptWitness,
|
||||
WrongSignature,
|
||||
MintingLacksPolicy,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[non_exhaustive]
|
||||
pub enum AlonzoError {
|
||||
UnknownTxSize,
|
||||
TxInsEmpty,
|
||||
InputNotInUTxO,
|
||||
CollateralNotInUTxO,
|
||||
BlockExceedsValInt,
|
||||
BlockPrecedesValInt,
|
||||
ValIntUpperBoundMissing,
|
||||
FeeBelowMin,
|
||||
CollateralMissing,
|
||||
TooManyCollaterals,
|
||||
CollateralNotVKeyLocked,
|
||||
AddressDecoding,
|
||||
CollateralMinLovelace,
|
||||
NonLovelaceCollateral,
|
||||
NegativeValue,
|
||||
PreservationOfValue,
|
||||
MinLovelaceUnreached,
|
||||
MaxValSizeExceeded,
|
||||
OutputWrongNetworkID,
|
||||
TxWrongNetworkID,
|
||||
RedeemerMissing,
|
||||
TxExUnitsExceeded,
|
||||
MaxTxSizeExceeded,
|
||||
VKWitnessMissing,
|
||||
VKWrongSignature,
|
||||
ReqSignerMissing,
|
||||
ReqSignerWrongSig,
|
||||
ScriptWitnessMissing,
|
||||
MintingLacksPolicy,
|
||||
InputDecoding,
|
||||
UnneededNativeScript,
|
||||
UnneededPlutusScript,
|
||||
UnneededRedeemer,
|
||||
DatumMissing,
|
||||
UnneededDatum,
|
||||
MetadataHash,
|
||||
ScriptIntegrityHash,
|
||||
}
|
||||
|
||||
pub type ValidationResult = Result<(), ValidationError>;
|
||||
Loading…
Add table
Add a link
Reference in a new issue