feat(applying): add remaining validations for Byron era (#325)

This commit is contained in:
Maico Leberle 2023-11-09 14:50:46 -03:00 committed by GitHub
parent ac4aef4eaa
commit 68b46c36a8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 562 additions and 224 deletions

View file

@ -13,8 +13,12 @@ authors = ["Maico Leberle <maico.leberle@gmail.com>"]
doctest = false
[dependencies]
pallas-addresses = { path = "../pallas-addresses" }
pallas-codec = { path = "../pallas-codec" }
pallas-crypto = { path = "../pallas-crypto" }
pallas-primitives = { path = "../pallas-primitives" }
pallas-traverse = { path = "../pallas-traverse" }
rand = "0.8"
[dev-dependencies]
hex = "0.4"

View file

@ -1,19 +1,43 @@
//! Utilities required for Byron-era transaction validation.
use crate::types::{ByronProtParams, MultiEraInput, UTxOs, ValidationError, ValidationResult};
use std::borrow::Cow;
use pallas_primitives::byron::{MintedTxPayload, Tx};
use crate::types::{
ByronProtParams, MultiEraInput, MultiEraOutput, SigningTag, UTxOs, ValidationError,
ValidationResult,
};
use pallas_addresses::byron::{
AddrAttrs, AddrType, AddressId, AddressPayload, ByronAddress, SpendingData,
};
use pallas_codec::{
minicbor::{encode, Encoder},
utils::CborWrap,
};
use pallas_crypto::{
hash::Hash,
key::ed25519::{PublicKey, Signature},
};
use pallas_primitives::byron::{
Address, MintedTxPayload, PubKey, Signature as ByronSignature, Twit, Tx, TxIn, TxOut,
};
use pallas_traverse::OriginalHash;
// TODO: implement missing validation rules.
pub fn validate_byron_tx(
mtxp: &MintedTxPayload,
utxos: &UTxOs,
_prot_pps: &ByronProtParams,
prot_pps: &ByronProtParams,
prot_magic: &u32,
) -> ValidationResult {
let tx: &Tx = &mtxp.transaction;
let size: u64 = get_tx_size(tx)?;
check_ins_not_empty(tx)?;
check_outs_not_empty(tx)?;
check_ins_in_utxos(tx, utxos)
check_ins_in_utxos(tx, utxos)?;
check_outs_have_lovelace(tx)?;
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 {
@ -38,3 +62,192 @@ fn check_ins_in_utxos(tx: &Tx, utxos: &UTxOs) -> ValidationResult {
}
Ok(())
}
fn check_outs_have_lovelace(tx: &Tx) -> ValidationResult {
for output in tx.outputs.iter() {
if output.amount == 0 {
return Err(ValidationError::OutputWithoutLovelace);
}
}
Ok(())
}
fn check_fees(tx: &Tx, size: &u64, utxos: &UTxOs, prot_pps: &ByronProtParams) -> ValidationResult {
let mut inputs_balance: u64 = 0;
for input in tx.inputs.iter() {
match utxos
.get(&MultiEraInput::from_byron(input))
.and_then(MultiEraOutput::as_byron)
{
Some(byron_utxo) => inputs_balance += byron_utxo.amount,
None => return Err(ValidationError::UnableToComputeFees),
}
}
let mut outputs_balance: u64 = 0;
for output in tx.outputs.iter() {
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;
if total_balance < min_fees {
return Err(ValidationError::FeesBelowMin);
}
Ok(())
}
fn check_size(size: &u64, prot_pps: &ByronProtParams) -> ValidationResult {
if *size > prot_pps.max_tx_size {
return Err(ValidationError::MaxTxSizeExceeded);
}
Ok(())
}
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),
}
}
pub enum TaggedSignature<'a> {
PkWitness(&'a ByronSignature),
RedeemWitness(&'a ByronSignature),
}
fn check_witnesses(mtxp: &MintedTxPayload, utxos: &UTxOs, prot_magic: &u32) -> ValidationResult {
let tx: &Tx = &mtxp.transaction;
let tx_hash: Hash<32> = mtxp.transaction.original_hash();
let witnesses: Vec<(&PubKey, TaggedSignature)> = tag_witnesses(&mtxp.witness)?;
let tx_inputs: &Vec<TxIn> = &tx.inputs;
for input in tx_inputs {
let tx_out: &TxOut = find_tx_out(input, utxos)?;
let (pub_key, sign): (&PubKey, &TaggedSignature) = find_raw_witness(tx_out, &witnesses)?;
let public_key: PublicKey = get_verification_key(pub_key);
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);
}
}
Ok(())
}
fn tag_witnesses(wits: &[Twit]) -> Result<Vec<(&PubKey, TaggedSignature)>, ValidationError> {
let mut res: Vec<(&PubKey, TaggedSignature)> = Vec::new();
for wit in wits.iter() {
match wit {
Twit::PkWitness(CborWrap((pk, sig))) => {
res.push((pk, TaggedSignature::PkWitness(sig)));
}
Twit::RedeemWitness(CborWrap((pk, sig))) => {
res.push((pk, TaggedSignature::RedeemWitness(sig)));
}
_ => return Err(ValidationError::UnableToProcessWitnesses),
}
}
Ok(res)
}
fn find_tx_out<'a>(input: &'a TxIn, utxos: &'a UTxOs) -> Result<&'a TxOut, ValidationError> {
let key: MultiEraInput = MultiEraInput::Byron(Box::new(Cow::Borrowed(input)));
utxos
.get(&key)
.ok_or(ValidationError::InputMissingInUTxO)?
.as_byron()
.ok_or(ValidationError::InputMissingInUTxO)
}
fn find_raw_witness<'a>(
tx_out: &TxOut,
witnesses: &'a Vec<(&'a PubKey, TaggedSignature<'a>)>,
) -> Result<(&'a PubKey, &'a TaggedSignature<'a>), ValidationError> {
let address: ByronAddress = mk_byron_address(&tx_out.address);
let addr_payload: AddressPayload = address
.decode()
.map_err(|_| ValidationError::UnableToProcessWitnesses)?;
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),
}
}
}
Err(ValidationError::MissingWitness)
}
fn mk_byron_address(addr: &Address) -> ByronAddress {
ByronAddress::new((*addr.payload.0).as_slice(), addr.crc)
}
fn redeems(
pub_key: &PubKey,
sign: &TaggedSignature,
root: &AddressId,
attrs: &AddrAttrs,
addr_type: &AddrType,
) -> bool {
let spending_data: SpendingData = mk_spending_data(pub_key, addr_type);
let hash_to_check: AddressId =
AddressPayload::hash_address_id(addr_type, &spending_data, attrs);
hash_to_check == *root && convert_to_addr_type(sign) == *addr_type
}
fn convert_to_addr_type(sign: &TaggedSignature) -> AddrType {
match sign {
TaggedSignature::PkWitness(_) => AddrType::PubKey,
TaggedSignature::RedeemWitness(_) => AddrType::Redeem,
}
}
fn mk_spending_data(pub_key: &PubKey, addr_type: &AddrType) -> SpendingData {
match addr_type {
AddrType::PubKey => SpendingData::PubKey(pub_key.clone()),
AddrType::Redeem => SpendingData::Redeem(pub_key.clone()),
_ => unreachable!(),
}
}
fn get_verification_key(pk: &PubKey) -> PublicKey {
let mut trunc_len: [u8; PublicKey::SIZE] = [0; PublicKey::SIZE];
trunc_len.copy_from_slice(&pk.as_slice()[0..PublicKey::SIZE]);
From::<[u8; PublicKey::SIZE]>::from(trunc_len)
}
fn get_data_to_verify(
sign: &TaggedSignature,
prot_magic: &u32,
tx_hash: &Hash<32>,
) -> Result<Vec<u8>, ValidationError> {
let buff: &mut Vec<u8> = &mut Vec::new();
let mut enc: Encoder<&mut Vec<u8>> = Encoder::new(buff);
match sign {
TaggedSignature::PkWitness(_) => {
enc.encode(SigningTag::Tx as u64)
.map_err(|_| ValidationError::UnableToProcessWitnesses)?;
}
TaggedSignature::RedeemWitness(_) => {
enc.encode(SigningTag::RedeemTx as u64)
.map_err(|_| ValidationError::UnableToProcessWitnesses)?;
}
}
enc.encode(prot_magic)
.map_err(|_| ValidationError::UnableToProcessWitnesses)?;
enc.encode(tx_hash)
.map_err(|_| ValidationError::UnableToProcessWitnesses)?;
Ok(enc.into_writer().clone())
}
fn get_signature(tagged_signature: &TaggedSignature<'_>) -> Signature {
let inner_sig = match tagged_signature {
TaggedSignature::PkWitness(sign) => sign,
TaggedSignature::RedeemWitness(sign) => sign,
};
let mut trunc_len: [u8; Signature::SIZE] = [0; Signature::SIZE];
trunc_len.copy_from_slice(inner_sig.as_slice());
From::<[u8; Signature::SIZE]>::from(trunc_len)
}

