diff --git a/pallas-applying/src/alonzo.rs b/pallas-applying/src/alonzo.rs index a3da8bdcd..d52adff7d 100644 --- a/pallas-applying/src/alonzo.rs +++ b/pallas-applying/src/alonzo.rs @@ -12,8 +12,8 @@ use pallas_addresses::{Address, ShelleyAddress, ShelleyPaymentPart}; use pallas_codec::{minicbor::encode, utils::KeepRaw}; use pallas_primitives::{ alonzo::{ - MintedTx, MintedWitnessSet, NativeScript, PlutusData, PlutusScript, TransactionBody, - TransactionInput, TransactionOutput, VKeyWitness, Value, + MintedTx, MintedWitnessSet, NativeScript, PlutusData, PlutusScript, Redeemer, + TransactionBody, TransactionInput, TransactionOutput, VKeyWitness, Value, }, byron::TxOut, }; @@ -376,7 +376,24 @@ fn check_tx_size(_size: &u64, _prot_pps: &AlonzoProtParams) -> ValidationResult // 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 { +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(()) } diff --git a/pallas-applying/src/utils/environment.rs b/pallas-applying/src/utils/environment.rs index 47748594d..8357497d2 100644 --- a/pallas-applying/src/utils/environment.rs +++ b/pallas-applying/src/utils/environment.rs @@ -46,7 +46,7 @@ pub struct AlonzoProtParams { pub languages: Vec, pub max_block_ex_mem: u64, pub max_block_ex_steps: u64, - pub max_tx_ex_mem: u64, + pub max_tx_ex_mem: u32, pub max_tx_ex_steps: u64, pub max_val_size: u64, pub collateral_percent: u64, diff --git a/pallas-applying/src/utils/validation.rs b/pallas-applying/src/utils/validation.rs index 238eea122..e19e3583c 100644 --- a/pallas-applying/src/utils/validation.rs +++ b/pallas-applying/src/utils/validation.rs @@ -71,6 +71,8 @@ pub enum AlonzoError { OutputMinLovelace, OutputWrongNetworkID, TxWrongNetworkID, + RedeemerMissing, + TxExUnitsExceeded, } pub type ValidationResult = Result<(), ValidationError>; diff --git a/pallas-applying/tests/alonzo.rs b/pallas-applying/tests/alonzo.rs index ec63bb7be..b2f569e54 100644 --- a/pallas-applying/tests/alonzo.rs +++ b/pallas-applying/tests/alonzo.rs @@ -1220,4 +1220,115 @@ mod alonzo_tests { }, } } + + #[test] + // Same as successful_mainnet_tx_with_plutus_script, except that + fn tx_ex_units_exceeded() { + let cbor_bytes: Vec = cbor_to_bytes(include_str!("../../test_data/alonzo2.tx")); + let mtx: MintedTx = minted_tx_from_cbor(&cbor_bytes); + let metx: MultiEraTx = MultiEraTx::from_alonzo_compatible(&mtx, Era::Alonzo); + let mut utxos: UTxOs = mk_utxo_for_alonzo_compatible_tx( + &mtx.transaction_body, + &[ + ( + // (tx hash, tx output index): + // (117325a52d60be3a1e4072af39d9e630bf61ce59d315d6c1bf4c4d140f8066ea, 0) + String::from(include_str!("../../test_data/alonzo2.0.address")), + Value::Multiasset( + 1724100, + KeyValuePairs::from(Vec::from([( + Hash::<28>::new([ + 176, 1, 7, 107, 52, 168, 126, 125, 72, 236, 70, 112, 58, 111, 80, + 249, 50, 137, 88, 42, 217, 189, 190, 255, 127, 30, 50, 149, + ]), + KeyValuePairs::from(Vec::from([( + Bytes::from(hex::decode("4879706562656173747332343233").unwrap()), + 1, + )])), + )])), + ), + None, + ), + ( + // (tx hash, tx output index): + // (d2f9764fa93ae5bcabbb65c7a2f97d1e31188064ae3d2ba1462114453928dd99, 0) + String::from(include_str!("../../test_data/alonzo2.1.address")), + Value::Coin(20292207), + None, + ), + ( + // (tx hash, tx output index): + // (9fab354c2825376a943e505d13a3861e4d9ad3e177028d7bb2bbabce5453fa11, 0) + String::from(include_str!("../../test_data/alonzo2.2.address")), + Value::Coin(20292207), + None, + ), + ( + // (tx hash, tx output index): + // (3077a999b1d22cb1a4e5ee485adbde6a4596704a96384fbc9727028b8b28ba47, 0) + String::from(include_str!("../../test_data/alonzo2.3.address")), + Value::Coin(29792207), + None, + ), + ( + // (tx hash, tx output index): + // (b231aca45a38add7378d2ed7a0822626fee3396821e8791a5af5926807db962d, 0) + String::from(include_str!("../../test_data/alonzo2.4.address")), + Value::Coin(29792207), + None, + ), + ( + // (tx hash, tx output index): + // (11579a841b3c7a64aa057c9adf993ef42520570450499b0a724c7ef706b2a435, 0) + String::from(include_str!("../../test_data/alonzo2.5.address")), + Value::Coin(61233231), + None, + ), + ( + // (tx hash, tx output index): + // (b857f98162b753d117464c499d53bbbfec5aa38b94bd624e295a7e3fddc77130, 0) + String::from(include_str!("../../test_data/alonzo2.6.address")), + Value::Coin(20292207), + None, + ), + ], + ); + add_collateral( + &mtx.transaction_body, + &mut utxos, + &[( + String::from(include_str!("../../test_data/alonzo2.collateral.address")), + Value::Coin(5000000), + None, + )], + ); + let env: Environment = Environment { + prot_params: MultiEraProtParams::Alonzo(AlonzoProtParams { + fee_policy: FeePolicy { + summand: 155381, + multiplier: 44, + }, + max_tx_size: 16384, + languages: vec![Language::PlutusV1, Language::PlutusV2], + max_block_ex_mem: 50000000, + max_block_ex_steps: 40000000000, + max_tx_ex_mem: 4649575, // 1 lower than that of the transaction + max_tx_ex_steps: 1765246503, // 1 lower than that of the transaction + max_val_size: 5000, + collateral_percent: 150, + max_collateral_inputs: 3, + coints_per_utxo_word: 34482, + }), + prot_magic: 764824073, + block_slot: 58924928, + network_id: 1, + }; + match validate(&metx, &utxos, &env) { + Ok(()) => assert!(false, "Transaction ex units should be below maximum"), + Err(err) => match err { + Alonzo(TxExUnitsExceeded) => (), + _ => assert!(false, "Unexpected error ({:?})", err), + }, + } + } }