feat(applying): implement ShelleyMA phase-1 validations (#354)

This commit is contained in:
Maico Leberle 2023-12-12 07:53:10 -03:00 committed by GitHub
parent ffcd70c57a
commit 4e1eb79ef4
15 changed files with 1640 additions and 206 deletions

View file

@ -1,8 +1,11 @@
use std::{borrow::Cow, vec::Vec};
use pallas_applying::{
types::{ByronProtParams, Environment, MultiEraProtParams, ValidationError},
validate, UTxOs, ValidationResult,
types::{
ByronError::*, ByronProtParams, Environment, FeePolicy, MultiEraProtParams,
ValidationError::*,
},
validate, UTxOs,
};
use pallas_codec::{
minicbor::{
@ -10,67 +13,11 @@ use pallas_codec::{
decode::{Decode, Decoder},
encode,
},
utils::{CborWrap, KeepRaw, MaybeIndefArray, TagWrap},
utils::{CborWrap, MaybeIndefArray, TagWrap},
};
use pallas_primitives::byron::{Address, MintedTxPayload, Twit, Tx, TxIn, TxOut, Witnesses};
use pallas_traverse::{MultiEraInput, MultiEraOutput, MultiEraTx};
// Helper functions.
fn add_to_utxo(utxos: &mut UTxOs, 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);
}
// 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(
tx: &Tx,
wits: &Witnesses,
utxos: &UTxOs,
env: &Environment,
) -> ValidationResult {
let mut tx_buf: Vec<u8> = Vec::new();
match encode(tx, &mut tx_buf) {
Ok(_) => (),
Err(err) => panic!("Unable to encode Tx ({:?}).", err),
};
let kptx: KeepRaw<Tx> = match Decode::decode(&mut Decoder::new(tx_buf.as_slice()), &mut ()) {
Ok(kp) => kp,
Err(err) => panic!("Unable to decode Tx ({:?}).", err),
};
let mut wit_buf: Vec<u8> = Vec::new();
match encode(wits, &mut wit_buf) {
Ok(_) => (),
Err(err) => panic!("Unable to encode Witnesses ({:?}).", err),
};
let kpwit: KeepRaw<Witnesses> =
match Decode::decode(&mut Decoder::new(wit_buf.as_slice()), &mut ()) {
Ok(kp) => kp,
Err(err) => panic!("Unable to decode Witnesses ({:?}).", err),
};
let mtxp: MintedTxPayload = MintedTxPayload {
transaction: kptx,
witness: kpwit,
};
let metx: MultiEraTx = MultiEraTx::from_byron(&mtxp);
validate(&metx, utxos, env)
}
fn new_utxos<'a>() -> UTxOs<'a> {
UTxOs::new()
}
#[cfg(test)]
mod byron_tests {
use super::*;
@ -79,60 +26,66 @@ mod byron_tests {
hex::decode(input).unwrap()
}
fn mainnet_tx_from_bytes_cbor(tx_cbor: &[u8]) -> MintedTxPayload<'_> {
pallas_codec::minicbor::decode::<MintedTxPayload>(tx_cbor).unwrap()
fn tx_from_cbor<'a>(tx_cbor: &'a Vec<u8>) -> MintedTxPayload<'a> {
pallas_codec::minicbor::decode::<MintedTxPayload>(&tx_cbor[..]).unwrap()
}
// Careful: this function assumes tx has exactly one input.
fn mk_utxo_for_single_input_tx<'a>(tx: &Tx, address_payload: String, amount: u64) -> UTxOs<'a> {
let mut tx_ins: Vec<TxIn> = tx.inputs.clone().to_vec();
assert_eq!(tx_ins.len(), 1, "Unexpected number of inputs.");
assert_eq!(tx_ins.len(), 1, "Unexpected number of inputs");
let tx_in: TxIn = tx_ins.pop().unwrap();
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."),
_ => panic!("Unable to decode input address"),
};
let tx_out: TxOut = TxOut {
address: input_tx_out_addr,
amount,
};
let mut utxos: UTxOs = new_utxos();
let mut utxos: UTxOs = UTxOs::new();
add_to_utxo(&mut utxos, tx_in, tx_out);
utxos
}
#[test]
// Transaction hash: a9e4413a5fb61a7a43c7df006ffcaaf3f2ffc9541f54757023968c5a8f8294fd
fn successful_mainnet_tx_with_genesis_utxos() {
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/byron2.tx"));
let mtxp: MintedTxPayload = mainnet_tx_from_bytes_cbor(&cbor_bytes);
let mtxp: MintedTxPayload = tx_from_cbor(&cbor_bytes);
let metx: MultiEraTx = MultiEraTx::from_byron(&mtxp);
let utxos: UTxOs = mk_utxo_for_single_input_tx(
&mtxp.transaction,
String::from(include_str!("../../test_data/byron2.address")),
// The number of lovelace in this input is irrelevant, since no fees have to be paid
// for this transaction.
1,
19999000000,
);
let env: Environment = Environment {
prot_params: MultiEraProtParams::Byron(ByronProtParams {
min_fees_const: 155381,
min_fees_factor: 44,
fee_policy: FeePolicy {
summand: 155381,
multiplier: 44,
},
max_tx_size: 4096,
}),
prot_magic: 764824073,
block_slot: 6341,
network_id: 1,
};
match mk_byron_tx_and_validate(&mtxp.transaction, &mtxp.witness, &utxos, &env) {
match validate(&metx, &utxos, &env) {
Ok(()) => (),
Err(err) => panic!("Unexpected error ({:?}).", err),
Err(err) => panic!("Unexpected error ({:?})", err),
}
}
#[test]
// Transaction hash: a06e5a0150e09f8983be2deafab9e04afc60d92e7110999eb672c903343f1e26
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 mtxp: MintedTxPayload = tx_from_cbor(&cbor_bytes);
let metx: MultiEraTx = MultiEraTx::from_byron(&mtxp);
let utxos: UTxOs = mk_utxo_for_single_input_tx(
&mtxp.transaction,
String::from(include_str!("../../test_data/byron1.address")),
@ -140,15 +93,19 @@ mod byron_tests {
);
let env: Environment = Environment {
prot_params: MultiEraProtParams::Byron(ByronProtParams {
min_fees_const: 155381,
min_fees_factor: 44,
fee_policy: FeePolicy {
summand: 155381,
multiplier: 44,
},
max_tx_size: 4096,
}),
prot_magic: 764824073,
block_slot: 3241381,
network_id: 1,
};
match mk_byron_tx_and_validate(&mtxp.transaction, &mtxp.witness, &utxos, &env) {
match validate(&metx, &utxos, &env) {
Ok(()) => (),
Err(err) => panic!("Unexpected error ({:?}).", err),
Err(err) => panic!("Unexpected error ({:?})", err),
}
}
@ -156,7 +113,7 @@ mod byron_tests {
// Identical to successful_mainnet_tx, except that all inputs are removed.
fn empty_ins() {
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 mut mtxp: MintedTxPayload = tx_from_cbor(&cbor_bytes);
let utxos: UTxOs = mk_utxo_for_single_input_tx(
&mtxp.transaction,
String::from(include_str!("../../test_data/byron1.address")),
@ -168,22 +125,27 @@ mod byron_tests {
let mut tx_buf: Vec<u8> = Vec::new();
match encode(tx, &mut tx_buf) {
Ok(_) => (),
Err(err) => panic!("Unable to encode Tx ({:?}).", err),
Err(err) => panic!("Unable to encode Tx ({:?})", err),
};
mtxp.transaction = Decode::decode(&mut Decoder::new(tx_buf.as_slice()), &mut ()).unwrap();
mtxp.transaction = Decode::decode(&mut Decoder::new(&tx_buf.as_slice()), &mut ()).unwrap();
let metx: MultiEraTx = MultiEraTx::from_byron(&mtxp);
let env: Environment = Environment {
prot_params: MultiEraProtParams::Byron(ByronProtParams {
min_fees_const: 155381,
min_fees_factor: 44,
fee_policy: FeePolicy {
summand: 155381,
multiplier: 44,
},
max_tx_size: 4096,
}),
prot_magic: 764824073,
block_slot: 3241381,
network_id: 1,
};
match mk_byron_tx_and_validate(&mtxp.transaction, &mtxp.witness, &utxos, &env) {
Ok(()) => panic!("Inputs set should not be empty."),
match validate(&metx, &utxos, &env) {
Ok(()) => assert!(false, "Inputs set should not be empty"),
Err(err) => match err {
ValidationError::TxInsEmpty => (),
_ => panic!("Unexpected error ({:?}).", err),
Byron(TxInsEmpty) => (),
_ => panic!("Unexpected error ({:?})", err),
},
}
}
@ -192,34 +154,39 @@ mod byron_tests {
// Identical to successful_mainnet_tx, except that all outputs are removed.
fn empty_outs() {
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 = mk_utxo_for_single_input_tx(
&mtxp.transaction,
String::from(include_str!("../../test_data/byron1.address")),
19999000000,
);
let mut mtxp: MintedTxPayload = tx_from_cbor(&cbor_bytes);
// 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) => panic!("Unable to encode Tx ({:?}).", err),
Err(err) => panic!("Unable to encode Tx ({:?})", err),
};
mtxp.transaction = Decode::decode(&mut Decoder::new(tx_buf.as_slice()), &mut ()).unwrap();
mtxp.transaction = Decode::decode(&mut Decoder::new(&tx_buf.as_slice()), &mut ()).unwrap();
let metx: MultiEraTx = MultiEraTx::from_byron(&mtxp);
let utxos: UTxOs = mk_utxo_for_single_input_tx(
&mtxp.transaction,
String::from(include_str!("../../test_data/byron1.address")),
19999000000,
);
let env: Environment = Environment {
prot_params: MultiEraProtParams::Byron(ByronProtParams {
min_fees_const: 155381,
min_fees_factor: 44,
fee_policy: FeePolicy {
summand: 155381,
multiplier: 44,
},
max_tx_size: 4096,
}),
prot_magic: 764824073,
block_slot: 3241381,
network_id: 1,
};
match mk_byron_tx_and_validate(&mtxp.transaction, &mtxp.witness, &utxos, &env) {
Ok(()) => panic!("Outputs set should not be empty."),
match validate(&metx, &utxos, &env) {
Ok(()) => assert!(false, "Outputs set should not be empty"),
Err(err) => match err {
ValidationError::TxOutsEmpty => (),
_ => panic!("Unexpected error ({:?}).", err),
Byron(TxOutsEmpty) => (),
_ => panic!("Unexpected error ({:?})", err),
},
}
}
@ -228,21 +195,26 @@ mod byron_tests {
// The transaction is valid, but the UTxO set is empty.
fn unfound_utxo() {
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 mtxp: MintedTxPayload = tx_from_cbor(&cbor_bytes);
let metx: MultiEraTx = MultiEraTx::from_byron(&mtxp);
let utxos: UTxOs = UTxOs::new();
let env: Environment = Environment {
prot_params: MultiEraProtParams::Byron(ByronProtParams {
min_fees_const: 155381,
min_fees_factor: 44,
fee_policy: FeePolicy {
summand: 155381,
multiplier: 44,
},
max_tx_size: 4096,
}),
prot_magic: 764824073,
block_slot: 3241381,
network_id: 1,
};
match mk_byron_tx_and_validate(&mtxp.transaction, &mtxp.witness, &utxos, &env) {
Ok(()) => panic!("All inputs must be within the UTxO set."),
match validate(&metx, &utxos, &env) {
Ok(()) => assert!(false, "All inputs must be within the UTxO set"),
Err(err) => match err {
ValidationError::InputMissingInUTxO => (),
_ => panic!("Unexpected error ({:?}).", err),
Byron(InputNotInUTxO) => (),
_ => panic!("Unexpected error ({:?})", err),
},
}
}
@ -251,12 +223,7 @@ mod byron_tests {
// 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 = mk_utxo_for_single_input_tx(
&mtxp.transaction,
String::from(include_str!("../../test_data/byron1.address")),
19999000000,
);
let mut mtxp: MintedTxPayload = tx_from_cbor(&cbor_bytes);
// Remove lovelace from output.
let mut tx: Tx = (*mtxp.transaction).clone();
let altered_tx_out: TxOut = TxOut {
@ -269,22 +236,32 @@ mod byron_tests {
let mut tx_buf: Vec<u8> = Vec::new();
match encode(tx, &mut tx_buf) {
Ok(_) => (),
Err(err) => panic!("Unable to encode Tx ({:?}).", err),
Err(err) => panic!("Unable to encode Tx ({:?})", err),
};
mtxp.transaction = Decode::decode(&mut Decoder::new(tx_buf.as_slice()), &mut ()).unwrap();
mtxp.transaction = Decode::decode(&mut Decoder::new(&tx_buf.as_slice()), &mut ()).unwrap();
let metx: MultiEraTx = MultiEraTx::from_byron(&mtxp);
let utxos: UTxOs = mk_utxo_for_single_input_tx(
&mtxp.transaction,
String::from(include_str!("../../test_data/byron1.address")),
19999000000,
);
let env: Environment = Environment {
prot_params: MultiEraProtParams::Byron(ByronProtParams {
min_fees_const: 155381,
min_fees_factor: 44,
fee_policy: FeePolicy {
summand: 155381,
multiplier: 44,
},
max_tx_size: 4096,
}),
prot_magic: 764824073,
block_slot: 3241381,
network_id: 1,
};
match mk_byron_tx_and_validate(&mtxp.transaction, &mtxp.witness, &utxos, &env) {
Ok(()) => panic!("All outputs must contain lovelace."),
match validate(&metx, &utxos, &env) {
Ok(()) => assert!(false, "All outputs must contain lovelace"),
Err(err) => match err {
ValidationError::OutputWithoutLovelace => (),
_ => panic!("Unexpected error ({:?}).", err),
Byron(OutputWithoutLovelace) => (),
_ => panic!("Unexpected error ({:?})", err),
},
}
}
@ -293,7 +270,8 @@ mod byron_tests {
// 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 mtxp: MintedTxPayload = tx_from_cbor(&cbor_bytes);
let metx: MultiEraTx = MultiEraTx::from_byron(&mtxp);
let utxos: UTxOs = mk_utxo_for_single_input_tx(
&mtxp.transaction,
String::from(include_str!("../../test_data/byron1.address")),
@ -301,17 +279,21 @@ mod byron_tests {
);
let env: Environment = Environment {
prot_params: MultiEraProtParams::Byron(ByronProtParams {
min_fees_const: 1000,
min_fees_factor: 1000,
fee_policy: FeePolicy {
summand: 1000,
multiplier: 1000,
},
max_tx_size: 4096,
}),
prot_magic: 764824073,
block_slot: 3241381,
network_id: 1,
};
match mk_byron_tx_and_validate(&mtxp.transaction, &mtxp.witness, &utxos, &env) {
Ok(()) => panic!("Fees should not be below minimum."),
match validate(&metx, &utxos, &env) {
Ok(()) => assert!(false, "Fees should not be below minimum"),
Err(err) => match err {
ValidationError::FeesBelowMin => (),
_ => panic!("Unexpected error ({:?}).", err),
Byron(FeesBelowMin) => (),
_ => panic!("Unexpected error ({:?})", err),
},
}
}
@ -320,7 +302,8 @@ mod byron_tests {
// 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 mtxp: MintedTxPayload = tx_from_cbor(&cbor_bytes);
let metx: MultiEraTx = MultiEraTx::from_byron(&mtxp);
let utxos: UTxOs = mk_utxo_for_single_input_tx(
&mtxp.transaction,
String::from(include_str!("../../test_data/byron1.address")),
@ -328,17 +311,21 @@ mod byron_tests {
);
let env: Environment = Environment {
prot_params: MultiEraProtParams::Byron(ByronProtParams {
min_fees_const: 155381,
min_fees_factor: 44,
fee_policy: FeePolicy {
summand: 155381,
multiplier: 44,
},
max_tx_size: 0,
}),
prot_magic: 764824073,
block_slot: 3241381,
network_id: 1,
};
match mk_byron_tx_and_validate(&mtxp.transaction, &mtxp.witness, &utxos, &env) {
Ok(()) => panic!("Transaction size cannot exceed protocol limit."),
match validate(&metx, &utxos, &env) {
Ok(()) => assert!(false, "Transaction size cannot exceed protocol limit"),
Err(err) => match err {
ValidationError::MaxTxSizeExceeded => (),
_ => panic!("Unexpected error ({:?}).", err),
Byron(MaxTxSizeExceeded) => (),
_ => panic!("Unexpected error ({:?})", err),
},
}
}
@ -347,33 +334,38 @@ mod byron_tests {
// 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 = mk_utxo_for_single_input_tx(
&mtxp.transaction,
String::from(include_str!("../../test_data/byron1.address")),
19999000000,
);
let mut mtxp: MintedTxPayload = tx_from_cbor(&cbor_bytes);
// 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) => panic!("Unable to encode Tx ({:?}).", err),
Err(err) => panic!("Unable to encode Tx ({:?})", err),
};
mtxp.witness = Decode::decode(&mut Decoder::new(tx_buf.as_slice()), &mut ()).unwrap();
mtxp.witness = Decode::decode(&mut Decoder::new(&tx_buf.as_slice()), &mut ()).unwrap();
let metx: MultiEraTx = MultiEraTx::from_byron(&mtxp);
let utxos: UTxOs = mk_utxo_for_single_input_tx(
&mtxp.transaction,
String::from(include_str!("../../test_data/byron1.address")),
19999000000,
);
let env: Environment = Environment {
prot_params: MultiEraProtParams::Byron(ByronProtParams {
min_fees_const: 155381,
min_fees_factor: 44,
fee_policy: FeePolicy {
summand: 155381,
multiplier: 44,
},
max_tx_size: 4096,
}),
prot_magic: 764824073,
block_slot: 3241381,
network_id: 1,
};
match mk_byron_tx_and_validate(&mtxp.transaction, &mtxp.witness, &utxos, &env) {
Ok(()) => panic!("All inputs must have a witness signature."),
match validate(&metx, &utxos, &env) {
Ok(()) => assert!(false, "All inputs must have a witness signature"),
Err(err) => match err {
ValidationError::MissingWitness => (),
_ => panic!("Unexpected error ({:?}).", err),
Byron(MissingWitness) => (),
_ => panic!("Unexpected error ({:?})", err),
},
}
}
@ -383,12 +375,7 @@ mod byron_tests {
// 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 = mk_utxo_for_single_input_tx(
&mtxp.transaction,
String::from(include_str!("../../test_data/byron1.address")),
19999000000,
);
let mut mtxp: MintedTxPayload = tx_from_cbor(&cbor_bytes);
// Modify signature in witness
let new_wit: Twit = match mtxp.witness[0].clone() {
Twit::PkWitness(CborWrap((pk, _))) => {
@ -402,26 +389,40 @@ mod byron_tests {
match encode(new_witnesses, &mut tx_buf) {
Ok(_) => (),
Err(err) => panic!("Unable to encode Tx ({:?}).", err),
Err(err) => panic!("Unable to encode Tx ({:?})", err),
};
mtxp.witness = Decode::decode(&mut Decoder::new(tx_buf.as_slice()), &mut ()).unwrap();
mtxp.witness = Decode::decode(&mut Decoder::new(&tx_buf.as_slice()), &mut ()).unwrap();
let metx: MultiEraTx = MultiEraTx::from_byron(&mtxp);
let utxos: UTxOs = mk_utxo_for_single_input_tx(
&mtxp.transaction,
String::from(include_str!("../../test_data/byron1.address")),
19999000000,
);
let env: Environment = Environment {
prot_params: MultiEraProtParams::Byron(ByronProtParams {
min_fees_const: 155381,
min_fees_factor: 44,
fee_policy: FeePolicy {
summand: 155381,
multiplier: 44,
},
max_tx_size: 4096,
}),
prot_magic: 764824073,
block_slot: 3241381,
network_id: 1,
};
match mk_byron_tx_and_validate(&mtxp.transaction, &mtxp.witness, &utxos, &env) {
Ok(()) => panic!("Witness signature should verify the transaction."),
match validate(&metx, &utxos, &env) {
Ok(()) => assert!(false, "Witness signature should verify the transaction"),
Err(err) => match err {
ValidationError::WrongSignature => (),
_ => panic!("Unexpected error ({:?}).", err),
Byron(WrongSignature) => (),
_ => panic!("Unexpected error ({:?})", err),
},
}
}
}
// 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);
}

View file

@ -0,0 +1,727 @@
use std::borrow::Cow;
use pallas_addresses::{Address, Network, ShelleyAddress};
use pallas_applying::{
types::{
Environment, FeePolicy, MultiEraProtParams, ShelleyMAError::*, ShelleyProtParams,
ValidationError::*,
},
validate, UTxOs,
};
use pallas_codec::{
minicbor::{
decode::{Decode, Decoder},
encode,
},
utils::Bytes,
};
use pallas_crypto::hash::Hash;
use pallas_primitives::alonzo::{
MintedTx, MintedWitnessSet, TransactionBody, TransactionInput, TransactionOutput, VKeyWitness,
Value,
};
use pallas_traverse::{Era, MultiEraInput, MultiEraOutput, MultiEraTx};
#[cfg(test)]
mod shelley_tests {
use super::*;
fn cbor_to_bytes(input: &str) -> Vec<u8> {
hex::decode(input).unwrap()
}
fn minted_tx_from_cbor<'a>(tx_cbor: &'a Vec<u8>) -> MintedTx<'a> {
pallas_codec::minicbor::decode::<MintedTx>(&tx_cbor[..]).unwrap()
}
// Careful: this function assumes tx_body has exactly one input.
fn mk_utxo_for_single_input_tx<'a>(
tx_body: &TransactionBody,
address: String,
amount: Value,
datum_hash: Option<Hash<32>>,
) -> UTxOs<'a> {
let tx_ins: &Vec<TransactionInput> = &tx_body.inputs;
assert_eq!(tx_ins.len(), 1, "Unexpected number of inputs");
let tx_in: TransactionInput = tx_ins.first().unwrap().clone();
let address_bytes: Bytes = match hex::decode(address) {
Ok(bytes_vec) => Bytes::from(bytes_vec),
_ => panic!("Unable to decode input address"),
};
let tx_out: TransactionOutput = TransactionOutput {
address: address_bytes,
amount,
datum_hash,
};
let mut utxos: UTxOs = UTxOs::new();
add_to_utxo(&mut utxos, tx_in, tx_out);
utxos
}
#[test]
// Transaction hash: 50eba65e73c8c5f7b09f4ea28cf15dce169f3d1c322ca3deff03725f51518bb2
fn successful_mainnet_shelley_tx() {
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/shelley1.tx"));
let mtx: MintedTx = minted_tx_from_cbor(&cbor_bytes);
let metx: MultiEraTx = MultiEraTx::from_alonzo_compatible(&mtx, Era::Shelley);
let utxos: UTxOs = mk_utxo_for_single_input_tx(
&mtx.transaction_body,
String::from(include_str!("../../test_data/shelley1.address")),
Value::Coin(2332267427205),
None,
);
let env: Environment = Environment {
prot_params: MultiEraProtParams::Shelley(ShelleyProtParams {
fee_policy: FeePolicy {
summand: 155381,
multiplier: 44,
},
max_tx_size: 4096,
min_lovelace: 1000000,
}),
prot_magic: 764824073,
block_slot: 5281340,
network_id: 1,
};
match validate(&metx, &utxos, &env) {
Ok(()) => (),
Err(err) => assert!(false, "Unexpected error ({:?})", err),
}
}
#[test]
// Transaction hash: 4a3f86762383f1d228542d383ae7ac89cf75cf7ff84dec8148558ea92b0b92d0
fn successful_mainnet_shelley_tx_with_script() {
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/shelley2.tx"));
let mtx: MintedTx = minted_tx_from_cbor(&cbor_bytes);
let metx: MultiEraTx = MultiEraTx::from_alonzo_compatible(&mtx, Era::Shelley);
let utxos: UTxOs = mk_utxo_for_single_input_tx(
&mtx.transaction_body,
String::from(include_str!("../../test_data/shelley2.address")),
Value::Coin(2000000),
None,
);
let env: Environment = Environment {
prot_params: MultiEraProtParams::Shelley(ShelleyProtParams {
fee_policy: FeePolicy {
summand: 155381,
multiplier: 44,
},
max_tx_size: 4096,
min_lovelace: 1000000,
}),
prot_magic: 764824073,
block_slot: 17584925,
network_id: 1,
};
match validate(&metx, &utxos, &env) {
Ok(()) => (),
Err(err) => assert!(false, "Unexpected error ({:?})", err),
}
}
#[test]
// Transaction hash: c220e20cc480df9ce7cd871df491d7390c6a004b9252cf20f45fc3c968535b4a
fn successful_mainnet_shelley_tx_with_metadata() {
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/shelley3.tx"));
let mtx: MintedTx = minted_tx_from_cbor(&cbor_bytes);
let metx: MultiEraTx = MultiEraTx::from_alonzo_compatible(&mtx, Era::Shelley);
let utxos: UTxOs = mk_utxo_for_single_input_tx(
&mtx.transaction_body,
String::from(include_str!("../../test_data/shelley3.address")),
Value::Coin(10000000),
None,
);
let env: Environment = Environment {
prot_params: MultiEraProtParams::Shelley(ShelleyProtParams {
fee_policy: FeePolicy {
summand: 155381,
multiplier: 44,
},
max_tx_size: 4096,
min_lovelace: 1000000,
}),
prot_magic: 764824073,
block_slot: 5860488,
network_id: 1,
};
match validate(&metx, &utxos, &env) {
Ok(()) => (),
Err(err) => assert!(false, "Unexpected error ({:?})", err),
}
}
#[test]
// Transaction hash: b7b1046d1787ac6917f5bb5841e73b3f4bef8f0a6bf692d05ef18e1db9c3f519
fn successful_mainnet_mary_tx_with_minting() {
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/mary1.tx"));
let mtx: MintedTx = minted_tx_from_cbor(&cbor_bytes);
let metx: MultiEraTx = MultiEraTx::from_alonzo_compatible(&mtx, Era::Mary);
let utxos: UTxOs = mk_utxo_for_single_input_tx(
&mtx.transaction_body,
String::from(include_str!("../../test_data/mary1.address")),
Value::Coin(3500000),
None,
);
let env: Environment = Environment {
prot_params: MultiEraProtParams::Shelley(ShelleyProtParams {
fee_policy: FeePolicy {
summand: 155381,
multiplier: 44,
},
max_tx_size: 4096,
min_lovelace: 1000000,
}),
prot_magic: 764824073,
block_slot: 24381863,
network_id: 1,
};
match validate(&metx, &utxos, &env) {
Ok(()) => (),
Err(err) => assert!(false, "Unexpected error ({:?})", err),
}
}
#[test]
// All inputs are removed.
fn empty_ins() {
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/shelley1.tx"));
let mut mtx: MintedTx = minted_tx_from_cbor(&cbor_bytes);
let utxos: UTxOs = mk_utxo_for_single_input_tx(
&mtx.transaction_body,
String::from(include_str!("../../test_data/shelley1.address")),
Value::Coin(2332267427205),
None,
);
// Clear the set of inputs in the transaction.
let mut tx_body: TransactionBody = (*mtx.transaction_body).clone();
tx_body.inputs = Vec::new();
let mut tx_buf: Vec<u8> = Vec::new();
match encode(tx_body, &mut tx_buf) {
Ok(_) => (),
Err(err) => assert!(false, "Unable to encode Tx ({:?})", err),
};
mtx.transaction_body =
Decode::decode(&mut Decoder::new(&tx_buf.as_slice()), &mut ()).unwrap();
let metx: MultiEraTx = MultiEraTx::from_alonzo_compatible(&mtx, Era::Shelley);
let env: Environment = Environment {
prot_params: MultiEraProtParams::Shelley(ShelleyProtParams {
fee_policy: FeePolicy {
summand: 155381,
multiplier: 44,
},
max_tx_size: 4096,
min_lovelace: 1000000,
}),
prot_magic: 764824073,
block_slot: 5281340,
network_id: 1,
};
match validate(&metx, &utxos, &env) {
Ok(()) => assert!(false, "Inputs set should not be empty"),
Err(err) => match err {
Shelley(TxInsEmpty) => (),
_ => assert!(false, "Unexpected error ({:?})", err),
},
}
}
#[test]
// The UTxO set is empty.
fn unfound_utxo() {
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/shelley1.tx"));
let mtx: MintedTx = minted_tx_from_cbor(&cbor_bytes);
let metx: MultiEraTx = MultiEraTx::from_alonzo_compatible(&mtx, Era::Shelley);
let utxos: UTxOs = UTxOs::new();
let env: Environment = Environment {
prot_params: MultiEraProtParams::Shelley(ShelleyProtParams {
fee_policy: FeePolicy {
summand: 155381,
multiplier: 44,
},
max_tx_size: 4096,
min_lovelace: 1000000,
}),
prot_magic: 764824073,
block_slot: 5281340,
network_id: 1,
};
match validate(&metx, &utxos, &env) {
Ok(()) => assert!(false, "All inputs must be within the UTxO set"),
Err(err) => match err {
Shelley(InputNotInUTxO) => (),
_ => assert!(false, "Unexpected error ({:?})", err),
},
}
}
#[test]
// Time-to-live is missing.
fn missing_ttl() {
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/shelley1.tx"));
let mut mtx: MintedTx = minted_tx_from_cbor(&cbor_bytes);
let utxos: UTxOs = mk_utxo_for_single_input_tx(
&mtx.transaction_body,
String::from(include_str!("../../test_data/shelley1.address")),
Value::Coin(2332267427205),
None,
);
let mut tx_body: TransactionBody = (*mtx.transaction_body).clone();
tx_body.ttl = None;
let mut tx_buf: Vec<u8> = Vec::new();
match encode(tx_body, &mut tx_buf) {
Ok(_) => (),
Err(err) => assert!(false, "Unable to encode Tx ({:?})", err),
};
mtx.transaction_body =
Decode::decode(&mut Decoder::new(&tx_buf.as_slice()), &mut ()).unwrap();
let metx: MultiEraTx = MultiEraTx::from_alonzo_compatible(&mtx, Era::Shelley);
let env: Environment = Environment {
prot_params: MultiEraProtParams::Shelley(ShelleyProtParams {
fee_policy: FeePolicy {
summand: 155381,
multiplier: 44,
},
max_tx_size: 4096,
min_lovelace: 1000000,
}),
prot_magic: 764824073,
block_slot: 5281340,
network_id: 1,
};
match validate(&metx, &utxos, &env) {
Ok(()) => assert!(false, "TTL must always be present in Shelley transactions"),
Err(err) => match err {
Shelley(AlonzoCompNotShelley) => (),
_ => assert!(false, "Unexpected error ({:?})", err),
},
}
}
#[test]
// Transaction's time-to-live is before block slot.
fn ttl_exceeded() {
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/shelley1.tx"));
let mtx: MintedTx = minted_tx_from_cbor(&cbor_bytes);
let metx: MultiEraTx = MultiEraTx::from_alonzo_compatible(&mtx, Era::Shelley);
let utxos: UTxOs = mk_utxo_for_single_input_tx(
&mtx.transaction_body,
String::from(include_str!("../../test_data/shelley1.address")),
Value::Coin(2332267427205),
None,
);
let env: Environment = Environment {
prot_params: MultiEraProtParams::Shelley(ShelleyProtParams {
fee_policy: FeePolicy {
summand: 155381,
multiplier: 44,
},
max_tx_size: 4096,
min_lovelace: 1000000,
}),
prot_magic: 764824073,
block_slot: 9999999,
network_id: 1,
};
match validate(&metx, &utxos, &env) {
Ok(()) => assert!(false, "TTL cannot be exceeded"),
Err(err) => match err {
Shelley(TTLExceeded) => (),
_ => assert!(false, "Unexpected error ({:?})", err),
},
}
}
#[test]
// Transaction size exceeds max limit (namely, 0).
fn max_tx_size_exceeded() {
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/shelley1.tx"));
let mtx: MintedTx = minted_tx_from_cbor(&cbor_bytes);
let metx: MultiEraTx = MultiEraTx::from_alonzo_compatible(&mtx, Era::Shelley);
let utxos: UTxOs = mk_utxo_for_single_input_tx(
&mtx.transaction_body,
String::from(include_str!("../../test_data/shelley1.address")),
Value::Coin(2332267427205),
None,
);
let env: Environment = Environment {
prot_params: MultiEraProtParams::Shelley(ShelleyProtParams {
fee_policy: FeePolicy {
summand: 155381,
multiplier: 44,
},
max_tx_size: 0,
min_lovelace: 1000000,
}),
prot_magic: 764824073,
block_slot: 5281340,
network_id: 1,
};
match validate(&metx, &utxos, &env) {
Ok(()) => assert!(false, "Tx size exceeds max limit"),
Err(err) => match err {
Shelley(MaxTxSizeExceeded) => (),
_ => assert!(false, "Unexpected error ({:?})", err),
},
}
}
#[test]
// Min lovelace per UTxO is too high (10000000000000 lovelace against 2332262258756 lovelace in
// transaction output).
fn output_below_min_lovelace() {
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/shelley1.tx"));
let mtx: MintedTx = minted_tx_from_cbor(&cbor_bytes);
let metx: MultiEraTx = MultiEraTx::from_alonzo_compatible(&mtx, Era::Shelley);
let utxos: UTxOs = mk_utxo_for_single_input_tx(
&mtx.transaction_body,
String::from(include_str!("../../test_data/shelley1.address")),
Value::Coin(2332267427205),
None,
);
let env: Environment = Environment {
prot_params: MultiEraProtParams::Shelley(ShelleyProtParams {
fee_policy: FeePolicy {
summand: 155381,
multiplier: 44,
},
max_tx_size: 4096,
min_lovelace: 10000000000000,
}),
prot_magic: 764824073,
block_slot: 5281340,
network_id: 1,
};
match validate(&metx, &utxos, &env) {
Ok(()) => assert!(false, "Output amount must be above min lovelace value"),
Err(err) => match err {
Shelley(MinLovelaceUnreached) => (),
_ => assert!(false, "Unexpected error ({:?})", err),
},
}
}
#[test]
// The "preservation of value" property doesn't hold - the fee is reduced by exactly 1.
fn preservation_of_value() {
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/shelley1.tx"));
let mut mtx: MintedTx = minted_tx_from_cbor(&cbor_bytes);
let mut tx_body: TransactionBody = (*mtx.transaction_body).clone();
tx_body.fee = tx_body.fee - 1;
let mut tx_buf: Vec<u8> = Vec::new();
match encode(tx_body, &mut tx_buf) {
Ok(_) => (),
Err(err) => assert!(false, "Unable to encode Tx ({:?})", err),
};
mtx.transaction_body =
Decode::decode(&mut Decoder::new(&tx_buf.as_slice()), &mut ()).unwrap();
let metx: MultiEraTx = MultiEraTx::from_alonzo_compatible(&mtx, Era::Shelley);
let utxos: UTxOs = mk_utxo_for_single_input_tx(
&mtx.transaction_body,
String::from(include_str!("../../test_data/shelley1.address")),
Value::Coin(2332267427205),
None,
);
let env: Environment = Environment {
prot_params: MultiEraProtParams::Shelley(ShelleyProtParams {
fee_policy: FeePolicy {
summand: 155381,
multiplier: 44,
},
max_tx_size: 4096,
min_lovelace: 1000000,
}),
prot_magic: 764824073,
block_slot: 5281340,
network_id: 1,
};
match validate(&metx, &utxos, &env) {
Ok(()) => assert!(false, "Preservation of value property doesn't hold"),
Err(err) => match err {
Shelley(PreservationOfValue) => (),
_ => assert!(false, "Unexpected error ({:?})", err),
},
}
}
#[test]
// Fee policy imposes higher fees on the transaction.
fn fee_below_minimum() {
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/shelley1.tx"));
let mtx: MintedTx = minted_tx_from_cbor(&cbor_bytes);
let metx: MultiEraTx = MultiEraTx::from_alonzo_compatible(&mtx, Era::Shelley);
let utxos: UTxOs = mk_utxo_for_single_input_tx(
&mtx.transaction_body,
String::from(include_str!("../../test_data/shelley1.address")),
Value::Coin(2332267427205),
None,
);
let env: Environment = Environment {
prot_params: MultiEraProtParams::Shelley(ShelleyProtParams {
fee_policy: FeePolicy {
summand: 155381,
multiplier: 70, // This value was 44 during Shelley on mainnet.
},
max_tx_size: 4096,
min_lovelace: 1000000,
}),
prot_magic: 764824073,
block_slot: 5281340,
network_id: 1,
};
match validate(&metx, &utxos, &env) {
Ok(()) => assert!(false, "Fee should not be below minimum"),
Err(err) => match err {
Shelley(FeesBelowMin) => (),
_ => assert!(false, "Unexpected error ({:?})", err),
},
}
}
#[test]
// One of the output's address network ID is changed from the mainnet value to the testnet one.
fn wrong_network_id() {
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/shelley1.tx"));
let mut mtx: MintedTx = minted_tx_from_cbor(&cbor_bytes);
// Modify the first output address.
let mut tx_body: TransactionBody = (*mtx.transaction_body).clone();
let (first_output, rest): (&TransactionOutput, &[TransactionOutput]) =
(&tx_body.outputs).split_first().unwrap();
let addr: ShelleyAddress =
match Address::from_bytes(&Vec::<u8>::from(first_output.address.clone())) {
Ok(Address::Shelley(sa)) => sa,
Ok(_) => panic!("Decoded output address and found the wrong era"),
Err(e) => panic!("Unable to parse output address ({:?})", e),
};
let altered_address: ShelleyAddress = ShelleyAddress::new(
Network::Testnet,
addr.payment().clone(),
addr.delegation().clone(),
);
let altered_output: TransactionOutput = TransactionOutput {
address: Bytes::from(altered_address.to_vec()),
amount: first_output.amount.clone(),
datum_hash: first_output.datum_hash,
};
let mut new_outputs = Vec::from(rest);
new_outputs.insert(0, altered_output);
tx_body.outputs = new_outputs;
let mut tx_buf: Vec<u8> = Vec::new();
match encode(tx_body, &mut tx_buf) {
Ok(_) => (),
Err(err) => assert!(false, "Unable to encode Tx ({:?})", err),
};
mtx.transaction_body =
Decode::decode(&mut Decoder::new(&tx_buf.as_slice()), &mut ()).unwrap();
let metx: MultiEraTx = MultiEraTx::from_alonzo_compatible(&mtx, Era::Shelley);
let env: Environment = Environment {
prot_params: MultiEraProtParams::Shelley(ShelleyProtParams {
fee_policy: FeePolicy {
summand: 155381,
multiplier: 44,
},
max_tx_size: 4096,
min_lovelace: 1000000,
}),
prot_magic: 764824073,
block_slot: 5281340,
network_id: 1,
};
let utxos: UTxOs = mk_utxo_for_single_input_tx(
&mtx.transaction_body,
String::from(include_str!("../../test_data/shelley1.address")),
Value::Coin(2332267427205),
None,
);
match validate(&metx, &utxos, &env) {
Ok(()) => assert!(false, "Output with wrong network ID should be rejected"),
Err(err) => match err {
Shelley(WrongNetworkID) => (),
_ => assert!(false, "Unexpected error ({:?})", err),
},
}
}
#[test]
// Like successful_mainnet_shelley_tx_with_metadata (hash:
// c220e20cc480df9ce7cd871df491d7390c6a004b9252cf20f45fc3c968535b4a)
fn auxiliary_data_removed() {
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/shelley3.tx"));
let mtx: MintedTx = minted_tx_from_cbor(&cbor_bytes);
let metx: MultiEraTx = MultiEraTx::from_alonzo_compatible(&mtx, Era::Shelley);
let utxos: UTxOs = mk_utxo_for_single_input_tx(
&mtx.transaction_body,
String::from(include_str!("../../test_data/shelley3.address")),
Value::Coin(10000000),
None,
);
let env: Environment = Environment {
prot_params: MultiEraProtParams::Shelley(ShelleyProtParams {
fee_policy: FeePolicy {
summand: 155381,
multiplier: 44,
},
max_tx_size: 4096,
min_lovelace: 1000000,
}),
prot_magic: 764824073,
block_slot: 5860488,
network_id: 1,
};
match validate(&metx, &utxos, &env) {
Ok(()) => (),
Err(err) => assert!(false, "Unexpected error ({:?})", err),
}
}
#[test]
// Like successful_mainnet_shelley_tx (hash:
// 50eba65e73c8c5f7b09f4ea28cf15dce169f3d1c322ca3deff03725f51518bb2), but the verification-key
// witness is removed.
fn missing_vk_witness() {
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/shelley1.tx"));
let mut mtx: MintedTx = minted_tx_from_cbor(&cbor_bytes);
// Modify the first output address.
let mut tx_wits: MintedWitnessSet = (*mtx.transaction_witness_set).clone();
tx_wits.vkeywitness = Some(Vec::new());
let mut tx_buf: Vec<u8> = Vec::new();
match encode(tx_wits, &mut tx_buf) {
Ok(_) => (),
Err(err) => assert!(false, "Unable to encode Tx ({:?})", err),
};
mtx.transaction_witness_set =
Decode::decode(&mut Decoder::new(&tx_buf.as_slice()), &mut ()).unwrap();
let metx: MultiEraTx = MultiEraTx::from_alonzo_compatible(&mtx, Era::Shelley);
let env: Environment = Environment {
prot_params: MultiEraProtParams::Shelley(ShelleyProtParams {
fee_policy: FeePolicy {
summand: 155381,
multiplier: 44,
},
max_tx_size: 4096,
min_lovelace: 1000000,
}),
prot_magic: 764824073,
block_slot: 5281340,
network_id: 1,
};
let utxos: UTxOs = mk_utxo_for_single_input_tx(
&mtx.transaction_body,
String::from(include_str!("../../test_data/shelley1.address")),
Value::Coin(2332267427205),
None,
);
match validate(&metx, &utxos, &env) {
Ok(()) => assert!(false, "Missing verification key witness"),
Err(err) => match err {
Shelley(MissingVKWitness) => (),
_ => assert!(false, "Unexpected error ({:?})", err),
},
}
}
#[test]
// Like successful_mainnet_shelley_tx (hash:
// 50eba65e73c8c5f7b09f4ea28cf15dce169f3d1c322ca3deff03725f51518bb2), but the signature inside
// the verification-key witness is changed.
fn vk_witness_changed() {
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/shelley1.tx"));
let mut mtx: MintedTx = minted_tx_from_cbor(&cbor_bytes);
// Modify the first output address.
let mut tx_wits: MintedWitnessSet = (*mtx.transaction_witness_set).clone();
let mut wit: VKeyWitness = tx_wits.vkeywitness.clone().unwrap().pop().unwrap();
let mut sig_as_vec: Vec<u8> = wit.signature.to_vec();
sig_as_vec.pop();
sig_as_vec.push(0u8);
wit.signature = Bytes::from(sig_as_vec);
tx_wits.vkeywitness = Some(Vec::from([wit]));
let mut tx_buf: Vec<u8> = Vec::new();
match encode(tx_wits, &mut tx_buf) {
Ok(_) => (),
Err(err) => assert!(false, "Unable to encode Tx ({:?})", err),
};
mtx.transaction_witness_set =
Decode::decode(&mut Decoder::new(&tx_buf.as_slice()), &mut ()).unwrap();
let metx: MultiEraTx = MultiEraTx::from_alonzo_compatible(&mtx, Era::Shelley);
let env: Environment = Environment {
prot_params: MultiEraProtParams::Shelley(ShelleyProtParams {
fee_policy: FeePolicy {
summand: 155381,
multiplier: 44,
},
max_tx_size: 4096,
min_lovelace: 1000000,
}),
prot_magic: 764824073,
block_slot: 5281340,
network_id: 1,
};
let utxos: UTxOs = mk_utxo_for_single_input_tx(
&mtx.transaction_body,
String::from(include_str!("../../test_data/shelley1.address")),
Value::Coin(2332267427205),
None,
);
match validate(&metx, &utxos, &env) {
Ok(()) => assert!(false, "Missing verification key witness"),
Err(err) => match err {
Shelley(WrongSignature) => (),
_ => assert!(false, "Unexpected error ({:?})", err),
},
}
}
#[test]
// Like successful_mainnet_shelley_tx_with_script(hash:
// 4a3f86762383f1d228542d383ae7ac89cf75cf7ff84dec8148558ea92b0b92d0), but the native-script
// witness is removed.
fn missing_native_script_witness() {
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/shelley2.tx"));
let mut mtx: MintedTx = minted_tx_from_cbor(&cbor_bytes);
// Modify the first output address.
let mut tx_wits: MintedWitnessSet = (*mtx.transaction_witness_set).clone();
tx_wits.native_script = Some(Vec::new());
let mut tx_buf: Vec<u8> = Vec::new();
match encode(tx_wits, &mut tx_buf) {
Ok(_) => (),
Err(err) => assert!(false, "Unable to encode Tx ({:?})", err),
};
mtx.transaction_witness_set =
Decode::decode(&mut Decoder::new(&tx_buf.as_slice()), &mut ()).unwrap();
let metx: MultiEraTx = MultiEraTx::from_alonzo_compatible(&mtx, Era::Shelley);
let env: Environment = Environment {
prot_params: MultiEraProtParams::Shelley(ShelleyProtParams {
fee_policy: FeePolicy {
summand: 155381,
multiplier: 44,
},
max_tx_size: 4096,
min_lovelace: 1000000,
}),
prot_magic: 764824073,
block_slot: 5281340,
network_id: 1,
};
let utxos: UTxOs = mk_utxo_for_single_input_tx(
&mtx.transaction_body,
String::from(include_str!("../../test_data/shelley2.address")),
Value::Coin(2000000),
None,
);
match validate(&metx, &utxos, &env) {
Ok(()) => assert!(false, "Missing native script witness"),
Err(err) => match err {
Shelley(MissingScriptWitness) => (),
_ => assert!(false, "Unexpected error ({:?})", err),
},
}
}
}
// Helper functions.
fn add_to_utxo<'a>(utxos: &mut UTxOs<'a>, tx_in: TransactionInput, tx_out: TransactionOutput) {
let multi_era_in: MultiEraInput = MultiEraInput::AlonzoCompatible(Box::new(Cow::Owned(tx_in)));
let multi_era_out: MultiEraOutput =
MultiEraOutput::AlonzoCompatible(Box::new(Cow::Owned(tx_out)));
utxos.insert(multi_era_in, multi_era_out);
}