View file

@ -7,17 +7,17 @@ use byron::validate_byron_tx;
use pallas_traverse::{MultiEraTx, MultiEraTx::Byron as ByronTxPayload};
pub use types::{
MultiEraProtParams, MultiEraProtParams::Byron as ByronProtParams, UTxOs, ValidationResult,
};
pub use types::{Environment, MultiEraProtParams, 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),
pub fn validate(metx: &MultiEraTx, utxos: &UTxOs, env: &Environment) -> ValidationResult {
match (metx, env) {
(
ByronTxPayload(mtxp),
Environment {
prot_params: MultiEraProtParams::Byron(bpp),
prot_magic,
},
) => validate_byron_tx(mtxp, utxos, bpp, prot_magic),
// TODO: implement the rest of the eras.
_ => Ok(()),
}

View file

@ -1,29 +1,51 @@
//! Base types used for validating transactions in each era.
use std::{borrow::Cow, collections::HashMap};
use std::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;
pub struct ByronProtParams {
pub min_fees_const: u64,
pub min_fees_factor: u64,
pub max_tx_size: u64,
}
// TODO: add variants for the other eras.
#[derive(Debug)]
#[non_exhaustive]
pub enum MultiEraProtParams<'b> {
Byron(Box<Cow<'b, ByronProtParams>>),
pub enum MultiEraProtParams {
Byron(ByronProtParams),
}
#[derive(Debug)]
pub struct Environment {
pub prot_params: MultiEraProtParams,
pub prot_magic: u32,
}
#[non_exhaustive]
pub enum SigningTag {
Tx = 0x01,
RedeemTx = 0x02,
}
// TODO: replace this generic variant with validation-rule-specific ones.
#[derive(Debug)]
#[non_exhaustive]
pub enum ValidationError {
InputMissingInUTxO,
TxInsEmpty,
TxOutsEmpty,
OutputWithoutLovelace,
UnknownTxSize,
UnableToComputeFees,
FeesBelowMin,
MaxTxSizeExceeded,
UnableToProcessWitnesses,
MissingWitness,
WrongSignature,
}
pub type ValidationResult = Result<(), ValidationError>;

View file

@ -0,0 +1,20 @@
# Testing framework documentation
## Execution
Starting at the root of the repository, simply go to *pallas-applying* and run `cargo test`.
## Explanations
*pallas-applying/tests/byron.rs* contains multiple unit tests for validation on the Byron era.
The first one, **suceessful_mainnet_tx**, is a positive unit test. It takes the CBOR of a mainnet transaction. Namely, the one whose hash is `a06e5a0150e09f8983be2deafab9e04afc60d92e7110999eb672c903343f1e26`, which can be viewed on Cardano Explorer [here](https://cexplorer.io/tx/a06e5a0150e09f8983be2deafab9e04afc60d92e7110999eb672c903343f1e26). Such a transaction has a single input which is added to the UTxO, prior to validation, by associating it to a transaction output sitting at its real (mainnet) address. This information was taken from Cardano Explorer as well, following the address link of the only input to the transaction, and taking its raw address CBOR content.
Then comes a series of negative unit tests, namely:
- **empty_ins** takes the mainnet transaction, removes its input, and calls validation on it.
- **empty_outs** is analogous to the **empty_ins** test, removing all outputs instead.
- **unfound_utxo** takes the mainnet transaction and calls validation on it without a proper UTxO containing an entry for its input.
- **output_without_lovelace** takes the mainnet transaction and modifies its output by removing all of its lovelace.
- **not_enough_fees** takes the mainnet transaction and calls validation on it using wrong protocol parameters, which requiere that the transaction pay a higher fee than the one actually paid.
- **tx_size_exceeds_max** takes the mainnet transaction and calls validation on it using wrong protocol parameters, which only allow transactions of a size smaller than that of the transaction.
- **missing_witness** takes the mainnet transaction, removes its witness, and calls validation on it.
- **wrong_signature** takes the mainnet transaction, alters the content of its witness, and calls validation on it.

View file

@ -1,8 +1,7 @@
use rand::Rng;
use std::{borrow::Cow, vec::Vec};
use pallas_applying::{
types::{ByronProtParams, MultiEraProtParams, ValidationError},
types::{ByronProtParams, Environment, MultiEraProtParams, ValidationError},
validate, UTxOs, ValidationResult,
};
use pallas_codec::{
@ -11,77 +10,89 @@ use pallas_codec::{
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,
utils::{CborWrap, KeepRaw, MaybeIndefArray, TagWrap},
};
use pallas_primitives::byron::{Address, MintedTxPayload, Twit, Tx, TxIn, TxOut, Witnesses};
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_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_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);
fn cbor_to_bytes(input: &str) -> Vec<u8> {
hex::decode(input).unwrap()
}
fn mainnet_tx_from_bytes_cbor<'a>(tx_cbor: &'a Vec<u8>) -> MintedTxPayload<'a> {
pallas_codec::minicbor::decode::<MintedTxPayload>(&tx_cbor[..]).unwrap()
}
fn build_utxo<'a>(tx: &Tx) -> UTxOs<'a> {
let mut tx_ins: Vec<TxIn> = tx.inputs.clone().to_vec();
assert_eq!(tx_ins.len(), 1, "Unexpected number of inputs.");
let tx_in: TxIn = tx_ins.pop().unwrap();
let address_payload =
"83581cff66e7549ee0706abe5ce63ba325f792f2c1145d918baf563db2b457a101581e581cca3e553c9c63\
c5927480e7434620200eb3a162ef0b6cf6f671ba925100";
let input_tx_out_addr: Address = match hex::decode(address_payload) {
Ok(addr_bytes) => Address {
payload: TagWrap(ByteVec::from(addr_bytes)),
crc: 3430631884,
},
_ => panic!("Unable to decode input address."),
};
let tx_out: TxOut = TxOut {
address: input_tx_out_addr,
amount: 19999000000,
};
let mut utxos: UTxOs = new_utxos();
// input_tx_out is the ByronTxOut associated with tx_in.
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 {
add_to_utxo(&mut utxos, tx_in, tx_out);
utxos
}
#[test]
fn successful_mainnet_tx() {
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/byron1.tx"));
let mtxp: MintedTxPayload = mainnet_tx_from_bytes_cbor(&cbor_bytes);
let utxos: UTxOs = build_utxo(&mtxp.transaction);
let env: Environment = Environment {
prot_params: MultiEraProtParams::Byron(ByronProtParams {
min_fees_const: 155381,
min_fees_factor: 44,
max_tx_size: 4096,
}),
prot_magic: 764824073,
};
match mk_byron_tx_and_validate(&mtxp.transaction, &mtxp.witness, &utxos, &env) {
Ok(()) => (),
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.
// Identical to successful_mainnet_tx, except that all inputs are removed.
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 {
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/byron1.tx"));
let mut mtxp: MintedTxPayload = mainnet_tx_from_bytes_cbor(&cbor_bytes);
let utxos: UTxOs = build_utxo(&mtxp.transaction);
// Clear the set of inputs in the transaction.
let mut tx: Tx = (*mtxp.transaction).clone();
tx.inputs = MaybeIndefArray::Def(Vec::new());
let mut tx_buf: Vec<u8> = Vec::new();
match encode(tx, &mut tx_buf) {
Ok(_) => (),
Err(err) => assert!(false, "Unable to encode Tx ({:?}).", err),
};
mtxp.transaction = Decode::decode(&mut Decoder::new(&tx_buf.as_slice()), &mut ()).unwrap();
let env: Environment = Environment {
prot_params: MultiEraProtParams::Byron(ByronProtParams {
min_fees_const: 155381,
min_fees_factor: 44,
max_tx_size: 4096,
}),
prot_magic: 764824073,
};
match mk_byron_tx_and_validate(&mtxp.transaction, &mtxp.witness, &utxos, &env) {
Ok(()) => assert!(false, "Inputs set should not be empty."),
Err(err) => match err {
ValidationError::TxInsEmpty => (),
@ -91,25 +102,29 @@ mod byron_tests {
}
#[test]
// Similar to empty_ins, except that this time no outputs are added to the
// transaction, which should raise a ValidationError:TxOutsEmpty error.
// Identical to successful_mainnet_tx, except that all outputs are removed.
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 {
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/byron1.tx"));
let mut mtxp: MintedTxPayload = mainnet_tx_from_bytes_cbor(&cbor_bytes);
let utxos: UTxOs = build_utxo(&mtxp.transaction);
// Clear the set of outputs in the transaction.
let mut tx: Tx = (*mtxp.transaction).clone();
tx.outputs = MaybeIndefArray::Def(Vec::new());
let mut tx_buf: Vec<u8> = Vec::new();
match encode(tx, &mut tx_buf) {
Ok(_) => (),
Err(err) => assert!(false, "Unable to encode Tx ({:?}).", err),
};
mtxp.transaction = Decode::decode(&mut Decoder::new(&tx_buf.as_slice()), &mut ()).unwrap();
let env: Environment = Environment {
prot_params: MultiEraProtParams::Byron(ByronProtParams {
min_fees_const: 155381,
min_fees_factor: 44,
max_tx_size: 4096,
}),
prot_magic: 764824073,
};
match mk_byron_tx_and_validate(&mtxp.transaction, &mtxp.witness, &utxos, &env) {
Ok(()) => assert!(false, "Outputs set should not be empty."),
Err(err) => match err {
ValidationError::TxOutsEmpty => (),
@ -119,28 +134,20 @@ mod byron_tests {
}
#[test]
// The UTxO set does not contain an entry for the single input to this transaction. This
// represents the situation where a transaction tries to spend a non-existent UTxO (e.g., one
// which has already been spent).
// The transaction is valid, but the UTxO set is empty.
fn unfound_utxo() {
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 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);
// Note: utxos is empty, hence the only input to this transaction will not be found, for
// which an error should be raised.
let utxos: UTxOs = new_utxos();
let validation_result = mk_byron_tx_and_validate(
&new_tx(tx_ins, tx_outs, empty_attributes()),
&empty_witnesses(),
&utxos,
&protocol_params,
);
match validation_result {
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/byron1.tx"));
let mtxp: MintedTxPayload = mainnet_tx_from_bytes_cbor(&cbor_bytes);
let utxos: UTxOs = UTxOs::new();
let env: Environment = Environment {
prot_params: MultiEraProtParams::Byron(ByronProtParams {
min_fees_const: 155381,
min_fees_factor: 44,
max_tx_size: 4096,
}),
prot_magic: 764824073,
};
match mk_byron_tx_and_validate(&mtxp.transaction, &mtxp.witness, &utxos, &env) {
Ok(()) => assert!(false, "All inputs must be within the UTxO set."),
Err(err) => match err {
ValidationError::InputMissingInUTxO => (),
@ -148,135 +155,206 @@ mod byron_tests {
},
}
}
#[test]
// All lovelace in one of the outputs was removed.
fn output_without_lovelace() {
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/byron1.tx"));
let mut mtxp: MintedTxPayload = mainnet_tx_from_bytes_cbor(&cbor_bytes);
let utxos: UTxOs = build_utxo(&mtxp.transaction);
// Remove lovelace from output.
let mut tx: Tx = (*mtxp.transaction).clone();
let altered_tx_out: TxOut = TxOut {
address: tx.outputs[0].address.clone(),
amount: 0,
};
let mut new_tx_outs: Vec<TxOut> = Vec::new();
new_tx_outs.push(tx.outputs[1].clone());
new_tx_outs.push(altered_tx_out);
tx.outputs = MaybeIndefArray::Indef(new_tx_outs);
let mut tx_buf: Vec<u8> = Vec::new();
match encode(tx, &mut tx_buf) {
Ok(_) => (),
Err(err) => assert!(false, "Unable to encode Tx ({:?}).", err),
};
mtxp.transaction = Decode::decode(&mut Decoder::new(&tx_buf.as_slice()), &mut ()).unwrap();
let env: Environment = Environment {
prot_params: MultiEraProtParams::Byron(ByronProtParams {
min_fees_const: 155381,
min_fees_factor: 44,
max_tx_size: 4096,
}),
prot_magic: 764824073,
};
match mk_byron_tx_and_validate(&mtxp.transaction, &mtxp.witness, &utxos, &env) {
Ok(()) => assert!(false, "All outputs must contain lovelace."),
Err(err) => match err {
ValidationError::OutputWithoutLovelace => (),
_ => assert!(false, "Unexpected error ({:?}).", err),
},
}
}
// Types aliases.
type ByronTxIns = MaybeIndefArray<ByronTxIn>;
type ByronTxOuts = MaybeIndefArray<ByronTxOut>;
// Helper functions.
fn empty_tx_ins() -> ByronTxIns {
MaybeIndefArray::Def(Vec::new())
#[test]
// Expected fees are increased by increasing the protocol parameters.
fn not_enough_fees() {
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/byron1.tx"));
let mtxp: MintedTxPayload = mainnet_tx_from_bytes_cbor(&cbor_bytes);
let utxos: UTxOs = build_utxo(&mtxp.transaction);
let env: Environment = Environment {
prot_params: MultiEraProtParams::Byron(ByronProtParams {
min_fees_const: 1000,
min_fees_factor: 1000,
max_tx_size: 4096,
}),
prot_magic: 764824073,
};
match mk_byron_tx_and_validate(&mtxp.transaction, &mtxp.witness, &utxos, &env) {
Ok(()) => assert!(false, "Fees should not be below minimum."),
Err(err) => match err {
ValidationError::FeesBelowMin => (),
_ => assert!(false, "Unexpected error ({:?}).", err),
},
}
}
fn rand_tx_id() -> ByronTxId {
let mut rng = rand::thread_rng();
let mut bytes = [0u8; 32];
for elem in bytes.iter_mut() {
*elem = rng.gen();
#[test]
// Tx size limit set by protocol parameters is established at 0.
fn tx_size_exceeds_max() {
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/byron1.tx"));
let mtxp: MintedTxPayload = mainnet_tx_from_bytes_cbor(&cbor_bytes);
let utxos: UTxOs = build_utxo(&mtxp.transaction);
let env: Environment = Environment {
prot_params: MultiEraProtParams::Byron(ByronProtParams {
min_fees_const: 155381,
min_fees_factor: 44,
max_tx_size: 0,
}),
prot_magic: 764824073,
};
match mk_byron_tx_and_validate(&mtxp.transaction, &mtxp.witness, &utxos, &env) {
Ok(()) => assert!(false, "Transaction size cannot exceed protocol limit."),
Err(err) => match err {
ValidationError::MaxTxSizeExceeded => (),
_ => assert!(false, "Unexpected error ({:?}).", err),
},
}
Hash::new(bytes)
}
fn new_tx_in(tx_id: ByronTxId, index: u32) -> ByronTxIn {
ByronTxIn::Variant0(CborWrap((tx_id, index)))
#[test]
// The input to the transaction does not have a corresponding witness.
fn missing_witness() {
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/byron1.tx"));
let mut mtxp: MintedTxPayload = mainnet_tx_from_bytes_cbor(&cbor_bytes);
let utxos: UTxOs = build_utxo(&mtxp.transaction);
// Remove witness
let new_witnesses: Witnesses = MaybeIndefArray::Def(Vec::new());
let mut tx_buf: Vec<u8> = Vec::new();
match encode(new_witnesses, &mut tx_buf) {
Ok(_) => (),
Err(err) => assert!(false, "Unable to encode Tx ({:?}).", err),
};
mtxp.witness = Decode::decode(&mut Decoder::new(&tx_buf.as_slice()), &mut ()).unwrap();
let env: Environment = Environment {
prot_params: MultiEraProtParams::Byron(ByronProtParams {
min_fees_const: 155381,
min_fees_factor: 44,
max_tx_size: 4096,
}),
prot_magic: 764824073,
};
match mk_byron_tx_and_validate(&mtxp.transaction, &mtxp.witness, &utxos, &env) {
Ok(()) => assert!(false, "All inputs must have a witness signature."),
Err(err) => match err {
ValidationError::MissingWitness => (),
_ => assert!(false, "Unexpected error ({:?}).", err),
},
}
}
fn add_byron_tx_in(ins: &mut ByronTxIns, new_in: &ByronTxIn) {
match ins {
MaybeIndefArray::Def(vec) | MaybeIndefArray::Indef(vec) => vec.push(new_in.clone()),
#[test]
// The input to the transaction has an associated witness, but the signature is wrong.
fn wrong_signature() {
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/byron1.tx"));
let mut mtxp: MintedTxPayload = mainnet_tx_from_bytes_cbor(&cbor_bytes);
let utxos: UTxOs = build_utxo(&mtxp.transaction);
// Modify signature in witness
let new_wit: Twit = match mtxp.witness[0].clone() {
Twit::PkWitness(CborWrap((pk, _))) => {
Twit::PkWitness(CborWrap((pk, [0u8; 64].to_vec().into())))
}
_ => unreachable!(),
};
let mut new_witnesses_vec = Vec::new();
new_witnesses_vec.push(new_wit);
let new_witnesses: Witnesses = MaybeIndefArray::Def(new_witnesses_vec);
let mut tx_buf: Vec<u8> = Vec::new();
match encode(new_witnesses, &mut tx_buf) {
Ok(_) => (),
Err(err) => assert!(false, "Unable to encode Tx ({:?}).", err),
};
mtxp.witness = Decode::decode(&mut Decoder::new(&tx_buf.as_slice()), &mut ()).unwrap();
let env: Environment = Environment {
prot_params: MultiEraProtParams::Byron(ByronProtParams {
min_fees_const: 155381,
min_fees_factor: 44,
max_tx_size: 4096,
}),
prot_magic: 764824073,
};
match mk_byron_tx_and_validate(&mtxp.transaction, &mtxp.witness, &utxos, &env) {
Ok(()) => assert!(false, "Witness signature should verify the transaction."),
Err(err) => match err {
ValidationError::WrongSignature => (),
_ => assert!(false, "Unexpected error ({:?}).", err),
},
}
}
}
fn new_tx_outs() -> ByronTxOuts {
MaybeIndefArray::Def(Vec::new())
}
fn rand_addr_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_addr(payload: TagWrap<ByteVec, 24>, crc: u32) -> Address {
Address {
payload: payload,
crc: crc,
}
}
fn new_tx_out(address: Address, amount: u64) -> ByronTxOut {
ByronTxOut {
address: address,
amount: amount,
}
}
fn add_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) {
// Helper functions.
fn add_to_utxo<'a>(utxos: &mut UTxOs<'a>, tx_in: TxIn, tx_out: TxOut) {
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_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 Tx and a Witnesses. To be able to build a
// MultiEraTx from a Tx and a Witnesses, we need to encode each of them and then decode them into
// KeepRaw<Tx> and KeepRaw<Witnesses> values, respectively, to be able to make the MultiEraTx value.
fn mk_byron_tx_and_validate(
btx: &ByronTx,
bwit: &ByronWitnesses,
tx: &Tx,
wits: &Witnesses,
utxos: &UTxOs,
prot_pps: &ByronProtParams,
env: &Environment,
) -> ValidationResult {
// Encode btx and decode into a KeepRaw<ByronTx> value.
let mut btx_buf: Vec<u8> = Vec::new();
match encode(btx, &mut btx_buf) {
let mut tx_buf: Vec<u8> = Vec::new();
match encode(tx, &mut tx_buf) {
Ok(_) => (),
Err(err) => assert!(false, "Unable to encode ByronTx ({:?}).", err),
Err(err) => assert!(false, "Unable to encode Tx ({:?}).", err),
};
let kpbtx: KeepRaw<ByronTx> =
match Decode::decode(&mut Decoder::new(&btx_buf.as_slice()), &mut ()) {
let kptx: KeepRaw<Tx> = match Decode::decode(&mut Decoder::new(&tx_buf.as_slice()), &mut ()) {
Ok(kp) => kp,
Err(err) => panic!("Unable to decode ByronTx ({:?}).", err),
Err(err) => panic!("Unable to decode Tx ({:?}).", err),
};
// Encode bwit and decode into a KeepRaw<ByronWitnesses> value.
let mut wit_buf: Vec<u8> = Vec::new();
match encode(bwit, &mut wit_buf) {
match encode(wits, &mut wit_buf) {
Ok(_) => (),
Err(err) => assert!(false, "Unable to encode ByronWitnesses ({:?}).", err),
Err(err) => assert!(false, "Unable to encode Witnesses ({:?}).", err),
};
let kpbwit: KeepRaw<ByronWitnesses> =
let kpwit: KeepRaw<Witnesses> =
match Decode::decode(&mut Decoder::new(&wit_buf.as_slice()), &mut ()) {
Ok(kp) => kp,
Err(err) => panic!("Unable to decode ByronWitnesses ({:?}).", err),
Err(err) => panic!("Unable to decode Witnesses ({:?}).", err),
};
let mtxp: ByronTxPayload = ByronTxPayload {
transaction: kpbtx,
witness: kpbwit,
let mtxp: MintedTxPayload = MintedTxPayload {
transaction: kptx,
witness: kpwit,
};
let metx: MultiEraTx = MultiEraTx::from_byron(&mtxp);
validate(
&metx,
utxos,
&MultiEraProtParams::Byron(Box::new(Cow::Borrowed(&prot_pps))),
)
}
fn new_tx(ins: ByronTxIns, outs: ByronTxOuts, attrs: Attributes) -> ByronTx {
ByronTx {
inputs: ins,
outputs: outs,
attributes: attrs,
}
}
fn empty_witnesses() -> ByronWitnesses {
MaybeIndefArray::Def(Vec::new())
validate(&metx, utxos, env)
}
fn new_utxos<'a>() -> UTxOs<'a> {

1
test_data/byron1.tx Normal file
View file

@ -0,0 +1 @@
82839f8200d8185824825820da832fb5ef57df5b91817e9a7448d26e92552afb34f8ee5adb491b24bbe990d50eff9f8282d818584283581cdac5d9464c2140aeb0e3b6d69f0657e61f51e0c259fe19681ed268e8a101581e581c2b5a44277e3543c08eae5d9d9d1146f43ba009fea6e285334f2549be001ae69c4d201b0000000172a84e408282d818584283581c2b8e5e0cb6495ec275872d1340b0581613b04a49a3c6f2f760ecaf95a101581e581cca3e553c9c63c5b66689e943ce7dad7d560ae84d7c2eaf21611c024c001ad27c159a1b00000003355d95efffa0818200d8185885825840888cdf85991d85f2023423ba4c80d41570ebf1fc878c9f5731df1d20c64aecf3e8aa2bbafc9beba8ef33acb4d7e199b445229085718fba83b7f86ab6a3bcf782584063e34cf5fa6d8c0288630437fa5e151d93907e826e66ba273145e3ee712930b6f446ff81cb91d7f0cb4ceccd0466ba9ab14448d7eab9fc480a122324bd80170e