Skip to content

Commit

Permalink
feat(applying): re-structure validation errors
Browse files Browse the repository at this point in the history
  • Loading branch information
MaicoLeberle committed Nov 23, 2023
1 parent f7de948 commit ef3c638
Show file tree
Hide file tree
Showing 6 changed files with 79 additions and 66 deletions.
7 changes: 0 additions & 7 deletions pallas-applying/docs/shelley-validation-rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,6 @@ Refer to the [Shelley's ledger white paper](https://github.com/input-output-hk/c
- ***txInsScript(txBody) ⊆ P(TxIn)*** is the list of script inputs in the transaction body.
- ***consumed(pps, utxo, txBody) ∈ ℤ*** is the *consumed value* of the transaction. To our purposes, this equals the sum of all lovelace in the tx's inputs.
- ***produced(pps, txBody) ∈ ℤ*** is the *produced value* of the transaction. To our purposes, this equals the sum of all lovelace in the outputs plus the transaction fee.
- **Transaction metadata**:
- ***txMD(tx)*** is the metadata of the transaction.
- ***txMDHash(txBody)*** is the metadata hash contained within the transaction body.
- ***hashMD(md)*** is the result of hasing metadata ***md***.
- ***Addr*** is the set of all valid Shelley addresses.
- ***netId(addr)*** is the network ID of the address.
- ***NetworkId*** is the global network ID.
Expand Down Expand Up @@ -82,9 +78,6 @@ Let ***tx ∈ Tx*** be a Shelley transaction whose body is ***txBody ∈ TxBody*
- **The network ID of each output matches the global network ID**:

<code>∀(_ -> (a, _)) ∈ txOuts(txBody): netId(a) = NetworkId</code>
- **The metadata of the transaction is valid**:

<code>txMDHash(tx) = hashMD(txMD(tx))</code>
- **Verification-key witnesses**: The owner of each transaction input signed the transaction. That is, if transaction ***tx*** with body ***txBody***, then for each ***txIn ∈ txIns(txBody)*** there must exist ***(vk, σ) ∈ txVKWits(tx)*** such that:

- <code>verify(vk, σ, ⟦txBody⟧<sub>TxBody</sub>)</code>
Expand Down
42 changes: 22 additions & 20 deletions pallas-applying/src/byron.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
use std::borrow::Cow;

use crate::types::{
ByronProtParams, MultiEraInput, MultiEraOutput, SigningTag, UTxOs, ValidationError,
ByronError::*,
ByronProtParams, MultiEraInput, MultiEraOutput, SigningTag, UTxOs,
ValidationError::{self, *},
ValidationResult,
};

Expand Down Expand Up @@ -42,22 +44,22 @@ pub fn validate_byron_tx(

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

fn check_outs_not_empty(tx: &Tx) -> ValidationResult {
if tx.outputs.clone().to_vec().is_empty() {
return Err(ValidationError::TxOutsEmpty);
return Err(Byron(TxOutsEmpty));
}
Ok(())
}

fn check_ins_in_utxos(tx: &Tx, utxos: &UTxOs) -> ValidationResult {
for input in tx.inputs.iter() {
if !(utxos.contains_key(&MultiEraInput::from_byron(input))) {
return Err(ValidationError::InputMissingInUTxO);
return Err(Byron(InputMissingInUTxO));
}
}
Ok(())
Expand All @@ -66,7 +68,7 @@ fn check_ins_in_utxos(tx: &Tx, utxos: &UTxOs) -> ValidationResult {
fn check_outs_have_lovelace(tx: &Tx) -> ValidationResult {
for output in tx.outputs.iter() {
if output.amount == 0 {
return Err(ValidationError::OutputWithoutLovelace);
return Err(Byron(OutputWithoutLovelace));
}
}
Ok(())
Expand All @@ -84,7 +86,7 @@ fn check_fees(tx: &Tx, size: &u64, utxos: &UTxOs, prot_pps: &ByronProtParams) ->
.and_then(MultiEraOutput::as_byron)
{
Some(byron_utxo) => inputs_balance += byron_utxo.amount,
None => return Err(ValidationError::UnableToComputeFees),
None => return Err(Byron(UnableToComputeFees)),
}
}
if only_redeem_utxos {
Expand All @@ -97,7 +99,7 @@ fn check_fees(tx: &Tx, size: &u64, utxos: &UTxOs, prot_pps: &ByronProtParams) ->
let total_balance: u64 = inputs_balance - outputs_balance;
let min_fees: u64 = prot_pps.min_fees_const + prot_pps.min_fees_factor * size;
if total_balance < min_fees {
Err(ValidationError::FeesBelowMin)
Err(Byron(FeesBelowMin))
} else {
Ok(())
}
Expand All @@ -119,7 +121,7 @@ fn is_redeem_utxo(input: &TxIn, utxos: &UTxOs) -> bool {

fn check_size(size: &u64, prot_pps: &ByronProtParams) -> ValidationResult {
if *size > prot_pps.max_tx_size {
return Err(ValidationError::MaxTxSizeExceeded);
return Err(Byron(MaxTxSizeExceeded));
}
Ok(())
}
Expand All @@ -128,7 +130,7 @@ fn get_tx_size(tx: &Tx) -> Result<u64, ValidationError> {
let mut buff: Vec<u8> = Vec::new();
match encode(tx, &mut buff) {
Ok(()) => Ok(buff.len() as u64),
Err(_) => Err(ValidationError::UnknownTxSize),
Err(_) => Err(Byron(UnknownTxSize)),
}
}

Expand All @@ -149,7 +151,7 @@ fn check_witnesses(mtxp: &MintedTxPayload, utxos: &UTxOs, prot_magic: &u32) -> V
let data_to_verify: Vec<u8> = get_data_to_verify(sign, prot_magic, &tx_hash)?;
let signature: Signature = get_signature(sign);
if !public_key.verify(data_to_verify, &signature) {
return Err(ValidationError::WrongSignature);
return Err(Byron(WrongSignature));
}
}
Ok(())
Expand All @@ -165,7 +167,7 @@ fn tag_witnesses(wits: &[Twit]) -> Result<Vec<(&PubKey, TaggedSignature)>, Valid
Twit::RedeemWitness(CborWrap((pk, sig))) => {
res.push((pk, TaggedSignature::RedeemWitness(sig)));
}
_ => return Err(ValidationError::UnableToProcessWitnesses),
_ => return Err(Byron(UnableToProcessWitness)),
}
}
Ok(res)
Expand All @@ -175,9 +177,9 @@ fn find_tx_out<'a>(input: &'a TxIn, utxos: &'a UTxOs) -> Result<&'a TxOut, Valid
let key: MultiEraInput = MultiEraInput::Byron(Box::new(Cow::Borrowed(input)));
utxos
.get(&key)
.ok_or(ValidationError::InputMissingInUTxO)?
.ok_or(Byron(InputMissingInUTxO))?
.as_byron()
.ok_or(ValidationError::InputMissingInUTxO)
.ok_or(Byron(InputMissingInUTxO))
}

fn find_raw_witness<'a>(
Expand All @@ -187,19 +189,19 @@ fn find_raw_witness<'a>(
let address: ByronAddress = mk_byron_address(&tx_out.address);
let addr_payload: AddressPayload = address
.decode()
.map_err(|_| ValidationError::UnableToProcessWitnesses)?;
.map_err(|_| Byron(UnableToProcessWitness))?;
let root: AddressId = addr_payload.root;
let attr: AddrAttrs = addr_payload.attributes;
let addr_type: AddrType = addr_payload.addrtype;
for (pub_key, sign) in witnesses {
if redeems(pub_key, sign, &root, &attr, &addr_type) {
match addr_type {
AddrType::PubKey | AddrType::Redeem => return Ok((pub_key, sign)),
_ => return Err(ValidationError::UnableToProcessWitnesses),
_ => return Err(Byron(UnableToProcessWitness)),
}
}
}
Err(ValidationError::MissingWitness)
Err(Byron(MissingWitness))
}

fn mk_byron_address(addr: &Address) -> ByronAddress {
Expand Down Expand Up @@ -250,17 +252,17 @@ fn get_data_to_verify(
match sign {
TaggedSignature::PkWitness(_) => {
enc.encode(SigningTag::Tx as u64)
.map_err(|_| ValidationError::UnableToProcessWitnesses)?;
.map_err(|_| Byron(UnableToProcessWitness))?;
}
TaggedSignature::RedeemWitness(_) => {
enc.encode(SigningTag::RedeemTx as u64)
.map_err(|_| ValidationError::UnableToProcessWitnesses)?;
.map_err(|_| Byron(UnableToProcessWitness))?;
}
}
enc.encode(prot_magic)
.map_err(|_| ValidationError::UnableToProcessWitnesses)?;
.map_err(|_| Byron(UnableToProcessWitness))?;
enc.encode(tx_hash)
.map_err(|_| ValidationError::UnableToProcessWitnesses)?;
.map_err(|_| Byron(UnableToProcessWitness))?;
Ok(enc.into_writer().clone())
}

Expand Down
18 changes: 10 additions & 8 deletions pallas-applying/src/shelley.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
//! Utilities required for Shelley-era transaction validation.
use crate::types::{ShelleyProtParams, UTxOs, ValidationError, ValidationResult};
use crate::types::{
ShelleyError::*, ShelleyProtParams, UTxOs, ValidationError::*, ValidationResult,
};
use pallas_addresses::{Address, ShelleyAddress};
use pallas_primitives::alonzo::{MintedTx, TransactionBody};
use pallas_traverse::MultiEraInput;
Expand All @@ -23,15 +25,15 @@ pub fn validate_shelley_tx(

fn check_ins_not_empty(tx_body: &TransactionBody) -> ValidationResult {
if tx_body.inputs.is_empty() {
return Err(ValidationError::TxInsEmpty);
return Err(Shelley(TxInsEmpty));
}
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);
return Err(Shelley(InputMissingInUTxO));
}
}
Ok(())
Expand All @@ -41,12 +43,12 @@ fn check_ttl(tx_body: &TransactionBody, block_slot: &u64) -> ValidationResult {
match tx_body.ttl {
Some(ttl) => {
if ttl < *block_slot {
Err(ValidationError::TTLExceeded)
Err(Shelley(TTLExceeded))
} else {
Ok(())
}
}
None => Err(ValidationError::AlonzoCompatibleNotShelley),
None => Err(Shelley(AlonzoCompNotShelley)),
}
}

Expand All @@ -55,11 +57,11 @@ fn check_network_id(tx_body: &TransactionBody, network_id: &u8) -> ValidationRes
let addr: ShelleyAddress =
match Address::from_bytes(&Vec::<u8>::from(output.address.clone())) {
Ok(Address::Shelley(sa)) => sa,
Ok(_) => return Err(ValidationError::WrongEraOutput),
Err(_) => return Err(ValidationError::UnableToDecodeAddress),
Ok(_) => return Err(Shelley(WrongEraOutput)),
Err(_) => return Err(Shelley(AddressDecoding)),
};
if addr.network().value() != *network_id {
return Err(ValidationError::WrongNetworkID);
return Err(Shelley(WrongNetworkID));
}
}
Ok(())
Expand Down
46 changes: 30 additions & 16 deletions pallas-applying/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,22 +41,36 @@ pub enum SigningTag {
#[derive(Debug)]
#[non_exhaustive]
pub enum ValidationError {
InputMissingInUTxO, // >= Byron
TxInsEmpty, // >= Byron
TxOutsEmpty, // >= Byron
OutputWithoutLovelace, // == Byron
UnknownTxSize, // >= Byron
UnableToComputeFees, // >= Byron
FeesBelowMin, // >= Byron
MaxTxSizeExceeded, // >= Byron
UnableToProcessWitnesses, // >= Byron
MissingWitness, // >= Byron
WrongSignature, // >= Byron
TTLExceeded, // >= Shelley
AlonzoCompatibleNotShelley, // == Shelley
WrongEraOutput, // >= Shelley
UnableToDecodeAddress, // >= Shelley
WrongNetworkID, // >= Shelley
Byron(ByronError),
Shelley(ShelleyError),
}

#[derive(Debug)]
#[non_exhaustive]
pub enum ByronError {
TxInsEmpty,
TxOutsEmpty,
InputMissingInUTxO,
OutputWithoutLovelace,
UnknownTxSize,
UnableToComputeFees,
FeesBelowMin,
MaxTxSizeExceeded,
UnableToProcessWitness,
MissingWitness,
WrongSignature,
}

#[derive(Debug)]
#[non_exhaustive]
pub enum ShelleyError {
TxInsEmpty,
InputMissingInUTxO,
TTLExceeded,
AlonzoCompNotShelley,
WrongEraOutput,
AddressDecoding,
WrongNetworkID,
}

pub type ValidationResult = Result<(), ValidationError>;
18 changes: 9 additions & 9 deletions pallas-applying/tests/byron.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::{borrow::Cow, vec::Vec};

use pallas_applying::{
types::{ByronProtParams, Environment, MultiEraProtParams, ValidationError},
types::{ByronError::*, ByronProtParams, Environment, MultiEraProtParams, ValidationError::*},
validate, UTxOs,
};
use pallas_codec::{
Expand Down Expand Up @@ -135,7 +135,7 @@ mod byron_tests {
match validate(&metx, &utxos, &env) {
Ok(()) => assert!(false, "Inputs set should not be empty."),
Err(err) => match err {
ValidationError::TxInsEmpty => (),
Byron(TxInsEmpty) => (),
_ => panic!("Unexpected error ({:?}).", err),
},
}
Expand Down Expand Up @@ -174,7 +174,7 @@ mod byron_tests {
match validate(&metx, &utxos, &env) {
Ok(()) => assert!(false, "Outputs set should not be empty."),
Err(err) => match err {
ValidationError::TxOutsEmpty => (),
Byron(TxOutsEmpty) => (),
_ => panic!("Unexpected error ({:?}).", err),
},
}
Expand All @@ -200,7 +200,7 @@ mod byron_tests {
match validate(&metx, &utxos, &env) {
Ok(()) => assert!(false, "All inputs must be within the UTxO set."),
Err(err) => match err {
ValidationError::InputMissingInUTxO => (),
Byron(InputMissingInUTxO) => (),
_ => panic!("Unexpected error ({:?}).", err),
},
}
Expand Down Expand Up @@ -245,7 +245,7 @@ mod byron_tests {
match validate(&metx, &utxos, &env) {
Ok(()) => assert!(false, "All outputs must contain lovelace."),
Err(err) => match err {
ValidationError::OutputWithoutLovelace => (),
Byron(OutputWithoutLovelace) => (),
_ => panic!("Unexpected error ({:?}).", err),
},
}
Expand Down Expand Up @@ -275,7 +275,7 @@ mod byron_tests {
match validate(&metx, &utxos, &env) {
Ok(()) => assert!(false, "Fees should not be below minimum."),
Err(err) => match err {
ValidationError::FeesBelowMin => (),
Byron(FeesBelowMin) => (),
_ => panic!("Unexpected error ({:?}).", err),
},
}
Expand Down Expand Up @@ -305,7 +305,7 @@ mod byron_tests {
match validate(&metx, &utxos, &env) {
Ok(()) => assert!(false, "Transaction size cannot exceed protocol limit."),
Err(err) => match err {
ValidationError::MaxTxSizeExceeded => (),
Byron(MaxTxSizeExceeded) => (),
_ => panic!("Unexpected error ({:?}).", err),
},
}
Expand Down Expand Up @@ -343,7 +343,7 @@ mod byron_tests {
match validate(&metx, &utxos, &env) {
Ok(()) => assert!(false, "All inputs must have a witness signature."),
Err(err) => match err {
ValidationError::MissingWitness => (),
Byron(MissingWitness) => (),
_ => panic!("Unexpected error ({:?}).", err),
},
}
Expand Down Expand Up @@ -390,7 +390,7 @@ mod byron_tests {
match validate(&metx, &utxos, &env) {
Ok(()) => assert!(false, "Witness signature should verify the transaction."),
Err(err) => match err {
ValidationError::WrongSignature => (),
Byron(WrongSignature) => (),
_ => panic!("Unexpected error ({:?}).", err),
},
}
Expand Down
Loading

0 comments on commit ef3c638

Please sign in to comment.