Skip to content

Commit

Permalink
feat: scaffold Byron phase-1 validations (#300)
Browse files Browse the repository at this point in the history
Co-authored-by: Santiago Carmuega <[email protected]>
  • Loading branch information
MaicoLeberle and scarmuega authored Oct 10, 2023
1 parent aafe375 commit 986ee75
Show file tree
Hide file tree
Showing 14 changed files with 306 additions and 19 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
[workspace]
resolver = "2"
members = [
"pallas-applying",
"pallas-codec",
"pallas-addresses",
"pallas-network",
Expand Down
20 changes: 20 additions & 0 deletions pallas-applying/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[package]
name = "pallas-applying"
description = "Logic for validating and applying new blocks and txs to the chain state"
version = "0.19.1"
edition = "2021"
repository = "https://github.com/MaicoLeberle/pallas"
homepage = "https://github.com/MaicoLeberle/pallas"
license = "Apache-2.0"
readme = "README.md"
authors = ["Maico Leberle <[email protected]>"]

[lib]
doctest = false

[dependencies]
pallas-codec = { path = "../pallas-codec" }
pallas-crypto = { path = "../pallas-crypto" }
pallas-primitives = { path = "../pallas-primitives" }
pallas-traverse = { path = "../pallas-traverse" }
rand = "0.8"
3 changes: 3 additions & 0 deletions pallas-applying/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Pallas Applying

Crate for performing transaction validation according to the Cardano protocol.
14 changes: 14 additions & 0 deletions pallas-applying/src/byron.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//! Utilities required for Byron-era transaction validation.
use crate::types::{ByronProtParams, UTxOs, ValidationResult};

use pallas_primitives::byron::MintedTxPayload;

// TODO: implement each of the validation rules.
pub fn validate_byron_tx(
_mtxp: &MintedTxPayload,
_utxos: &UTxOs,
_prot_pps: &ByronProtParams,
) -> ValidationResult {
Ok(())
}
24 changes: 24 additions & 0 deletions pallas-applying/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//! Logic for validating and applying new blocks and txs to the chain state
pub mod byron;
pub mod types;

use byron::validate_byron_tx;

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

pub use types::{
MultiEraProtParams, MultiEraProtParams::Byron as ByronProtParams, UTxOs, ValidationResult,
};

pub fn validate(
metx: &MultiEraTx,
utxos: &UTxOs,
prot_pps: &MultiEraProtParams,
) -> ValidationResult {
match (metx, prot_pps) {
(ByronTxPayload(mtxp), ByronProtParams(bpp)) => validate_byron_tx(mtxp, utxos, bpp),
// TODO: implement the rest of the eras.
_ => Ok(()),
}
}
27 changes: 27 additions & 0 deletions pallas-applying/src/types.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//! Base types used for validating transactions in each era.
use std::{borrow::Cow, collections::HashMap};

pub use pallas_traverse::{MultiEraInput, MultiEraOutput};

pub type UTxOs<'b> = HashMap<MultiEraInput<'b>, MultiEraOutput<'b>>;

// TODO: add a field for each protocol parameter in the Byron era.
#[derive(Debug, Clone)]
pub struct ByronProtParams;

// TODO: add variants for the other eras.
#[derive(Debug)]
#[non_exhaustive]
pub enum MultiEraProtParams<'b> {
Byron(Box<Cow<'b, ByronProtParams>>),
}

// TODO: replace this generic variant with validation-rule-specific ones.
#[derive(Debug)]
#[non_exhaustive]
pub enum ValidationError {
ValidationError,
}

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

use pallas_applying::{
types::{ByronProtParams, MultiEraProtParams},
validate, UTxOs, ValidationResult,
};
use pallas_codec::{
minicbor::{
bytes::ByteVec,
decode::{Decode, Decoder},
encode,
},
utils::{CborWrap, EmptyMap, KeepRaw, MaybeIndefArray, TagWrap},
};
use pallas_crypto::hash::Hash;
use pallas_primitives::byron::{
Address, Attributes, MintedTxPayload as ByronTxPayload, Tx as ByronTx, TxId as ByronTxId,
TxIn as ByronTxIn, TxOut as ByronTxOut, Witnesses as ByronWitnesses,
};
use pallas_traverse::{MultiEraInput, MultiEraOutput, MultiEraTx};

#[cfg(test)]
mod byron_tests {
use super::*;

#[test]
// Note that:
// 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.
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);
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 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);
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(),
&utxos,
&protocol_params,
);
match validation_result {
Ok(()) => (),
Err(err) => assert!(false, "Unexpected error (sucessful_case - {:?}).", err),
}
}
}

