pallas-txbuilder: thread auxiliary_data through StagingTransaction → Conway build
addresses the four TODO comments in pallas-txbuilder/src/transaction/model.rs and conway.rs around auxiliary_data + auxiliary_data_hash. without this the conway builder hardcodes auxiliary_data: None, which blocks CIP-20 / CIP-25 / CIP-68 metadata. implementation: - new StagingTransaction field: pub auxiliary_data: Option<Vec<u8>>. stored as opaque cbor bytes (caller encodes alonzo::AuxiliaryData) since AuxiliaryData itself doesn't impl Eq, which the rest of StagingTransaction requires. - builder methods .auxiliary_data(cbor) and .clear_auxiliary_data(). - conway::build_conway_raw now decodes the bytes back into AuxiliaryData::decode_fragment and plugs it into pallas_tx.auxiliary_data. the existing auxiliary_data_hash population block is unchanged — it already computes the hash from pallas_tx.auxiliary_data after assignment. - decode failures map to TxBuilderError::CorruptedTxBytes. tests: - auxiliary_data_round_trips_through_build: encodes a CIP-25-shaped metadata, attaches, builds, decodes resulting tx, asserts both body.auxiliary_data_hash matches expected_hash.compute_hash() and the aux re-encodes byte-equivalent. - no_auxiliary_data_means_no_hash: confirms the absence path still works (no aux → hash field stays None). both pre-existing tests (staging_json_roundtrip, built_json_roundtrip, test_script_data_hash) continue to pass — the new field defaults to None and rides alongside. PR upstream pending; using as a vendored patch via [patch.crates-io] on the Sulkta side until merge.
This commit is contained in:
parent
af0463f5b6
commit
57b36d3a7c
3 changed files with 107 additions and 5 deletions
|
|
@ -4,8 +4,8 @@ use pallas_codec::utils::CborWrap;
|
|||
use pallas_crypto::hash::Hash;
|
||||
use pallas_primitives::{
|
||||
conway::{
|
||||
DatumOption, ExUnits as PallasExUnits, NativeScript, NetworkId, NonZeroInt, PlutusData,
|
||||
PlutusScript, PostAlonzoTransactionOutput, PseudoScript as PallasScript,
|
||||
AuxiliaryData, DatumOption, ExUnits as PallasExUnits, NativeScript, NetworkId, NonZeroInt,
|
||||
PlutusData, PlutusScript, PostAlonzoTransactionOutput, PseudoScript as PallasScript,
|
||||
PseudoTransactionOutput, Redeemer, RedeemerTag, TransactionBody, TransactionInput, Tx,
|
||||
Value, WitnessSet,
|
||||
},
|
||||
|
|
@ -270,8 +270,14 @@ impl BuildConway for StagingTransaction {
|
|||
Some(witness_set_redeemers)
|
||||
},
|
||||
},
|
||||
success: true, // TODO
|
||||
auxiliary_data: None.into(), // TODO
|
||||
success: true, // TODO
|
||||
auxiliary_data: self
|
||||
.auxiliary_data
|
||||
.as_deref()
|
||||
.map(AuxiliaryData::decode_fragment)
|
||||
.transpose()
|
||||
.map_err(|_| TxBuilderError::CorruptedTxBytes)?
|
||||
.into(),
|
||||
};
|
||||
|
||||
// TODO: pallas auxiliary_data_hash should be Hash<32> not Bytes
|
||||
|
|
@ -375,3 +381,74 @@ impl Output {
|
|||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::transaction::model::StagingTransaction;
|
||||
use pallas_codec::minicbor;
|
||||
use pallas_primitives::alonzo::{AuxiliaryData, Metadatum, PostAlonzoAuxiliaryData};
|
||||
use pallas_traverse::ComputeHash;
|
||||
|
||||
/// Encode a CIP-25-shaped auxiliary data, attach it to a tx, and
|
||||
/// confirm the built tx body carries the matching auxiliary_data_hash.
|
||||
/// Round-trip protection for the fork's metadata plumbing.
|
||||
#[test]
|
||||
fn auxiliary_data_round_trips_through_build() {
|
||||
// Minimal CIP-25-style metadata: label 721 → text "test".
|
||||
let metadata: pallas_primitives::alonzo::Metadata = vec![
|
||||
(721u64, Metadatum::Text("test".into())),
|
||||
]
|
||||
.into();
|
||||
let aux = AuxiliaryData::PostAlonzo(PostAlonzoAuxiliaryData {
|
||||
metadata: Some(metadata),
|
||||
native_scripts: None,
|
||||
plutus_scripts: None,
|
||||
});
|
||||
let aux_bytes = minicbor::to_vec(&aux).expect("encode aux");
|
||||
let expected_hash = aux.compute_hash();
|
||||
|
||||
let tx = StagingTransaction::new()
|
||||
.auxiliary_data(aux_bytes)
|
||||
.network_id(0)
|
||||
.fee(180_000)
|
||||
.build_conway_raw()
|
||||
.expect("build_conway_raw");
|
||||
|
||||
// The body's auxiliary_data_hash should match the aux's
|
||||
// compute_hash output.
|
||||
let body =
|
||||
pallas_primitives::conway::Tx::decode_fragment(tx.tx_bytes.as_ref())
|
||||
.expect("decode tx_bytes");
|
||||
let body_aux_hash_bytes: Vec<u8> = body
|
||||
.transaction_body
|
||||
.auxiliary_data_hash
|
||||
.clone()
|
||||
.expect("auxiliary_data_hash should be set when aux is attached")
|
||||
.to_vec();
|
||||
assert_eq!(body_aux_hash_bytes, expected_hash.to_vec());
|
||||
|
||||
// And the auxiliary_data should round-trip back.
|
||||
let pallas_aux = match body.auxiliary_data {
|
||||
pallas_codec::utils::Nullable::Some(a) => a,
|
||||
other => panic!("expected Some(aux), got {other:?}"),
|
||||
};
|
||||
// Re-encode the aux that came back and compare.
|
||||
let round_tripped_bytes = minicbor::to_vec(&pallas_aux).expect("re-encode aux");
|
||||
let original_again = minicbor::to_vec(&aux).expect("re-encode original");
|
||||
assert_eq!(round_tripped_bytes, original_again);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_auxiliary_data_means_no_hash() {
|
||||
let tx = StagingTransaction::new()
|
||||
.network_id(0)
|
||||
.fee(180_000)
|
||||
.build_conway_raw()
|
||||
.expect("build_conway_raw");
|
||||
let body =
|
||||
pallas_primitives::conway::Tx::decode_fragment(tx.tx_bytes.as_ref())
|
||||
.expect("decode tx_bytes");
|
||||
assert!(body.transaction_body.auxiliary_data_hash.is_none());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,10 +40,14 @@ pub struct StagingTransaction {
|
|||
pub signature_amount_override: Option<u8>,
|
||||
pub change_address: Option<Address>,
|
||||
pub language_view: Option<scriptdata::LanguageView>,
|
||||
/// CBOR-encoded `pallas_primitives::alonzo::AuxiliaryData`. Stored
|
||||
/// as bytes rather than the typed enum because `AuxiliaryData`
|
||||
/// doesn't implement `Eq`, which the rest of this struct requires.
|
||||
/// `conway::build_conway_raw` decodes on the way out.
|
||||
pub auxiliary_data: Option<Vec<u8>>,
|
||||
// pub certificates: TODO
|
||||
// pub withdrawals: TODO
|
||||
// pub updates: TODO
|
||||
// pub auxiliary_data: TODO
|
||||
// pub phase_2_valid: TODO
|
||||
}
|
||||
|
||||
|
|
@ -372,6 +376,26 @@ impl StagingTransaction {
|
|||
self.change_address = None;
|
||||
self
|
||||
}
|
||||
|
||||
/// Attach pre-encoded auxiliary data (CBOR bytes of an
|
||||
/// `alonzo::AuxiliaryData`). The conway builder will decode on
|
||||
/// build and compute `auxiliary_data_hash` automatically.
|
||||
///
|
||||
/// Most callers use this for transaction metadata (CIP-25 / CIP-20
|
||||
/// / CIP-68) and/or to ship native scripts as auxiliary data.
|
||||
/// Construct an `AuxiliaryData::PostAlonzo(...)` with the
|
||||
/// metadata + scripts you want, encode with `Fragment::encode_fragment`
|
||||
/// or `minicbor::to_vec`, and pass the bytes here.
|
||||
pub fn auxiliary_data(mut self, cbor_bytes: Vec<u8>) -> Self {
|
||||
self.auxiliary_data = Some(cbor_bytes);
|
||||
self
|
||||
}
|
||||
|
||||
/// Drop any auxiliary data previously attached.
|
||||
pub fn clear_auxiliary_data(mut self) -> Self {
|
||||
self.auxiliary_data = None;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Don't want our wrapper types in fields public
|
||||
|
|
|
|||
|
|
@ -480,6 +480,7 @@ mod tests {
|
|||
change_address: Some(Address(PallasAddress::from_str("addr1g9ekml92qyvzrjmawxkh64r2w5xr6mg9ngfmxh2khsmdrcudevsft64mf887333adamant").unwrap())),
|
||||
script_data_hash: Some(Bytes32([0; 32])),
|
||||
language_view: Some(crate::scriptdata::LanguageView(1, vec![1, 2, 3])),
|
||||
auxiliary_data: None,
|
||||
};
|
||||
|
||||
let serialised_tx = serde_json::to_string(&tx).unwrap();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue