Emurgo / cardano-serialization-lib

This is a library, written in Rust, for serialization & deserialization of data structures used in Cardano's Haskell implementation of Alonzo along with useful utility functions.
Other
231 stars 125 forks source link

PPViewHashesDontMatch when spending UTxO with InlineDatum from ReferenceScript #611

Closed jonathangenlambda closed 7 months ago

jonathangenlambda commented 1 year ago

I am trying to spend a UTxO with an InlineDatum sitting at a ReferenceScript. Effectively I want to create a new UTxO at the same ReferenceScript with the same InlineDatum, but with changed assets locked into it. However, I am running into:

Submitting TX failed due to error: ShelleyTxValidationError ShelleyBasedEraBabbage (ApplyTxError [UtxowFailure (AlonzoInBabbageUtxowPredFailure (PPViewHashesDontMatch SNothing (SJust (SafeHash "1f493f11378b3808b70d06f2da7a770a05694b37bec21c276083cc33bbc6172d"))))])

Here is the code I am using to construct the TX (shortened to the essential parts):

// setting up reference script stuff
const scriptAddress = CSL.Address.from_bech32(...);
const scriptHash = CSL.ScriptHash.from_hex(...);
const scrRefInput = CSL.TransactionInput.new(
    CSL.TransactionHash.from_hex(id),
    ix
  );
 const plutusRefInput =
  CSL.PlutusScriptSource.new_ref_input_with_lang_ver(
    scriptHash,
    scrRefInput,
    CSL.Language.new_plutus_v2()
  );

// setting up utxo we want to spend
const utxoInput = CSL.TransactionInput.new(
    CSL.TransactionHash.from_hex(utxo.id),
    utxo.ix
  );
const utxoValue = ...;
const utxoInlineDatum = CSL.encode_json_str_to_plutus_datum(
  JSON.stringify(utxo.datum),
  CSL.PlutusDatumSchema.DetailedSchema
);
const scriptRedeemerData = CSL.encode_json_str_to_plutus_datum(
  JSON.stringify(utxo.redeemer),
  CSL.PlutusDatumSchema.DetailedSchema
);
const scriptRedeemer = CSL.Redeemer.new(
  CSL.RedeemerTag.new_spend(),
  CSL.BigNum.zero(), // NOTE: using 0, add_plutus_script_input will set proper index automatically
  scriptRedeemerData,
  exUnits
);
const plutusWitness = CSL.PlutusWitness.new_with_ref_without_datum(
  plutusRefInput,
  scriptRedeemer
);

// constructing TxOut, shortened
const txOut = CSL.TransactionOutputBuilder.new()
  .with_address(scriptAddress)
  .with_plutus_data(utxoInlineDatum)
  .next()
  .with_value(utxoValue)
  .build();

// constructing inputs
const inputs = CSL.TxInputsBuilder.new();
const collInputs = CSL.TxInputsBuilder.new();
// adding some coin selection and collateral inputs...
inputs.add_plutus_script_input(plutusWitness, utxoInput, utxoValue); // crucial bit here
txBuilder.set_inputs(inputs);
txBuilder.add_reference_input(scrRefInput)
// setting collateral inputs total collaterl and return....

// add calc_script_data_hash here or...

// returning surplus
txBuilder.add_change_if_needed(walletAddress);

// .. add calc_script_data_hash here

// constructing TxBody, and witnesses
const txBody = txBuilder.build();
const txId = CSL.hash_transaction(txBody);
const witnesses = CSL.TransactionWitnessSet.new();

// add wallet witnesses...

const redeemers = CSL.Redeemers.new();
redeemers.add(psScriptRedeemer);
witnesses.set_redeemers(redeemers);

const tx = CSL.Transaction.new(...)

According to https://github.com/Emurgo/cardano-serialization-lib/issues/482 I should be using calc_script_data_hash however when I am using it before add_change_if_needed then I get PPViewHashesDontMatch with 2 hashes not matching:

Submitting TX failed due to error: ShelleyTxValidationError ShelleyBasedEraBabbage (ApplyTxError [UtxowFailure (AlonzoInBabbageUtxowPredFailure (PPViewHashesDontMatch (SJust (SafeHash "c1b930ae86f70cbf19e4b78b1749bf2dbbfc75be16e1455d09565990dffc63bd")) (SJust (SafeHash "1f493f11378b3808b70d06f2da7a770a05694b37bec21c276083cc33bbc6172d"))))])

If I use it after add_change_if_needed then I get PPViewHashesDontMatch with 2 hashes not matching as well as FeeTooSmallUTxO:

Submitting TX failed due to error: ShelleyTxValidationError ShelleyBasedEraBabbage (ApplyTxError [UtxowFailure (AlonzoInBabbageUtxowPredFailure (PPViewHashesDontMatch (SJust (SafeHash "c1b930ae86f70cbf19e4b78b1749bf2dbbfc75be16e1455d09565990dffc63bd")) (SJust (SafeHash "1f493f11378b3808b70d06f2da7a770a05694b37bec21c276083cc33bbc6172d")))),UtxowFailure (UtxoFailure (AlonzoInBabbageUtxoPredFailure (FeeTooSmallUTxO (Coin 192517) (Coin 191285))))])

