fix(interop): use correct input order to match redeemers (#487)

This commit is contained in:
Santiago Carmuega 2024-07-14 13:48:05 -03:00 committed by GitHub
parent 15538cd0bf
commit 1406d7a599
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 1917 additions and 27 deletions

View file

@ -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();

View file

@ -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() {

View file

@ -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

View file

@ -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(_) => &[],

View file

@ -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" }

View file

@ -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;
@ -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();
input: &trv::MultiEraInput,
) -> Option<u5c::TxOutput> {
let as_txref = (*input.hash(), input.index() as u32);
let redeemer = redeemers
.iter()
.find(|r| r.tag() == RedeemerTag::Spend && (r.index() as u64) == i.index());
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: 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: 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: 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,
}
}
@ -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(),
@ -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
@ -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(),
@ -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
test_data/u5c1.block Normal file

File diff suppressed because one or more lines are too long

1804
test_data/u5c1.json Normal file

File diff suppressed because one or more lines are too long