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): ShelleyMA phase-1 validations #354

Merged
merged 15 commits into from
Dec 12, 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
108 changes: 108 additions & 0 deletions pallas-applying/docs/shelleyMA-validation-rules.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# ShelleyMA transaction validation rules

This document covers the Shelley era, including its Allegra and Mary hard forks. We write *ShelleyMA* to refer to any of these ledger versions, and *Shelley*, *Allegra* or *Mary* when the discrimination is relevant. This document covers only the concepts, notation and validation rules realted to phase-1 validation in these ledger versions. For further information, refer to the corresponding white paper listed below:
- [Shelley's ledger white paper](https://github.com/input-output-hk/cardano-ledger/releases/latest/download/shelley-ledger.pdf)
- [Both Allegra's and Mary's ledger white paper](https://github.com/input-output-hk/cardano-ledger/releases/latest/download/mary-ledger.pdf)

## Definitions and notation
- **Scripts**:
- ***Script*** is the set of all possible native scripts.
- **Transactions**:
- ***Tx*** is the set of ShelleyMA transactions, composed of a transaction body and the set of witnesses.
- ***TxBody*** is the type of ShelleyMA transaction bodies. Each transaction body is composed of a set of inputs and a list of outputs.
- ***txBody(tx)*** is the transaction body of the transaction.
- ***TxOut = Addr x TA*** is the set of transaction outputs, where
- ***Addr*** is the set of transaction output addresses.
- ***TA = ℕ*** in Shelley and Allegra, while ***TA = Value*** in Mary, where ***Value*** is the type of multi-asset Mary values.
- ***txOuts(txBody) ∈ P(TxOut)*** gives the set of transaction outputs of a transaction body.
- ***balance : P(TxOut) → TA*** gives the sum of all lovelaces in a set of transaction outputs in Shelley and Allegra, while it gives the sum of all assets in a set of transaction outputs in Mary. That is, ***TA = ℕ*** in Shelley and Allegra, and ***TA = Value*** in Mary.
- ***TxIn = TxId x Ix*** is the set of transaction inputs, where
- ***TxId*** is the set of transaction IDs.
- ***Ix = ℕ*** is the set of indices (used to refer to a specific transaction output).
- ***txIns(txBody) ∈ P(TxIn)*** gives the set of transaction inputs of the transaction.
- ***utxo : TxIn → TxOut*** is a (partial) map that gives the unspent transaction output (UTxO) associated with a transaction input.
- Given ***A ⊆ dom(utxo)***, we will write ***A ◁ utxo := {to ∈ TxOut / ∃ ti ∈ dom utxo: utxo(ti) = to}***. Thus, we will write ***txIns(tx) ◁ utxo := {to ∈ TxOut / ∃ ti ∈ dom(utxo): utxo(ti) = to}*** to express the set of unspent transaction outputs associated with the set of transaction inputs of the transaction ***tx***.
- ***txTTL(txBody) ∈ Slot*** is the time-to-live of the transaction.
- ***txSize(Tx) ∈ ℕ*** gives the size of the transaction.
- ***fee(txBody) ∈ ℕ*** gives the fee paid by a transaction.
- ***minted(txBody)*** is the multiasset value minted (or burned) in the transaction.
- ***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.
- In Shelley and Allegra, this equals the sum of all lovelace in the transaction inputs.
- In Mary, this equals the sum of all multiasset values in the transaction inputs.
- ***produced(pps, txBody) ∈ ℤ*** is the *produced value* of the transaction.
- In Shelley and Allegra, this equals the sum of all lovelace in the transaction outputs plus the transaction fee.
- In Mary, this equals the sum of all multiasset values in the outputs of the transaction plus the transaction fee plus the minted value.
- **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***.
- **Addresses*:
- ***Addr*** is the set of all valid ShelleyMA addresses.
- ***netId(addr)*** is the network ID of the address.
- ***NetworkId*** is the global network ID.
- ***Slots***:
- ***Slot ∈ ℕ*** is the set of slots. When necessary, we write ***slot ∈ Slot*** to refer to the slot associated to the current block.
- **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.
- **Hashing**:
- ***KeyHash ⊆ Bytes*** is the set of fixed-size byte arrays resulting from hashing processes.
- ***hash: Bytes -> KeyHash*** is the hashing function.
- ***paymentCredential<sub>utxo</sub>(txIn) ∈ KeyHash*** gets from ***txIn*** the associated transaction output in ***utxo***, extracts the address contained in it, and returns its hash. In other words, given ***utxo*** and transaction input ***txIn*** such that ***utxo(txIn) = (a, _)***, we have that ***paymentCredential<sub>utxo</sub>(txIn) = hash(a)***.
- **Protocol Parameters**:
- We will write ***pps ∈ PParams*** to represent the set of (ShelleyMA) protocol parameters, with the following associated functions:
- ***minFees(pps, txBody) ∈ ℕ*** gives the minimum number of lovelace that must be paid for the transaction as fee.
- ***maxTxSize(pps) ∈ ℕ*** gives the (global) maximum transaction size.
- ***minUTxOValue(pps) ∈ ℕ***, the global minimum number of lovelace every UTxO must lock.
- ***Witnesses***:
- ***VKey*** is the set of verification keys (a.k.a. public keys).
- ***SKey*** is the set of signing keys (a.k.a. private keys).
- ***Sig*** is the set of signatures (i.e., the result of signing a byte array using a signing key).
- ***sig : SKey x Bytes -> Sig*** is the signing function.
- ***verify : VKey x Sig x Bytes -> Bool*** assesses whether the verification key applied to the signature yields the byte array as expected.
- The assumption is that if ***sk*** and ***vk*** are, respectively, a pair of secret and verification keys associated with one another. Thus, if ***sig(sk, d) = σ***, then it must be that ***verify(vk, σ, d) = true***.
- ***txVKWits(tx) ∈ P(VKey x Sig)*** gives the list of pairs of verification keys and signatures of the transaction.
- ***txScriptWits(tx) ⊆ P(Script)*** is the set of script witnesses of the transaction.

## Validation rules
Let ***tx ∈ Tx*** be a ShelleyMA transaction whose body is ***txBody ∈ TxBody***. ***tx*** is a phase-1 valid transaction if and only if

- **The set of transaction inputs is not empty**:

<code>txIns(txBody) ≠ ∅</code>
- **All transaction inputs are in the set of (yet) unspent transaction outputs**:

<code>txIns(txBody) ⊆ dom(utxo)</code>
- **The TTL limit of the transaction has not been exceeded**:

<code>slot ≥ txTTL(txBody)</code>
- **The transaction size does not exceed the protocol limit**:

<code>txSize(tx) ≤ maxTxSize(pps)</code>
- **All transaction outputs contain Lovelace values not under the minimum**:

<code>∀ (_, c) ∈ txOuts(txBody): minUTxOValue(pps) ≤ c</code>
- **The preservation of value property holds**: Assuming no staking or delegation actions are involved, this property takes one of the two forms below:
- In Shelley and Allegra, the equation for the preservation of value is

<code>consumed(pps, utxo, txBody) = produced(pps, poolParams, txBody) + fee(txBody)</code>,
- In Mary, the equation is:

<code>consumed(pps, utxo, txBody) = produced(pps, poolParams, txBody) + fee(txBody) + minted(txBody) </code>,
- **The fee paid by the transaction has to be greater than or equal to the minimum fee**:

<code>fee(txBody) ≥ minFees(pps, tx)</code>
- **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, given 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>
- <code>paymentCredential<sub>utxo</sub>(txIn) = hash(vk)</code>
- **Script witnesses**: Each script address has a corresponding witness:

<code>∀ (script_hash, _) ∈ txInsScript(txBody) ◁ utxo : ∃ script ∈ txScriptWits(tx): hash(script) = script_hash</code>
50 changes: 26 additions & 24 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 All @@ -30,34 +32,34 @@ pub fn validate_byron_tx(
prot_magic: &u32,
) -> ValidationResult {
let tx: &Tx = &mtxp.transaction;
let size: u64 = get_tx_size(tx)?;
let size: &u64 = &get_tx_size(tx)?;
check_ins_not_empty(tx)?;
check_outs_not_empty(tx)?;
check_ins_in_utxos(tx, utxos)?;
check_outs_have_lovelace(tx)?;
check_fees(tx, &size, utxos, prot_pps)?;
check_size(&size, prot_pps)?;
check_fees(tx, size, utxos, prot_pps)?;
check_size(size, prot_pps)?;
check_witnesses(mtxp, utxos, prot_magic)
}

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(InputNotInUTxO));
}
}
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 @@ -95,9 +97,9 @@ fn check_fees(tx: &Tx, size: &u64, utxos: &UTxOs, prot_pps: &ByronProtParams) ->
outputs_balance += output.amount
}
let total_balance: u64 = inputs_balance - outputs_balance;
let min_fees: u64 = prot_pps.min_fees_const + prot_pps.min_fees_factor * size;
let min_fees: u64 = prot_pps.fee_policy.summand + prot_pps.fee_policy.multiplier * 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(InputNotInUTxO))?
.as_byron()
.ok_or(ValidationError::InputMissingInUTxO)
.ok_or(Byron(InputNotInUTxO))
}

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
41 changes: 29 additions & 12 deletions pallas-applying/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,24 +1,41 @@
//! Logic for validating and applying new blocks and txs to the chain state

