From 36df92f8ad0ca049d934e06ca44b4553ec10ea36 Mon Sep 17 00:00:00 2001 From: Harper Date: Fri, 12 May 2023 13:01:44 +0100 Subject: [PATCH] fix: ignore duplicate consumed inputs (#257) --- pallas-traverse/src/input.rs | 30 ++++++++++++++++++++++++++++++ pallas-traverse/src/lib.rs | 2 +- pallas-traverse/src/tx.rs | 25 ++++++++++++++++++++++--- test_data/duplicateinput.tx | 1 + 4 files changed, 54 insertions(+), 4 deletions(-) create mode 100644 test_data/duplicateinput.tx diff --git a/pallas-traverse/src/input.rs b/pallas-traverse/src/input.rs index d7272e1..f3a9afb 100644 --- a/pallas-traverse/src/input.rs +++ b/pallas-traverse/src/input.rs @@ -138,6 +138,36 @@ mod tests { } } + #[test] + fn test_duplicate_consumed_inputs() { + let tx_bytecode_hex = include_str!("../../test_data/duplicateinput.tx"); + let bytecode = hex::decode(tx_bytecode_hex).unwrap(); + let tx = MultiEraTx::decode(Era::Alonzo, &bytecode).unwrap(); + + let expected_inputs = vec![ + "4d9cb6bf1c2e349f1bcd454a632d2b721d5badcf687220430c316588f39506ab#1", + "4d9cb6bf1c2e349f1bcd454a632d2b721d5badcf687220430c316588f39506ab#1", + ]; + + let inputs: Vec = tx + .inputs() + .into_iter() + .map(|x| x.output_ref().to_string()) + .collect(); + + let expected_consumed = + vec!["4d9cb6bf1c2e349f1bcd454a632d2b721d5badcf687220430c316588f39506ab#1"]; + + let consumed: Vec = tx + .consumes() + .into_iter() + .map(|x| x.output_ref().to_string()) + .collect(); + + assert_eq!(inputs, expected_inputs); + assert_eq!(consumed, expected_consumed); + } + #[test] fn test_utxo_ref_parsing() { let valid_vectors = [ diff --git a/pallas-traverse/src/lib.rs b/pallas-traverse/src/lib.rs index 6d9191a..328b0bf 100644 --- a/pallas-traverse/src/lib.rs +++ b/pallas-traverse/src/lib.rs @@ -135,7 +135,7 @@ pub enum MultiEraSigners<'b> { AlonzoCompatible(&'b alonzo::RequiredSigners), } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Hash, PartialEq, Eq)] pub struct OutputRef(Hash<32>, u64); #[derive(Debug, Error)] diff --git a/pallas-traverse/src/tx.rs b/pallas-traverse/src/tx.rs index 3965c25..91374e6 100644 --- a/pallas-traverse/src/tx.rs +++ b/pallas-traverse/src/tx.rs @@ -1,4 +1,4 @@ -use std::{borrow::Cow, ops::Deref}; +use std::{borrow::Cow, collections::HashSet, ops::Deref}; use pallas_codec::{minicbor, utils::KeepRaw}; use pallas_crypto::hash::Hash; @@ -114,6 +114,10 @@ impl<'b> MultiEraTx<'b> { } } + /// Return the transaction inputs + /// + /// NOTE: It is possible for this to return duplicates. See + /// https://github.com/input-output-hk/cardano-ledger/commit/a342b74f5db3d3a75eae3e2abe358a169701b1e7 pub fn inputs(&self) -> Vec { match self { MultiEraTx::AlonzoCompatible(x, _) => x @@ -137,6 +141,10 @@ impl<'b> MultiEraTx<'b> { } } + /// Return the transaction reference inputs + /// + /// NOTE: It is possible for this to return duplicates. See + /// https://github.com/input-output-hk/cardano-ledger/commit/a342b74f5db3d3a75eae3e2abe358a169701b1e7 pub fn reference_inputs(&self) -> Vec { match self { MultiEraTx::Babbage(x) => x @@ -192,6 +200,10 @@ impl<'b> MultiEraTx<'b> { } } + /// Return the transaction collateral inputs + /// + /// NOTE: It is possible for this to return duplicates. See + /// https://github.com/input-output-hk/cardano-ledger/commit/a342b74f5db3d3a75eae3e2abe358a169701b1e7 pub fn collateral(&self) -> Vec { match self { MultiEraTx::AlonzoCompatible(x, _) => x @@ -230,10 +242,17 @@ impl<'b> MultiEraTx<'b> { /// will return the list of inputs. If the tx is invalid, it will return the /// collateral. pub fn consumes(&self) -> Vec { - match self.is_valid() { + let consumed = match self.is_valid() { true => self.inputs(), false => self.collateral(), - } + }; + + let mut unique_consumed = HashSet::new(); + + consumed + .into_iter() + .filter(|i| unique_consumed.insert(i.output_ref())) + .collect() } /// Returns a list of tuples of the outputs produced by the Tx with their diff --git a/test_data/duplicateinput.tx b/test_data/duplicateinput.tx new file mode 100644 index 0000000..6b8cecc --- /dev/null +++ b/test_data/duplicateinput.tx @@ -0,0 +1 @@ +84a400828258204d9cb6bf1c2e349f1bcd454a632d2b721d5badcf687220430c316588f39506ab018258204d9cb6bf1c2e349f1bcd454a632d2b721d5badcf687220430c316588f39506ab010182825839018b038a44ef966d2140c923636cea9a2a5a5db4a003c75773d95b3a2562afd01de43f13aadf7a78b27a1b836603906002ebef9963f90fc8ff1a00493e0082583901e25497038dd6d9413e3e43bbe14730130856a2a5b5e08643fc7be1cb62afd01de43f13aadf7a78b27a1b836603906002ebef9963f90fc8ff1a00eaa588021a000f4240031a033dd1fca10081825820565ede44b1980b00a8d02e09887491cb873d691f675a8715e4a0a101154872b958404164058a3e2f5ce9a2582dc8eed0259d29a7cc3bb0ab3d38d1ca219c9a5a5e32a32f798dc59ad680c53e59b04cc60a688b90c30535c93dfae38561da639d710af5f6 \ No newline at end of file