Skip to content

Commit

Permalink
feat(applying): check all inputs in UTxO set
Browse files Browse the repository at this point in the history
  • Loading branch information
MaicoLeberle committed Nov 16, 2023
1 parent 4d9d6e4 commit d43f540
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 13 deletions.
16 changes: 13 additions & 3 deletions pallas-applying/src/shelley.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
//! Utilities required for Shelley-era transaction validation.
use crate::types::{ShelleyProtParams, UTxOs, ValidationError, ValidationResult};

use pallas_primitives::alonzo::{MintedTx, TransactionBody};
use pallas_traverse::MultiEraInput;

// TODO: implement each of the validation rules.
pub fn validate_shelley_tx(
mtx: &MintedTx,
_utxos: &UTxOs,
utxos: &UTxOs,
_prot_pps: &ShelleyProtParams,
_prot_magic: &u32,
) -> ValidationResult {
let tx_body: &TransactionBody = &mtx.transaction_body;
check_ins_not_empty(tx_body)
check_ins_not_empty(tx_body)?;
check_ins_in_utxos(tx_body, utxos)
}

fn check_ins_not_empty(tx_body: &TransactionBody) -> ValidationResult {
Expand All @@ -21,3 +22,12 @@ fn check_ins_not_empty(tx_body: &TransactionBody) -> ValidationResult {
}
Ok(())
}

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(ValidationError::InputMissingInUTxO);
}
}
Ok(())
}
6 changes: 3 additions & 3 deletions pallas-applying/tests/byron.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,16 +49,15 @@ mod byron_tests {
}

#[test]
// Transaction hash: a9e4413a5fb61a7a43c7df006ffcaaf3f2ffc9541f54757023968c5a8f8294fd
fn successful_mainnet_tx_with_genesis_utxos() {
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/byron2.tx"));
let mtxp: MintedTxPayload = tx_from_cbor(&cbor_bytes);
let metx: MultiEraTx = MultiEraTx::from_byron(&mtxp);
let utxos: UTxOs = mk_utxo_for_single_input_tx(
&mtxp.transaction,
String::from(include_str!("../../test_data/byron2.address")),
// The number of lovelace in this input is irrelevant, since no fees have to be paid
// for this transaction.
1,
19999000000,
);
let env: Environment = Environment {
prot_params: MultiEraProtParams::Byron(ByronProtParams {
Expand All @@ -75,6 +74,7 @@ mod byron_tests {
}

#[test]
// Transaction hash: a06e5a0150e09f8983be2deafab9e04afc60d92e7110999eb672c903343f1e26
fn successful_mainnet_tx() {
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/byron1.tx"));
let mtxp: MintedTxPayload = tx_from_cbor(&cbor_bytes);
Expand Down
85 changes: 78 additions & 7 deletions pallas-applying/tests/shelley.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
use std::borrow::Cow;

use pallas_applying::{
types::{Environment, MultiEraProtParams, ShelleyProtParams, ValidationError},
validate, UTxOs,
};
use pallas_codec::minicbor::{
decode::{Decode, Decoder},
encode,
use pallas_codec::{
minicbor::{
decode::{Decode, Decoder},
encode,
},
utils::Bytes,
};
use pallas_crypto::hash::Hash;
use pallas_primitives::alonzo::{
MintedTx, TransactionBody, TransactionInput, TransactionOutput, Value,
};
use pallas_primitives::alonzo::{MintedTx, TransactionBody};
use pallas_traverse::{Era, MultiEraTx};
use pallas_traverse::{Era, MultiEraInput, MultiEraOutput, MultiEraTx};

#[cfg(test)]
mod byron_tests {
Expand All @@ -21,7 +29,32 @@ mod byron_tests {
pallas_codec::minicbor::decode::<MintedTx>(&tx_cbor[..]).unwrap()
}

// Careful: this function assumes tx_body has exactly one input.
fn mk_utxo_for_single_input_tx<'a>(
tx_body: &TransactionBody,
address: String,
amount: Value,
datum_hash: Option<Hash<32>>,
) -> UTxOs<'a> {
let tx_ins: &Vec<TransactionInput> = &tx_body.inputs;
assert_eq!(tx_ins.len(), 1, "Unexpected number of inputs.");
let tx_in: TransactionInput = tx_ins.first().unwrap().clone();
let address_bytes: Bytes = match hex::decode(address) {
Ok(bytes_vec) => Bytes::from(bytes_vec),
_ => panic!("Unable to decode input address."),
};
let tx_out: TransactionOutput = TransactionOutput {
address: address_bytes,
amount,
datum_hash,
};
let mut utxos: UTxOs = UTxOs::new();
add_to_utxo(&mut utxos, tx_in, tx_out);
utxos
}

#[test]
// Transaction hash: 50eba65e73c8c5f7b09f4ea28cf15dce169f3d1c322ca3deff03725f51518bb2
fn successful_mainnet_tx() {
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/shelley1.tx"));
let mtx: MintedTx = minted_tx_from_cbor(&cbor_bytes);
Expand All @@ -30,7 +63,12 @@ mod byron_tests {
prot_params: MultiEraProtParams::Shelley(ShelleyProtParams),
prot_magic: 764824073,
};
let utxos: UTxOs = UTxOs::new();
let utxos: UTxOs = mk_utxo_for_single_input_tx(
&mtx.transaction_body,
String::from(include_str!("../../test_data/shelley1.address")),
Value::Coin(2332267427205),
None,
);
match validate(&metx, &utxos, &env) {
Ok(()) => (),
Err(err) => assert!(false, "Unexpected error ({:?}).", err),
Expand All @@ -42,6 +80,12 @@ mod byron_tests {
fn empty_ins() {
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/shelley1.tx"));
let mut mtx: MintedTx = minted_tx_from_cbor(&cbor_bytes);
let utxos: UTxOs = mk_utxo_for_single_input_tx(
&mtx.transaction_body,
String::from(include_str!("../../test_data/shelley1.address")),
Value::Coin(2332267427205),
None,
);
// Clear the set of inputs in the transaction.
let mut tx_body: TransactionBody = (*mtx.transaction_body).clone();
tx_body.inputs = Vec::new();
Expand All @@ -57,7 +101,6 @@ mod byron_tests {
prot_params: MultiEraProtParams::Shelley(ShelleyProtParams),
prot_magic: 764824073,
};
let utxos: UTxOs = UTxOs::new();
match validate(&metx, &utxos, &env) {
Ok(()) => assert!(false, "Inputs set should not be empty."),
Err(err) => match err {
Expand All @@ -66,4 +109,32 @@ mod byron_tests {
},
}
}

#[test]
// The transaction is valid, but the UTxO set is empty.
fn unfound_utxo() {
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/shelley1.tx"));
let mtx: MintedTx = minted_tx_from_cbor(&cbor_bytes);
let metx: MultiEraTx = MultiEraTx::from_alonzo_compatible(&mtx, Era::Shelley);
let env: Environment = Environment {
prot_params: MultiEraProtParams::Shelley(ShelleyProtParams),
prot_magic: 764824073,
};
let utxos: UTxOs = UTxOs::new();
match validate(&metx, &utxos, &env) {
Ok(()) => assert!(false, "All inputs must be within the UTxO set."),
Err(err) => match err {
ValidationError::InputMissingInUTxO => (),
_ => assert!(false, "Unexpected error ({:?}).", err),
},
}
}
}

// Helper functions.
fn add_to_utxo<'a>(utxos: &mut UTxOs<'a>, tx_in: TransactionInput, tx_out: TransactionOutput) {
let multi_era_in: MultiEraInput = MultiEraInput::AlonzoCompatible(Box::new(Cow::Owned(tx_in)));
let multi_era_out: MultiEraOutput =
MultiEraOutput::AlonzoCompatible(Box::new(Cow::Owned(tx_out)));
utxos.insert(multi_era_in, multi_era_out);
}
1 change: 1 addition & 0 deletions test_data/shelley1.address
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
0129bb156d52d014bb444a14138cbee36044c6faed37d0c2d49d2358315c465cbf8c5536970e8a29bb7adcda0d663b20007d481813694c64ef

0 comments on commit d43f540

Please sign in to comment.