pub mod byron;
pub mod shelley_ma;
pub mod types;

use byron::validate_byron_tx;
use pallas_traverse::{Era, MultiEraTx};
use shelley_ma::validate_shelley_ma_tx;

use pallas_traverse::{MultiEraTx, MultiEraTx::Byron as ByronTxPayload};

pub use types::{Environment, MultiEraProtParams, UTxOs, ValidationResult};
pub use types::{
Environment, MultiEraProtParams, UTxOs, ValidationError::TxAndProtParamsDiffer,
ValidationResult,
};

pub fn validate(metx: &MultiEraTx, utxos: &UTxOs, env: &Environment) -> ValidationResult {
match (metx, env) {
(
ByronTxPayload(mtxp),
Environment {
prot_params: MultiEraProtParams::Byron(bpp),
prot_magic,
match env {
Environment {
prot_params: MultiEraProtParams::Byron(bpp),
prot_magic,
..
} => match metx {
MultiEraTx::Byron(mtxp) => validate_byron_tx(mtxp, utxos, bpp, 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),
},
) => validate_byron_tx(mtxp, utxos, bpp, prot_magic),
// TODO: implement the rest of the eras.
_ => Ok(()),
_ => Err(TxAndProtParamsDiffer),
},
}
}
Loading
Loading