Skip to content

Commit

Permalink
feat(txbuilder): compute ScriptDataHash with all edge cases
Browse files Browse the repository at this point in the history
  • Loading branch information
scarmuega committed Oct 22, 2024
1 parent ae4e800 commit 37f4b34
Show file tree
Hide file tree
Showing 13 changed files with 220 additions and 139 deletions.
45 changes: 6 additions & 39 deletions pallas-primitives/src/alonzo/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1031,7 +1031,7 @@ pub enum PlutusData {
Map(KeyValuePairs<PlutusData, PlutusData>),
BigInt(BigInt),
BoundedBytes(BoundedBytes),
Array(Vec<PlutusData>),
Array(MaybeIndefArray<PlutusData>),
}

impl<'b, C> minicbor::decode::Decode<'b, C> for PlutusData {
Expand Down Expand Up @@ -1086,25 +1086,6 @@ impl<'b, C> minicbor::decode::Decode<'b, C> for PlutusData {
}
}

fn encode_list<C, W: minicbor::encode::Write, A: minicbor::encode::Encode<C>>(
a: &Vec<A>,
e: &mut minicbor::Encoder<W>,
ctx: &mut C,
) -> Result<(), minicbor::encode::Error<W::Error>> {
// Mimics default haskell list encoding from cborg:
// We use indef array for non-empty arrays but definite 0-length array for empty
if a.is_empty() {
e.array(0)?;
} else {
e.begin_array()?;
for v in a {
e.encode_with(v, ctx)?;
}
e.end()?;
}
Ok(())
}

