fix(interop): use correct input order to match redeemers (#487)
This commit is contained in:
parent
15538cd0bf
commit
1406d7a599
8 changed files with 1917 additions and 27 deletions
|
|
@ -17,8 +17,8 @@ async fn main() {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let point = Point::Specific(
|
let point = Point::Specific(
|
||||||
49159253,
|
101516417,
|
||||||
hex::decode("d034a2d0e4c3076f57368ed59319010c265718f0923057f8ff914a3b6bfd1314").unwrap(),
|
hex::decode("3d681e503fd9318d0f68c74a699895ce61f0a07010b516b80ce968a6b000e231").unwrap(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let block = peer.blockfetch().fetch_single(point).await.unwrap();
|
let block = peer.blockfetch().fetch_single(point).await.unwrap();
|
||||||
|
|
|
||||||
|
|
@ -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> {
|
pub fn hash(&self) -> &Hash<32> {
|
||||||
match self {
|
match self {
|
||||||
MultiEraInput::Byron(x) => match x.deref().deref() {
|
MultiEraInput::Byron(x) => match x.deref().deref() {
|
||||||
|
|
|
||||||
|
|
@ -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
|
/// Return the transaction reference inputs
|
||||||
///
|
///
|
||||||
/// NOTE: It is possible for this to return duplicates. See
|
/// NOTE: It is possible for this to return duplicates. See
|
||||||
|
|
|
||||||
|
|
@ -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] {
|
pub fn plutus_v2_scripts(&self) -> &[PlutusV2Script] {
|
||||||
match self {
|
match self {
|
||||||
Self::Byron(_) => &[],
|
Self::Byron(_) => &[],
|
||||||
|
|
|
||||||
|
|
@ -17,4 +17,8 @@ pallas-codec = { version = "=0.28.0", path = "../pallas-codec" }
|
||||||
pallas-crypto = { version = "=0.28.0", path = "../pallas-crypto" }
|
pallas-crypto = { version = "=0.28.0", path = "../pallas-crypto" }
|
||||||
|
|
||||||
utxorpc-spec = { version = "0.6.0" }
|
utxorpc-spec = { version = "0.6.0" }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
hex = "0.4.3"
|
||||||
|
serde_json = "1.0.120"
|
||||||
# utxorpc-spec = { path = "../../../utxorpc/spec/gen/rust" }
|
# utxorpc-spec = { path = "../../../utxorpc/spec/gen/rust" }
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,7 @@ use std::{collections::HashMap, ops::Deref};
|
||||||
|
|
||||||
use pallas_codec::utils::KeyValuePairs;
|
use pallas_codec::utils::KeyValuePairs;
|
||||||
use pallas_crypto::hash::Hash;
|
use pallas_crypto::hash::Hash;
|
||||||
use pallas_primitives::{
|
use pallas_primitives::{alonzo, babbage, conway};
|
||||||
alonzo, babbage,
|
|
||||||
conway::{self, RedeemerTag},
|
|
||||||
};
|
|
||||||
use pallas_traverse as trv;
|
use pallas_traverse as trv;
|
||||||
|
|
||||||
use trv::OriginalHash;
|
use trv::OriginalHash;
|
||||||
|
|
@ -57,33 +54,61 @@ impl<C: LedgerContext> Mapper<C> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn map_tx_input(
|
fn decode_resolved_utxo(
|
||||||
&self,
|
&self,
|
||||||
i: &trv::MultiEraInput,
|
|
||||||
tx: &trv::MultiEraTx,
|
|
||||||
resolved: &Option<UtxoMap>,
|
resolved: &Option<UtxoMap>,
|
||||||
) -> u5c::TxInput {
|
input: &trv::MultiEraInput,
|
||||||
let redeemers = tx.redeemers();
|
) -> Option<u5c::TxOutput> {
|
||||||
|
let as_txref = (*input.hash(), input.index() as u32);
|
||||||
|
|
||||||
let redeemer = redeemers
|
resolved
|
||||||
.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
|
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|x| x.get(&as_txref))
|
.and_then(|x| x.get(&as_txref))
|
||||||
.and_then(|(era, cbor)| {
|
.and_then(|(era, cbor)| {
|
||||||
let o = trv::MultiEraOutput::decode(*era, cbor.as_slice()).ok()?;
|
let o = trv::MultiEraOutput::decode(*era, cbor.as_slice()).ok()?;
|
||||||
Some(self.map_tx_output(&o))
|
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 {
|
u5c::TxInput {
|
||||||
tx_hash: i.hash().to_vec().into(),
|
tx_hash: input.hash().to_vec().into(),
|
||||||
output_index: i.index() as u32,
|
output_index: input.index() as u32,
|
||||||
redeemer: redeemer.map(|x| self.map_redeemer(x)),
|
as_output: self.decode_resolved_utxo(resolved, input),
|
||||||
as_output,
|
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 {
|
u5c::Tx {
|
||||||
hash: tx.hash().to_vec().into(),
|
hash: tx.hash().to_vec().into(),
|
||||||
inputs: tx
|
inputs: tx
|
||||||
.inputs()
|
.inputs_sorted_set()
|
||||||
.iter()
|
.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(),
|
.collect(),
|
||||||
outputs: tx.outputs().iter().map(|x| self.map_tx_output(x)).collect(),
|
outputs: tx.outputs().iter().map(|x| self.map_tx_output(x)).collect(),
|
||||||
certificates: tx.certs().iter().map(|x| self.map_cert(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: tx
|
||||||
.reference_inputs()
|
.reference_inputs()
|
||||||
.iter()
|
.iter()
|
||||||
.map(|x| self.map_tx_input(x, tx, &resolved))
|
.map(|x| self.map_tx_reference_input(x, &resolved))
|
||||||
.collect(),
|
.collect(),
|
||||||
witnesses: u5c::WitnessSet {
|
witnesses: u5c::WitnessSet {
|
||||||
vkeywitness: tx
|
vkeywitness: tx
|
||||||
|
|
@ -540,7 +566,7 @@ impl<C: LedgerContext> Mapper<C> {
|
||||||
collateral: tx
|
collateral: tx
|
||||||
.collateral()
|
.collateral()
|
||||||
.iter()
|
.iter()
|
||||||
.map(|x| self.map_tx_input(x, tx, &resolved))
|
.map(|x| self.map_tx_collateral(x, &resolved))
|
||||||
.collect(),
|
.collect(),
|
||||||
collateral_return: tx.collateral_return().map(|x| self.map_tx_output(&x)),
|
collateral_return: tx.collateral_return().map(|x| self.map_tx_output(&x)),
|
||||||
total_collateral: tx.total_collateral().unwrap_or_default(),
|
total_collateral: tx.total_collateral().unwrap_or_default(),
|
||||||
|
|
@ -586,3 +612,34 @@ impl<C: LedgerContext> Mapper<C> {
|
||||||
self.map_block(&block)
|
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
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
1804
test_data/u5c1.json
Normal file
File diff suppressed because one or more lines are too long
Loading…
Add table
Add a link
Reference in a new issue