Skip to content

Commit

Permalink
fix(interop): use correct input order to match redeemers (#487)
Browse files Browse the repository at this point in the history
  • Loading branch information
scarmuega authored Jul 14, 2024
1 parent 15538cd commit 1406d7a
Show file tree
Hide file tree
Showing 8 changed files with 1,917 additions and 27 deletions.
4 changes: 2 additions & 2 deletions examples/block-download/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ async fn main() {
.unwrap();

let point = Point::Specific(
49159253,
hex::decode("d034a2d0e4c3076f57368ed59319010c265718f0923057f8ff914a3b6bfd1314").unwrap(),
101516417,
hex::decode("3d681e503fd9318d0f68c74a699895ce61f0a07010b516b80ce968a6b000e231").unwrap(),
);

let block = peer.blockfetch().fetch_single(point).await.unwrap();
Expand Down
5 changes: 5 additions & 0 deletions pallas-traverse/src/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@ impl<'b> MultiEraInput<'b> {
}
}

/// Returns the key used for lexicographical ordering of the input
pub fn lexicographical_key(&self) -> String {
format!("{}#{}", self.hash(), self.index())
}

pub fn hash(&self) -> &Hash<32> {
match self {
MultiEraInput::Byron(x) => match x.deref().deref() {
Expand Down
13 changes: 13 additions & 0 deletions pallas-traverse/src/tx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,19 @@ impl<'b> MultiEraTx<'b> {
}
}

/// Return inputs as expected for processing
///
/// To process inputs we need a set (no duplicated) and lexicographical
/// order (hash#idx). This function will take the raw inputs and apply the
/// aforementioned cleanup changes.
pub fn inputs_sorted_set(&self) -> Vec<MultiEraInput> {
let mut raw = self.inputs();
raw.sort_by_key(|x| x.lexicographical_key());
raw.dedup_by_key(|x| x.lexicographical_key());

raw
}

/// Return the transaction reference inputs
///
/// NOTE: It is possible for this to return duplicates. See
Expand Down
6 changes: 6 additions & 0 deletions pallas-traverse/src/witnesses.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,12 @@ impl<'b> MultiEraTx<'b> {
}
}

pub fn find_spend_redeemer(&self, input_order: u32) -> Option<MultiEraRedeemer> {
self.redeemers().into_iter().find(|r| {
r.tag() == pallas_primitives::conway::RedeemerTag::Spend && r.index() == input_order
})
}

pub fn plutus_v2_scripts(&self) -> &[PlutusV2Script] {
match self {
Self::Byron(_) => &[],
Expand Down
4 changes: 4 additions & 0 deletions pallas-utxorpc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,8 @@ pallas-codec = { version = "=0.28.0", path = "../pallas-codec" }
pallas-crypto = { version = "=0.28.0", path = "../pallas-crypto" }

utxorpc-spec = { version = "0.6.0" }

[dev-dependencies]
hex = "0.4.3"
serde_json = "1.0.120"
# utxorpc-spec = { path = "../../../utxorpc/spec/gen/rust" }
107 changes: 82 additions & 25 deletions pallas-utxorpc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,7 @@ use std::{collections::HashMap, ops::Deref};

use pallas_codec::utils::KeyValuePairs;
use pallas_crypto::hash::Hash;
use pallas_primitives::{
alonzo, babbage,
conway::{self, RedeemerTag},
};
use pallas_primitives::{alonzo, babbage, conway};
use pallas_traverse as trv;

use trv::OriginalHash;
Expand Down Expand Up @@ -57,33 +54,61 @@ impl<C: LedgerContext> Mapper<C> {
}
}

pub fn map_tx_input(
fn decode_resolved_utxo(
&self,
i: &trv::MultiEraInput,
tx: &trv::MultiEraTx,
resolved: &Option<UtxoMap>,
) -> u5c::TxInput {
let redeemers = tx.redeemers();

let redeemer = redeemers
.iter()
.find(|r| r.tag() == RedeemerTag::Spend && (r.index() as u64) == i.index());
input: &trv::MultiEraInput,
) -> Option<u5c::TxOutput> {
let as_txref = (*input.hash(), input.index() as u32);

let as_txref = (*i.hash(), i.index() as u32);

let as_output = resolved
resolved
.as_ref()
.and_then(|x| x.get(&as_txref))
.and_then(|(era, cbor)| {
let o = trv::MultiEraOutput::decode(*era, cbor.as_slice()).ok()?;
Some(self.map_tx_output(&o))
});
})
}

pub fn map_tx_input(
&self,
input: &trv::MultiEraInput,
tx: &trv::MultiEraTx,
// lexicographical order of the input we're mapping
order: u32,
resolved: &Option<UtxoMap>,
) -> u5c::TxInput {
u5c::TxInput {
tx_hash: input.hash().to_vec().into(),
output_index: input.index() as u32,
as_output: self.decode_resolved_utxo(resolved, input),
redeemer: tx.find_spend_redeemer(order).map(|x| self.map_redeemer(&x)),
}
}

pub fn map_tx_reference_input(
&self,
input: &trv::MultiEraInput,
resolved: &Option<UtxoMap>,
) -> u5c::TxInput {
u5c::TxInput {
tx_hash: i.hash().to_vec().into(),
output_index: i.index() as u32,
redeemer: redeemer.map(|x| self.map_redeemer(x)),
as_output,
tx_hash: input.hash().to_vec().into(),
output_index: input.index() as u32,
as_output: self.decode_resolved_utxo(resolved, input),
redeemer: None,
}
}

pub fn map_tx_collateral(
&self,
input: &trv::MultiEraInput,
resolved: &Option<UtxoMap>,
) -> u5c::TxInput {
u5c::TxInput {
tx_hash: input.hash().to_vec().into(),
output_index: input.index() as u32,
as_output: self.decode_resolved_utxo(resolved, input),
redeemer: None,
}
}

Expand Down Expand Up @@ -500,9 +525,10 @@ impl<C: LedgerContext> Mapper<C> {
u5c::Tx {
hash: tx.hash().to_vec().into(),
inputs: tx
.inputs()
.inputs_sorted_set()
.iter()
.map(|i| self.map_tx_input(i, tx, &resolved))
.enumerate()
.map(|(order, i)| self.map_tx_input(i, tx, order as u32, &resolved))
.collect(),
outputs: tx.outputs().iter().map(|x| self.map_tx_output(x)).collect(),
certificates: tx.certs().iter().map(|x| self.map_cert(x)).collect(),
Expand All @@ -520,7 +546,7 @@ impl<C: LedgerContext> Mapper<C> {
reference_inputs: tx
.reference_inputs()
.iter()
.map(|x| self.map_tx_input(x, tx, &resolved))
.map(|x| self.map_tx_reference_input(x, &resolved))
.collect(),
witnesses: u5c::WitnessSet {
vkeywitness: tx
Expand All @@ -540,7 +566,7 @@ impl<C: LedgerContext> Mapper<C> {
collateral: tx
.collateral()
.iter()
.map(|x| self.map_tx_input(x, tx, &resolved))
.map(|x| self.map_tx_collateral(x, &resolved))
.collect(),
collateral_return: tx.collateral_return().map(|x| self.map_tx_output(&x)),
total_collateral: tx.total_collateral().unwrap_or_default(),
Expand Down Expand Up @@ -586,3 +612,34 @@ impl<C: LedgerContext> Mapper<C> {
self.map_block(&block)
}
}

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

#[derive(Clone)]
struct NoLedger;

impl LedgerContext for NoLedger {
fn get_utxos(&self, _refs: &[TxoRef]) -> Option<UtxoMap> {
None
}
}

#[test]
fn snapshot() {
let test_blocks = [include_str!("../../test_data/u5c1.block")];
let test_snapshots = [include_str!("../../test_data/u5c1.json")];

let mapper = Mapper::new(NoLedger);

for (block_str, json_str) in test_blocks.iter().zip(test_snapshots) {
let cbor = hex::decode(block_str).unwrap();
let block = pallas_traverse::MultiEraBlock::decode(&cbor).unwrap();
let current = serde_json::json!(mapper.map_block(&block));
let expected: serde_json::Value = serde_json::from_str(&json_str).unwrap();

assert_eq!(current, expected)
}
}
}
1 change: 1 addition & 0 deletions test_data/u5c1.block

Large diffs are not rendered by default.

Loading

0 comments on commit 1406d7a

Please sign in to comment.