// Types aliases.
type ByronTxIns = MaybeIndefArray<ByronTxIn>;
type ByronTxOuts = MaybeIndefArray<ByronTxOut>;

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

fn random_tx_id() -> ByronTxId {
let mut rng = rand::thread_rng();
let mut bytes = [0u8; 32];
for elem in bytes.iter_mut() {
*elem = rng.gen();
}
Hash::new(bytes)
}

fn new_tx_in(tx_id: ByronTxId, index: u32) -> ByronTxIn {
ByronTxIn::Variant0(CborWrap((tx_id, index)))
}

fn add_byron_tx_in(ins: &mut ByronTxIns, new_in: &ByronTxIn) {
match ins {
MaybeIndefArray::Def(vec) | MaybeIndefArray::Indef(vec) => vec.push(new_in.clone()),
}
}

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

fn random_address_payload() -> TagWrap<ByteVec, 24> {
let mut rng = rand::thread_rng();
let mut bytes = [0u8; 24];
for elem in bytes.iter_mut() {
*elem = rng.gen();
}
TagWrap::<ByteVec, 24>::new(ByteVec::from(bytes.to_vec()))
}

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

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

fn add_byron_tx_out(outs: &mut ByronTxOuts, new_out: &ByronTxOut) {
match outs {
MaybeIndefArray::Def(vec) | MaybeIndefArray::Indef(vec) => vec.push(new_out.clone()),
}
}

fn add_to_utxo<'a>(utxos: &mut UTxOs<'a>, tx_in: ByronTxIn, tx_out: ByronTxOut) {
let multi_era_in: MultiEraInput = MultiEraInput::Byron(Box::new(Cow::Owned(tx_in)));
let multi_era_out: MultiEraOutput = MultiEraOutput::Byron(Box::new(Cow::Owned(tx_out)));
utxos.insert(multi_era_in, multi_era_out);
}

fn empty_byron_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.
fn mk_byron_tx_and_validate(
btx: &ByronTx,
bwit: &ByronWitnesses,
utxos: &UTxOs,
prot_pps: &ByronProtParams,
) -> ValidationResult {
// Encode btx and decode into a KeepRaw<ByronTx> value.
let mut btx_buf: Vec<u8> = Vec::new();
match encode(btx, &mut btx_buf) {
Ok(_) => (),
Err(err) => assert!(false, "Unable to encode ByronTx ({:?}).", err),
};
let kpbtx: KeepRaw<ByronTx> =
match Decode::decode(&mut Decoder::new(&btx_buf.as_slice()), &mut ()) {
Ok(kp) => kp,
Err(err) => panic!("Unable to decode ByronTx ({:?}).", err),
};

// Encode bwit and decode into a KeepRaw<ByronWitnesses> value.
let mut wit_buf: Vec<u8> = Vec::new();
match encode(bwit, &mut wit_buf) {
Ok(_) => (),
Err(err) => assert!(false, "Unable to encode ByronWitnesses ({:?}).", err),
};
let kpbwit: KeepRaw<ByronWitnesses> =
match Decode::decode(&mut Decoder::new(&wit_buf.as_slice()), &mut ()) {
Ok(kp) => kp,
Err(err) => panic!("Unable to decode ByronWitnesses ({:?}).", err),
};

let mtxp: ByronTxPayload = ByronTxPayload {
transaction: kpbtx,
witness: kpbwit,
};
let metx: MultiEraTx = MultiEraTx::from_byron(&mtxp);
validate(
&metx,
utxos,
&MultiEraProtParams::Byron(Box::new(Cow::Borrowed(&prot_pps))),
)
}

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

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

