Skip to content

Commit

Permalink
feat(applying): check outputs network ID
Browse files Browse the repository at this point in the history
  • Loading branch information
MaicoLeberle committed Nov 22, 2023
1 parent 9b203ef commit f7de948
Show file tree
Hide file tree
Showing 5 changed files with 107 additions and 2 deletions.
10 changes: 9 additions & 1 deletion pallas-applying/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,16 @@ pub fn validate(metx: &MultiEraTx, utxos: &UTxOs, env: &Environment) -> Validati
prot_params: MultiEraProtParams::Shelley(spp),
prot_magic,
block_slot,
network_id,
},
) => validate_shelley_tx(shelley_minted_tx, utxos, spp, prot_magic, block_slot),
) => validate_shelley_tx(
shelley_minted_tx,
utxos,
spp,
prot_magic,
block_slot,
network_id,
),
// TODO: implement the rest of the eras.
_ => Ok(()),
}
Expand Down
20 changes: 19 additions & 1 deletion pallas-applying/src/shelley.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! Utilities required for Shelley-era transaction validation.
use crate::types::{ShelleyProtParams, UTxOs, ValidationError, ValidationResult};
use pallas_addresses::{Address, ShelleyAddress};
use pallas_primitives::alonzo::{MintedTx, TransactionBody};
use pallas_traverse::MultiEraInput;

Expand All @@ -11,11 +12,13 @@ pub fn validate_shelley_tx(
_prot_pps: &ShelleyProtParams,
_prot_magic: &u32,
block_slot: &u64,
network_id: &u8,
) -> ValidationResult {
let tx_body: &TransactionBody = &mtx.transaction_body;
check_ins_not_empty(tx_body)?;
check_ins_in_utxos(tx_body, utxos)?;
check_ttl(tx_body, block_slot)
check_ttl(tx_body, block_slot)?;
check_network_id(tx_body, network_id)
}

fn check_ins_not_empty(tx_body: &TransactionBody) -> ValidationResult {
Expand Down Expand Up @@ -46,3 +49,18 @@ fn check_ttl(tx_body: &TransactionBody, block_slot: &u64) -> ValidationResult {
None => Err(ValidationError::AlonzoCompatibleNotShelley),
}
}

fn check_network_id(tx_body: &TransactionBody, network_id: &u8) -> ValidationResult {
for output in tx_body.outputs.iter() {
let addr: ShelleyAddress =
match Address::from_bytes(&Vec::<u8>::from(output.address.clone())) {
Ok(Address::Shelley(sa)) => sa,
Ok(_) => return Err(ValidationError::WrongEraOutput),
Err(_) => return Err(ValidationError::UnableToDecodeAddress),
};
if addr.network().value() != *network_id {
return Err(ValidationError::WrongNetworkID);
}
}
Ok(())
}
4 changes: 4 additions & 0 deletions pallas-applying/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ pub struct Environment {
pub prot_params: MultiEraProtParams,
pub prot_magic: u32,
pub block_slot: u64,
pub network_id: u8,
}

#[non_exhaustive]
Expand All @@ -53,6 +54,9 @@ pub enum ValidationError {
WrongSignature, // >= Byron
TTLExceeded, // >= Shelley
AlonzoCompatibleNotShelley, // == Shelley
WrongEraOutput, // >= Shelley
UnableToDecodeAddress, // >= Shelley
WrongNetworkID, // >= Shelley
}

