Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(applying): checking non-empty set of inputs and outputs #312

Merged
merged 2 commits into from
Nov 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion pallas-applying/docs/byron-validation-rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ Refer to the [Byron's ledger white paper](https://github.com/input-output-hk/car
- ***fees: Tx → ℕ*** gives the fees paid by a transaction, defined as follows:
- ***fees(tx) := balance (txIns(tx) ◁ utxo) − balance (txOuts(tx))***, where
- ***balance : P(TxOut) → ℕ*** gives the summation of all the lovelaces in a set of transaction outputs.

- **Serialization**:
- ***Bytes*** is the set of byte arrays (a.k.a. data, upon which signatures are built).
- ***⟦_⟧<sub>A</sub> : A -> Bytes*** takes an element of type ***A*** and returns a byte array resulting from serializing it.
Expand Down
24 changes: 20 additions & 4 deletions pallas-applying/src/byron.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,30 @@
//! Utilities required for Byron-era transaction validation.

use crate::types::{ByronProtParams, UTxOs, ValidationResult};
use crate::types::{ByronProtParams, UTxOs, ValidationError, ValidationResult};

use pallas_primitives::byron::MintedTxPayload;
use pallas_primitives::byron::{MintedTxPayload, Tx};

// TODO: implement each of the validation rules.
// TODO: implement missing validation rules.
pub fn validate_byron_tx(
_mtxp: &MintedTxPayload,
mtxp: &MintedTxPayload,
_utxos: &UTxOs,
_prot_pps: &ByronProtParams,
) -> ValidationResult {
let tx: &Tx = &mtxp.transaction;
check_ins_not_empty(tx)?;
check_outs_not_empty(tx)
}

fn check_ins_not_empty(tx: &Tx) -> ValidationResult {
if tx.inputs.clone().to_vec().is_empty() {
return Err(ValidationError::TxInsEmpty);
}
Ok(())
}

fn check_outs_not_empty(tx: &Tx) -> ValidationResult {
if tx.outputs.clone().to_vec().is_empty() {
return Err(ValidationError::TxOutsEmpty);
}
Ok(())
}
3 changes: 2 additions & 1 deletion pallas-applying/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ pub enum MultiEraProtParams<'b> {
#[derive(Debug)]
#[non_exhaustive]
pub enum ValidationError {
ValidationError,
TxInsEmpty,
TxOutsEmpty,
}

pub type ValidationResult = Result<(), ValidationError>;
117 changes: 89 additions & 28 deletions pallas-applying/tests/byron.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use rand::Rng;
use std::{borrow::Cow, vec::Vec};

use pallas_applying::{
types::{ByronProtParams, MultiEraProtParams},
types::{ByronProtParams, MultiEraProtParams, ValidationError},
validate, UTxOs, ValidationResult,
};
use pallas_codec::{
Expand All @@ -29,32 +29,92 @@ mod byron_tests {
// i) the transaction input contains 100000 lovelace,
// ii) the minimum_fee_constant protocol parameter is 7,
// iii) the minimum_fee_factor protocol parameter is 11, and
// iv) the size of the transaction is 82 bytes—it is easy to verify that
// 82 == pallas_applying::get_byron_tx_size(tx)
// The expected fees are therefore 7 + 11 * 82 = 909 lovelace, which is why the output contains
// 100000 - 909 = 99091 lovelace.
// iv) the size of the transaction is 82 bytes—it is easy to verify
// that 82 == pallas_applying::get_byron_tx_size(tx).
// The expected fees are therefore 7 + 11 * 82 = 909 lovelace, which is why
// the output contains 100000 - 909 = 99091 lovelace.
fn successful_case() {
let protocol_params: ByronProtParams = ByronProtParams;
let mut tx_ins: ByronTxIns = empty_byron_tx_ins();
let tx_in: ByronTxIn = new_tx_in(random_tx_id(), 3);
let mut tx_ins: ByronTxIns = empty_tx_ins();
let tx_in: ByronTxIn = new_tx_in(rand_tx_id(), 3);
add_byron_tx_in(&mut tx_ins, &tx_in);
let mut tx_outs: ByronTxOuts = new_byron_tx_outs();
let tx_out: ByronTxOut = new_byron_tx_out(new_address(random_address_payload(), 0), 99091);
add_byron_tx_out(&mut tx_outs, &tx_out);
let mut tx_outs: ByronTxOuts = new_tx_outs();
let tx_out_addr: Address = new_addr(rand_addr_payload(), 0);
let tx_out: ByronTxOut = new_tx_out(tx_out_addr, 99091);
add_tx_out(&mut tx_outs, &tx_out);
let mut utxos: UTxOs = new_utxos();
// input_tx_out is the ByronTxOut associated with tx_in.
let input_tx_out: ByronTxOut =
new_byron_tx_out(new_address(random_address_payload(), 0), 100000);
let input_tx_out_addr: Address = new_addr(rand_addr_payload(), 0);
let input_tx_out: ByronTxOut = new_tx_out(input_tx_out_addr, 100000);
add_to_utxo(&mut utxos, tx_in, input_tx_out);
let validation_result = mk_byron_tx_and_validate(
&new_byron_tx(tx_ins, tx_outs, empty_byron_attributes()),
&empty_byron_witnesses(),
&new_tx(tx_ins, tx_outs, empty_attributes()),
&empty_witnesses(),
&utxos,
&protocol_params,
);
match validation_result {
Ok(()) => (),
Err(err) => assert!(false, "Unexpected error (sucessful_case - {:?}).", err),
Err(err) => assert!(false, "Unexpected error ({:?}).", err),
}
}

#[test]
// Similar to successful_case, except that no inputs are added to the
// transaction, which should raise a ValidationError:TxInsEmpty error.
fn empty_ins() {
let protocol_params: ByronProtParams = ByronProtParams;
let tx_ins: ByronTxIns = empty_tx_ins();
// Note: tx_in is not added to tx_ins, it is only added to the UTxOs set
let tx_in: ByronTxIn = new_tx_in(rand_tx_id(), 3);
let mut tx_outs: ByronTxOuts = new_tx_outs();
let tx_out_addr: Address = new_addr(rand_addr_payload(), 0);
let tx_out: ByronTxOut = new_tx_out(tx_out_addr, 99091);
add_tx_out(&mut tx_outs, &tx_out);
let mut utxos: UTxOs = new_utxos();
let input_tx_out_addr: Address = new_addr(rand_addr_payload(), 0);
let input_tx_out: ByronTxOut = new_tx_out(input_tx_out_addr, 100000);
add_to_utxo(&mut utxos, tx_in, input_tx_out);
let validation_result = mk_byron_tx_and_validate(
&new_tx(tx_ins, tx_outs, empty_attributes()),
&empty_witnesses(),
&utxos,
&protocol_params,
);
match validation_result {
Ok(()) => assert!(false, "Inputs set should not be empty."),
Err(err) => match err {
ValidationError::TxInsEmpty => (),
_ => assert!(false, "Unexpected error ({:?}).", err),
},
}
}

#[test]
// Similar to empty_ins, except that this time no outputs are added to the
// transaction, which should raise a ValidationError:TxOutsEmpty error.
fn empty_outs() {
let protocol_params: ByronProtParams = ByronProtParams;
let mut tx_ins: ByronTxIns = empty_tx_ins();
let tx_in: ByronTxIn = new_tx_in(rand_tx_id(), 3);
add_byron_tx_in(&mut tx_ins, &tx_in);
let tx_outs: ByronTxOuts = new_tx_outs();
let mut utxos: UTxOs = new_utxos();
let input_tx_out_addr: Address = new_addr(rand_addr_payload(), 0);
let input_tx_out: ByronTxOut = new_tx_out(input_tx_out_addr, 100000);
add_to_utxo(&mut utxos, tx_in, input_tx_out);
let validation_result = mk_byron_tx_and_validate(
&new_tx(tx_ins, tx_outs, empty_attributes()),
&empty_witnesses(),
&utxos,
&protocol_params,
);
match validation_result {
Ok(()) => assert!(false, "Outputs set should not be empty."),
Err(err) => match err {
ValidationError::TxOutsEmpty => (),
_ => assert!(false, "Unexpected error ({:?}).", err),
},
}
}
}
Expand All @@ -64,11 +124,11 @@ type ByronTxIns = MaybeIndefArray<ByronTxIn>;
type ByronTxOuts = MaybeIndefArray<ByronTxOut>;

// Helper functions.
fn empty_byron_tx_ins() -> ByronTxIns {
fn empty_tx_ins() -> ByronTxIns {
MaybeIndefArray::Def(Vec::new())
}

fn random_tx_id() -> ByronTxId {
fn rand_tx_id() -> ByronTxId {
let mut rng = rand::thread_rng();
let mut bytes = [0u8; 32];
for elem in bytes.iter_mut() {
Expand All @@ -87,11 +147,11 @@ fn add_byron_tx_in(ins: &mut ByronTxIns, new_in: &ByronTxIn) {
}
}

fn new_byron_tx_outs() -> ByronTxOuts {
fn new_tx_outs() -> ByronTxOuts {
MaybeIndefArray::Def(Vec::new())
}

fn random_address_payload() -> TagWrap<ByteVec, 24> {
fn rand_addr_payload() -> TagWrap<ByteVec, 24> {
let mut rng = rand::thread_rng();
let mut bytes = [0u8; 24];
for elem in bytes.iter_mut() {
Expand All @@ -100,21 +160,21 @@ fn random_address_payload() -> TagWrap<ByteVec, 24> {
TagWrap::<ByteVec, 24>::new(ByteVec::from(bytes.to_vec()))
}

fn new_address(payload: TagWrap<ByteVec, 24>, crc: u32) -> Address {
fn new_addr(payload: TagWrap<ByteVec, 24>, crc: u32) -> Address {
Address {
payload: payload,
crc: crc,
}
}

fn new_byron_tx_out(address: Address, amount: u64) -> ByronTxOut {
fn new_tx_out(address: Address, amount: u64) -> ByronTxOut {
ByronTxOut {
address: address,
amount: amount,
}
}

fn add_byron_tx_out(outs: &mut ByronTxOuts, new_out: &ByronTxOut) {
fn add_tx_out(outs: &mut ByronTxOuts, new_out: &ByronTxOut) {
match outs {
MaybeIndefArray::Def(vec) | MaybeIndefArray::Indef(vec) => vec.push(new_out.clone()),
}
Expand All @@ -126,13 +186,14 @@ fn add_to_utxo<'a>(utxos: &mut UTxOs<'a>, tx_in: ByronTxIn, tx_out: ByronTxOut)
utxos.insert(multi_era_in, multi_era_out);
}

fn empty_byron_attributes() -> Attributes {
fn empty_attributes() -> Attributes {
EmptyMap
}

/// pallas_applying::validate takes a MultiEraTx, not a ByronTx and a ByronWitnesses. To be able to
/// build a MultiEraTx from a ByronTx and a ByronWitnesses, we need to encode each of them and then
/// decode them into KeepRaw<ByronTx> and KeepRaw<ByronWitnesses> values, respectively.
// pallas_applying::validate takes a MultiEraTx, not a ByronTx and a
// ByronWitnesses. To be able to build a MultiEraTx from a ByronTx and a
// ByronWitnesses, we need to encode each of them and then decode them into
// KeepRaw<ByronTx> and KeepRaw<ByronWitnesses> values, respectively.
fn mk_byron_tx_and_validate(
btx: &ByronTx,
bwit: &ByronWitnesses,
Expand Down Expand Up @@ -175,15 +236,15 @@ fn mk_byron_tx_and_validate(
)
}

fn new_byron_tx(ins: ByronTxIns, outs: ByronTxOuts, attrs: Attributes) -> ByronTx {
fn new_tx(ins: ByronTxIns, outs: ByronTxOuts, attrs: Attributes) -> ByronTx {
ByronTx {
inputs: ins,
outputs: outs,
attributes: attrs,
}
}

fn empty_byron_witnesses() -> ByronWitnesses {
fn empty_witnesses() -> ByronWitnesses {
MaybeIndefArray::Def(Vec::new())
}

Expand Down