fn new_utxos<'a>() -> UTxOs<'a> {
UTxOs::new()
}
6 changes: 3 additions & 3 deletions pallas-codec/src/utils.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use minicbor::{data::Tag, Decode, Encode};
use serde::{Deserialize, Serialize};
use std::{fmt, ops::Deref};
use std::{fmt, hash::Hash as StdHash, ops::Deref};

/// Utility for skipping parts of the CBOR payload, use only for debugging
#[derive(Debug, PartialEq, PartialOrd, Eq, Ord)]
Expand Down Expand Up @@ -292,7 +292,7 @@ where
}

/// Wraps a struct so that it is encoded/decoded as a cbor bytes
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd)]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, StdHash)]
#[serde(transparent)]
pub struct CborWrap<T>(pub T);

Expand Down Expand Up @@ -390,7 +390,7 @@ impl<I, const T: u64> Deref for TagWrap<I, T> {
/// An empty map
///
/// don't ask me why, that's what the CDDL asks for.
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EmptyMap;

impl<'b, C> minicbor::decode::Decode<'b, C> for EmptyMap {
Expand Down
16 changes: 8 additions & 8 deletions pallas-network/src/miniprotocols/localtxsubmission/codec.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
use pallas_codec::minicbor::{decode, Decode, Decoder, encode, Encode, Encoder};
use pallas_codec::minicbor::data::Tag;
use pallas_codec::minicbor::{decode, encode, Decode, Decoder, Encode, Encoder};

use crate::miniprotocols::localtxsubmission::{EraTx, Message, RejectReason};

impl<Tx, Reject> Encode<()> for Message<Tx, Reject>
where
Tx: Encode<()>,
Reject: Encode<()>,
where
Tx: Encode<()>,
Reject: Encode<()>,
{
fn encode<W: encode::Write>(
&self,
Expand Down Expand Up @@ -97,14 +97,14 @@ impl Encode<()> for RejectReason {
) -> Result<(), encode::Error<W::Error>> {
e.writer_mut()
.write_all(&self.0)
.map_err(|w_err| encode::Error::write(w_err))?;
.map_err(encode::Error::write)?;
Ok(())
}
}

#[cfg(test)]
mod tests {
use pallas_codec::{Fragment, minicbor};
use pallas_codec::{minicbor, Fragment};

use crate::miniprotocols::localtxsubmission::{EraTx, Message, RejectReason};
use crate::multiplexer::Error;
Expand All @@ -117,8 +117,8 @@ mod tests {
}

fn try_decode_message<M>(buffer: &mut Vec<u8>) -> Result<Option<M>, Error>
where
M: Fragment,
where
M: Fragment,
{
let mut decoder = minicbor::Decoder::new(buffer);
let maybe_msg = decoder.decode();
Expand Down
6 changes: 4 additions & 2 deletions pallas-primitives/src/alonzo/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
//! Handcrafted, idiomatic rust artifacts based on based on the [Alonzo CDDL](https://github.com/input-output-hk/cardano-ledger/blob/master/eras/alonzo/test-suite/cddl-files/alonzo.cddl) file in IOHK repo.
use serde::{Deserialize, Serialize};
use std::{fmt, ops::Deref};
use std::{fmt, hash::Hash as StdHash, ops::Deref};

use pallas_codec::minicbor::{data::Tag, Decode, Encode};
use pallas_crypto::hash::Hash;
Expand Down Expand Up @@ -78,7 +78,9 @@ pub struct Header {
pub body_signature: Bytes,
}

#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
#[derive(
Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, StdHash,
)]
pub struct TransactionInput {
#[n(0)]
pub transaction_id: Hash<32>,
Expand Down
Loading

0 comments on commit 986ee75

Please sign in to comment.