pub type ValidationResult = Result<(), ValidationError>;
10 changes: 10 additions & 0 deletions pallas-applying/tests/byron.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ mod byron_tests {
}),
prot_magic: 764824073,
block_slot: 6341,
network_id: 1,
};
match validate(&metx, &utxos, &env) {
Ok(()) => (),
Expand All @@ -93,6 +94,7 @@ mod byron_tests {
}),
prot_magic: 764824073,
block_slot: 3241381,
network_id: 1,
};
match validate(&metx, &utxos, &env) {
Ok(()) => (),
Expand Down Expand Up @@ -128,6 +130,7 @@ mod byron_tests {
}),
prot_magic: 764824073,
block_slot: 3241381,
network_id: 1,
};
match validate(&metx, &utxos, &env) {
Ok(()) => assert!(false, "Inputs set should not be empty."),
Expand Down Expand Up @@ -166,6 +169,7 @@ mod byron_tests {
}),
prot_magic: 764824073,
block_slot: 3241381,
network_id: 1,
};
match validate(&metx, &utxos, &env) {
Ok(()) => assert!(false, "Outputs set should not be empty."),
Expand All @@ -191,6 +195,7 @@ mod byron_tests {
}),
prot_magic: 764824073,
block_slot: 3241381,
network_id: 1,
};
match validate(&metx, &utxos, &env) {
Ok(()) => assert!(false, "All inputs must be within the UTxO set."),
Expand Down Expand Up @@ -235,6 +240,7 @@ mod byron_tests {
}),
prot_magic: 764824073,
block_slot: 3241381,
network_id: 1,
};
match validate(&metx, &utxos, &env) {
Ok(()) => assert!(false, "All outputs must contain lovelace."),
Expand Down Expand Up @@ -264,6 +270,7 @@ mod byron_tests {
}),
prot_magic: 764824073,
block_slot: 3241381,
network_id: 1,
};
match validate(&metx, &utxos, &env) {
Ok(()) => assert!(false, "Fees should not be below minimum."),
Expand Down Expand Up @@ -293,6 +300,7 @@ mod byron_tests {
}),
prot_magic: 764824073,
block_slot: 3241381,
network_id: 1,
};
match validate(&metx, &utxos, &env) {
Ok(()) => assert!(false, "Transaction size cannot exceed protocol limit."),
Expand Down Expand Up @@ -330,6 +338,7 @@ mod byron_tests {
}),
prot_magic: 764824073,
block_slot: 3241381,
network_id: 1,
};
match validate(&metx, &utxos, &env) {
Ok(()) => assert!(false, "All inputs must have a witness signature."),
Expand Down Expand Up @@ -376,6 +385,7 @@ mod byron_tests {
}),
prot_magic: 764824073,
block_slot: 3241381,
network_id: 1,
};
match validate(&metx, &utxos, &env) {
Ok(()) => assert!(false, "Witness signature should verify the transaction."),
Expand Down
65 changes: 65 additions & 0 deletions pallas-applying/tests/shelley.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::borrow::Cow;

use pallas_addresses::{Address, Network, ShelleyAddress};
use pallas_applying::{
types::{Environment, MultiEraProtParams, ShelleyProtParams, ValidationError},
validate, UTxOs,
Expand Down Expand Up @@ -63,6 +64,7 @@ mod shelley_tests {
prot_params: MultiEraProtParams::Shelley(ShelleyProtParams),
prot_magic: 764824073,
block_slot: 5281340,
network_id: 1,
};
let utxos: UTxOs = mk_utxo_for_single_input_tx(
&mtx.transaction_body,
Expand Down Expand Up @@ -102,6 +104,7 @@ mod shelley_tests {
prot_params: MultiEraProtParams::Shelley(ShelleyProtParams),
prot_magic: 764824073,
block_slot: 5281340,
network_id: 1,
};
match validate(&metx, &utxos, &env) {
Ok(()) => assert!(false, "Inputs set should not be empty."),
Expand All @@ -122,6 +125,7 @@ mod shelley_tests {
prot_params: MultiEraProtParams::Shelley(ShelleyProtParams),
prot_magic: 764824073,
block_slot: 5281340,
network_id: 1,
};
let utxos: UTxOs = UTxOs::new();
match validate(&metx, &utxos, &env) {
Expand Down Expand Up @@ -158,6 +162,7 @@ mod shelley_tests {
prot_params: MultiEraProtParams::Shelley(ShelleyProtParams),
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."),
Expand All @@ -178,6 +183,7 @@ mod shelley_tests {
prot_params: MultiEraProtParams::Shelley(ShelleyProtParams),
prot_magic: 764824073,
block_slot: 9999999,
network_id: 1,
};
let utxos: UTxOs = mk_utxo_for_single_input_tx(
&mtx.transaction_body,
Expand All @@ -193,6 +199,65 @@ mod shelley_tests {
},
}
}

#[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),
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 {
ValidationError::WrongNetworkID => (),
_ => assert!(false, "Unexpected error ({:?}).", err),
},
}
}
}

// Helper functions.
Expand Down

0 comments on commit f7de948

Please sign in to comment.