impl<C> minicbor::encode::Encode<C> for PlutusData {
fn encode<W: minicbor::encode::Write>(
&self,
Expand All @@ -1131,11 +1112,7 @@ impl<C> minicbor::encode::Encode<C> for PlutusData {
e.encode_with(a, ctx)?;
}
Self::Array(a) => {
// we use definite array for empty array or indef array otherwise to match
// haskell implementation https://github.com/input-output-hk/plutus/blob/9538fc9829426b2ecb0628d352e2d7af96ec8204/plutus-core/plutus-core/src/PlutusCore/Data.hs#L153
// default encoder for a list:
// https://github.com/well-typed/cborg/blob/4bdc818a1f0b35f38bc118a87944630043b58384/serialise/src/Codec/Serialise/Class.hs#L181
encode_list(a, e, ctx)?;
e.encode_with(a, ctx)?;
}
};

Expand All @@ -1147,7 +1124,7 @@ impl<C> minicbor::encode::Encode<C> for PlutusData {
pub struct Constr<A> {
pub tag: u64,
pub any_constructor: Option<u64>,
pub fields: Vec<A>,
pub fields: MaybeIndefArray<A>,
}

impl<'b, C, A> minicbor::decode::Decode<'b, C> for Constr<A>
Expand Down Expand Up @@ -1197,22 +1174,12 @@ where

match self.tag {
102 => {
// definite length array here
// https://github.com/input-output-hk/plutus/blob/9538fc9829426b2ecb0628d352e2d7af96ec8204/plutus-core/plutus-core/src/PlutusCore/Data.hs#L152
e.array(2)?;
e.encode_with(self.any_constructor.unwrap_or_default(), ctx)?;

// we use definite array for empty array or indef array otherwise to match
// haskell implementation https://github.com/input-output-hk/plutus/blob/9538fc9829426b2ecb0628d352e2d7af96ec8204/plutus-core/plutus-core/src/PlutusCore/Data.hs#L144
// default encoder for a list:
// https://github.com/well-typed/cborg/blob/4bdc818a1f0b35f38bc118a87944630043b58384/serialise/src/Codec/Serialise/Class.hs#L181
encode_list(&self.fields, e, ctx)?;
let x = (self.any_constructor.unwrap_or_default(), &self.fields);
e.encode_with(x, ctx)?;
Ok(())
}
_ => {
// we use definite array for empty array or indef array otherwise to match
// haskell implementation. See above reference.
encode_list(&self.fields, e, ctx)?;
e.encode_with(&self.fields, ctx)?;
Ok(())
}
}
Expand Down
44 changes: 10 additions & 34 deletions pallas-primitives/src/conway/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1364,53 +1364,26 @@ pub struct RedeemersValue {
pub ex_units: ExUnits,
}

// TODO: Redeemers needs to be KeepRaw because of script data hash
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
#[serde(transparent)]
pub struct Redeemers(NonEmptyKeyValuePairs<RedeemersKey, RedeemersValue>);

impl Deref for Redeemers {
type Target = NonEmptyKeyValuePairs<RedeemersKey, RedeemersValue>;

fn deref(&self) -> &Self::Target {
&self.0
}
pub enum Redeemers {
List(MaybeIndefArray<Redeemer>),
Map(NonEmptyKeyValuePairs<RedeemersKey, RedeemersValue>),
}

impl From<NonEmptyKeyValuePairs<RedeemersKey, RedeemersValue>> for Redeemers {
fn from(value: NonEmptyKeyValuePairs<RedeemersKey, RedeemersValue>) -> Self {
Redeemers(value)
Redeemers::Map(value)
}
}

impl<'b, C> minicbor::Decode<'b, C> for Redeemers {
fn decode(d: &mut minicbor::Decoder<'b>, ctx: &mut C) -> Result<Self, minicbor::decode::Error> {
match d.datatype()? {
minicbor::data::Type::Array | minicbor::data::Type::ArrayIndef => {
let redeemers: Vec<Redeemer> = d.decode_with(ctx)?;

let kvs = redeemers
.into_iter()
.map(|x| {
(
RedeemersKey {
tag: x.tag,
index: x.index,
},
RedeemersValue {
data: x.data,
ex_units: x.ex_units,
},
)
})
.collect::<Vec<_>>()
.try_into()
.map_err(|_| Error::message("decoding empty redeemers"))?;

Ok(Self(kvs))
Ok(Self::List(d.decode_with(ctx)?))
}
minicbor::data::Type::Map | minicbor::data::Type::MapIndef => {
Ok(Self(d.decode_with(ctx)?))
Ok(Self::Map(d.decode_with(ctx)?))
}
_ => Err(minicbor::decode::Error::message(
"invalid type for redeemers struct",
Expand All @@ -1425,7 +1398,10 @@ impl<C> minicbor::Encode<C> for Redeemers {
e: &mut minicbor::Encoder<W>,
ctx: &mut C,
) -> Result<(), minicbor::encode::Error<W::Error>> {
e.encode_with(&self.0, ctx)?;
match self {
Self::List(x) => e.encode_with(x, ctx)?,
Self::Map(x) => e.encode_with(x, ctx)?,
};

Ok(())
}
Expand Down
14 changes: 7 additions & 7 deletions pallas-traverse/src/hashes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ mod tests {
use crate::{Era, MultiEraTx};

use super::{ComputeHash, OriginalHash};
use pallas_codec::utils::Int;
use pallas_codec::utils::{Int, MaybeIndefArray};
use pallas_codec::{minicbor, utils::Bytes};
use pallas_crypto::hash::Hash;
use pallas_crypto::key::ed25519::PublicKey;
Expand Down Expand Up @@ -295,26 +295,26 @@ mod tests {
let pd = alonzo::PlutusData::Constr(alonzo::Constr::<alonzo::PlutusData> {
tag: 1280,
any_constructor: None,
fields: vec![
fields: MaybeIndefArray::Indef(vec![
alonzo::PlutusData::BigInt(alonzo::BigInt::Int(Int::from(4))),
alonzo::PlutusData::Constr(alonzo::Constr::<alonzo::PlutusData> {
tag: 124,
any_constructor: None,
fields: vec![
fields: MaybeIndefArray::Indef(vec![
alonzo::PlutusData::BigInt(alonzo::BigInt::Int(Int::from(-4))),
alonzo::PlutusData::Constr(alonzo::Constr::<alonzo::PlutusData> {
tag: 102,
any_constructor: Some(453),
fields: vec![
fields: MaybeIndefArray::Indef(vec![
alonzo::PlutusData::BigInt(alonzo::BigInt::Int(Int::from(2))),
alonzo::PlutusData::BigInt(alonzo::BigInt::Int(Int::from(3434))),
],
]),
}),
alonzo::PlutusData::BigInt(alonzo::BigInt::Int(Int::from(-11828293))),
],
]),
}),
alonzo::PlutusData::BigInt(alonzo::BigInt::Int(Int::from(11828293))),
],
]),
});

// if you need to try this out in the cardano-cli, uncomment this line to see
Expand Down
15 changes: 14 additions & 1 deletion pallas-traverse/src/redeemers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ impl<'b> MultiEraRedeemer<'b> {
Self::AlonzoCompatible(Box::new(Cow::Borrowed(redeemer)))
}

pub fn from_conway(
pub fn from_conway_map(
redeemers_key: &'b conway::RedeemersKey,
redeemers_val: &'b conway::RedeemersValue,
) -> Self {
Expand All @@ -65,4 +65,17 @@ impl<'b> MultiEraRedeemer<'b> {
Box::new(Cow::Borrowed(redeemers_val)),
)
}

pub fn from_conway_list(redeemer: &'b conway::Redeemer) -> Self {
Self::Conway(
Box::new(Cow::Owned(conway::RedeemersKey {
tag: redeemer.tag,
index: redeemer.index,
})),
Box::new(Cow::Owned(conway::RedeemersValue {
data: redeemer.data.clone(),
ex_units: redeemer.ex_units,
})),
)
}
}
4 changes: 2 additions & 2 deletions pallas-traverse/src/tx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,11 @@ impl<'b> MultiEraTx<'b> {
/// decode using Babbage first even if Conway is newer.
pub fn decode(cbor: &'b [u8]) -> Result<Self, Error> {
if let Ok(tx) = minicbor::decode(cbor) {
return Ok(MultiEraTx::Babbage(Box::new(Cow::Owned(tx))));
return Ok(MultiEraTx::Conway(Box::new(Cow::Owned(tx))));
}

if let Ok(tx) = minicbor::decode(cbor) {
return Ok(MultiEraTx::Conway(Box::new(Cow::Owned(tx))));
return Ok(MultiEraTx::Babbage(Box::new(Cow::Owned(tx))));
}

if let Ok(tx) = minicbor::decode(cbor) {
Expand Down
19 changes: 11 additions & 8 deletions pallas-traverse/src/witnesses.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use pallas_codec::utils::KeepRaw;
use pallas_primitives::{
alonzo::{self, BootstrapWitness, NativeScript, PlutusData, VKeyWitness},
babbage::PlutusV2Script,
conway::PlutusV3Script,
conway::{self, PlutusV3Script},
};

use crate::{MultiEraRedeemer, MultiEraTx};
Expand Down Expand Up @@ -145,13 +145,16 @@ impl<'b> MultiEraTx<'b> {
.flat_map(|x| x.iter())
.map(MultiEraRedeemer::from_alonzo_compatible)
.collect(),
Self::Conway(x) => x
.transaction_witness_set
.redeemer
.iter()
.flat_map(|x| x.iter())
.map(|(k, v)| MultiEraRedeemer::from_conway(k, v))
.collect(),
Self::Conway(x) => match x.transaction_witness_set.redeemer.as_deref() {
Some(conway::Redeemers::Map(x)) => x
.iter()
.map(|(k, v)| MultiEraRedeemer::from_conway_map(k, v))
.collect(),
Some(conway::Redeemers::List(x)) => {
x.iter().map(MultiEraRedeemer::from_conway_list).collect()
}
_ => vec![],
},
}
}

Expand Down
13 changes: 12 additions & 1 deletion pallas-txbuilder/src/babbage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use pallas_primitives::{
use pallas_traverse::ComputeHash;

use crate::{
scriptdata,
transaction::{
model::{
BuilderEra, BuiltTransaction, DatumKind, ExUnits, Output, RedeemerPurpose, ScriptKind,
Expand Down Expand Up @@ -153,6 +154,7 @@ impl BuildBabbage for StagingTransaction {
.iter()
.map(|(p, _)| **p)
.collect::<Vec<_>>();

mint_policies.sort_unstable_by_key(|x| *x);

let mut redeemers = vec![];
Expand Down Expand Up @@ -206,6 +208,15 @@ impl BuildBabbage for StagingTransaction {
}
};

let script_data_hash = scriptdata::ScriptData {
redeemers: pallas_primitives::conway::Redeemers::List(
pallas_codec::utils::MaybeIndefArray::Def(redeemers.clone()),
),
datums: Some(plutus_data),
language_view: self.language_view,
}
.hash();

let mut pallas_tx = BabbageTx {
transaction_body: TransactionBody {
inputs,
Expand All @@ -218,7 +229,7 @@ impl BuildBabbage for StagingTransaction {
update: None, // TODO
auxiliary_data_hash: None, // TODO (accept user input)
mint,
script_data_hash: self.script_data_hash.map(|x| x.0.into()),
script_data_hash: Some(script_data_hash),
collateral: opt_if_empty(collateral),
required_signers: opt_if_empty(required_signers),
network_id,
Expand Down
1 change: 1 addition & 0 deletions pallas-txbuilder/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
mod babbage;
mod scriptdata;
mod transaction;

pub use babbage::BuildBabbage;
Expand Down
Loading

0 comments on commit 37f4b34

Please sign in to comment.