I have a working version of this TX construction using cardano-api and am doing exactly the same, so I have no idea why this fails. I honestly also have no idea why I have to call calc_script_data_hash, this is not necessary in cardano-api TX construction.

Any help is really appreciated!

lisicky commented 1 year ago

HI @jonathangenlambda ! The steps should be in that order:

  1. all stuff related to changing tx builder (adding inputs, outputs, mint and etc.)
  2. calc_script_data_hash
  3. add_change_if_needed
  4. build_tx (build is deprecated)
  5. add vkey witnesses

All redeemer, scripts, datums should be added before calc_script_data_hash. Otherwise script_data_hash will be incorrect. If you change script related witnesses after building the tx and calc_script_data_hash your script_data_hash will be incorrect.

jonathangenlambda commented 1 year ago

Hi @lisicky , thanks for your support!

I am doing the steps now in the order you specified and have switchted to build_tx. To be more specific I do this:

  1. inputs.add_plutus_script_input(...)
  2. txBuilder.set_inputs(inputs);
  3. txBuilder.calc_script_data_hash(CSL.TxBuilderConstants.plutus_vasil_cost_models());
  4. txBuilder.add_change_if_needed(walletAddress);
  5. Add witnesses the following way:
const txWithoutWitnesses = txBuilder.build_tx();
  const txBody = txWithoutWitnesses.body();
  const txId = CSL.hash_transaction(txBody);
  const witnesses = CSL.TransactionWitnessSet.new();

  // add wallet witness
  bip32PrivateKeys.forEach((k) => {
    const skey = CSL.PrivateKey.from_bech32(k.to_bech32());
    const vkeyWitnesses = CSL.Vkeywitnesses.new();
    const vkeyWitness = CSL.make_vkey_witness(txId, skey);
    vkeyWitnesses.add(vkeyWitness);
    witnesses.set_vkeys(vkeyWitnesses);
  });

  // NOTE: if we forget to add these, despite signing will fail submission with NoRedeemer and MissingRedeemers
  const redeemers = CSL.Redeemers.new();
  redeemers.add(psScriptRedeemer);
  witnesses.set_redeemers(redeemers);

  // create the finalized transaction with witnesses
  const txFinal = CSL.Transaction.new(
    txBody,
    witnesses,
    auxData
  );

  const hashTxInitial = txBody.script_data_hash();
  const hashTxFinal = txFinal.body().script_data_hash();
  console.log(hashTxInitial.to_hex());
  console.log(hashTxFinal.to_hex());

However no luck, I get the same error:

Submitting TX failed due to error: ShelleyTxValidationError ShelleyBasedEraBabbage (ApplyTxError [UtxowFailure (AlonzoInBabbageUtxowPredFailure (PPViewHashesDontMatch (SJust (SafeHash "c1b930ae86f70cbf19e4b78b1749bf2dbbfc75be16e1455d09565990dffc63bd")) (SJust (SafeHash "1f493f11378b3808b70d06f2da7a770a05694b37bec21c276083cc33bbc6172d"))))])

In both console.log cases I get c1b930ae86f70cbf19e4b78b1749bf2dbbfc75be16e1455d09565990dffc63bd

When I tried to compute the hash myself before calc_script_data_hash, then I get a different hash 089c09bd42ee1b83e42bbdbc77a7ebd97eaead77ad5c36f56f9583ec677a6914:

const redeemers = CSL.Redeemers.new()
redeemers.add(psScriptRedeemer);
const plutusWitnessHash = CSL.hash_script_data(redeemers, CSL.TxBuilderConstants.plutus_vasil_cost_models())
console.log(plutusWitnessHash.to_hex());
jonathangenlambda commented 1 year ago

So I had another thought: I am running this stuff on my own local testnet, where I have changed the protocol parameter slotLength - but I didn't touch the cost models. Could this be the origin of this error?

Then again I am constructing and submitting other TXs such as creating a reference script and also locking assets in a UTxO at this reference script, using CSL successfully to my local testnet without these errors.

lisicky commented 1 year ago

@jonathangenlambda do you add a redeemer after building the tx? Basically it is better if you add all script related witnesses (scripts, datums, redeemers) by transaction builder before you build the tx otherwise you will have headache with script_data_hash, fee calculation, and transaction body hash. If you calculate script_data_hash by CSL.hash_script_data you need to put list of cost models that is used in the tx. (example: if your tx spend UTXO with plutus v2, you need use cost model of plutus v2 only).

lisicky commented 1 year ago

Hi @jonathangenlambda ! Do you still have the issue ?

jonathangenlambda commented 1 year ago

@lisicky thanks for your reply - we are doing what you described in your previous post, that is: adding all scripts, datums and redeemers before building the TX, so after we built the TX we change nothing. We are still working on this issue.

lisicky commented 1 year ago

@jonathangenlambda Could you share your the last code if it still produces PPViewHashesDontMatch ? Which CSL version do you use ?

jonathangenlambda commented 1 year ago

Sorry for the delay - we are using now a different approach, so didn't pursue this issue longer.