From 8fbc48827a050e0389c62b9da4e73f7be0eb7788 Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Thu, 26 May 2022 15:10:44 +0300 Subject: [PATCH 01/22] Extracted tx_inputs_builder. Added `tx_builder.set_inputs` and `tx_builder.set_collateral`. Deprecated old methods to add inputs in the builder. --- rust/src/tx_builder.rs | 332 ++++----------------- rust/src/tx_builder/tx_inputs_builder.rs | 363 +++++++++++++++++++++++ 2 files changed, 425 insertions(+), 270 deletions(-) create mode 100644 rust/src/tx_builder/tx_inputs_builder.rs diff --git a/rust/src/tx_builder.rs b/rust/src/tx_builder.rs index 43b9db57..582e1ba8 100644 --- a/rust/src/tx_builder.rs +++ b/rust/src/tx_builder.rs @@ -1,42 +1,48 @@ +mod tx_inputs_builder; + use super::*; use super::fees; use super::utils; -use super::output_builder::{TransactionOutputAmountBuilder}; +use super::output_builder::TransactionOutputAmountBuilder; use std::collections::{BTreeMap, BTreeSet, HashSet}; use linked_hash_map::LinkedHashMap; +use tx_inputs_builder::{MockWitnessSet, PlutusWitness, PlutusWitnesses, ScriptWitnessType}; +use crate::tx_builder::tx_inputs_builder::{TxBuilderInput, TxInputsBuilder}; // comes from witsVKeyNeeded in the Ledger spec -fn witness_keys_for_cert(cert_enum: &Certificate, keys: &mut BTreeSet) { +fn witness_keys_for_cert(cert_enum: &Certificate) -> RequiredSignersSet { + let mut set = RequiredSignersSet::new(); match &cert_enum.0 { // stake key registrations do not require a witness CertificateEnum::StakeRegistration(_cert) => {}, CertificateEnum::StakeDeregistration(cert) => { - keys.insert(cert.stake_credential().to_keyhash().unwrap()); + set.insert(cert.stake_credential().to_keyhash().unwrap()); }, CertificateEnum::StakeDelegation(cert) => { - keys.insert(cert.stake_credential().to_keyhash().unwrap()); + set.insert(cert.stake_credential().to_keyhash().unwrap()); }, CertificateEnum::PoolRegistration(cert) => { for owner in &cert.pool_params().pool_owners().0 { - keys.insert(owner.clone()); + set.insert(owner.clone()); } - keys.insert( + set.insert( Ed25519KeyHash::from_bytes(cert.pool_params().operator().to_bytes()).unwrap() ); }, CertificateEnum::PoolRetirement(cert) => { - keys.insert( + set.insert( Ed25519KeyHash::from_bytes(cert.pool_keyhash().to_bytes()).unwrap() ); }, CertificateEnum::GenesisKeyDelegation(cert) => { - keys.insert( + set.insert( Ed25519KeyHash::from_bytes(cert.genesis_delegate_hash().to_bytes()).unwrap() ); }, // not witness as there is no single core node or genesis key that posts the certificate CertificateEnum::MoveInstantaneousRewardsCert(_cert) => {}, } + set } fn fake_private_key() -> Bip32PrivateKey { @@ -61,14 +67,12 @@ fn fake_raw_key_public() -> PublicKey { } fn count_needed_vkeys(tx_builder: &TransactionBuilder) -> usize { - let input_hashes = &tx_builder.input_types.vkeys; - match &tx_builder.mint_scripts { - None => input_hashes.len(), - Some(scripts) => { - // Union all input keys with minting keys - input_hashes.union(&RequiredSignersSet::from(scripts)).count() - } + let mut input_hashes: RequiredSignersSet = tx_builder.inputs.required_signers(); + input_hashes.extend(tx_builder.collateral.required_signers()); + if let Some(scripts) = &tx_builder.mint_scripts { + input_hashes.extend(RequiredSignersSet::from(scripts)); } + input_hashes.len() } // tx_body must be the result of building from tx_builder @@ -94,11 +98,12 @@ fn fake_full_tx(tx_builder: &TransactionBuilder, body: TransactionBody) -> Resul Some(result) }, }; - let bootstrap_keys = match tx_builder.input_types.bootstraps.len() { + let bootstraps = &tx_builder.inputs.bootstraps(); + let bootstrap_keys = match bootstraps.len() { 0 => None, _x => { let mut result = BootstrapWitnesses::new(); - for addr in &tx_builder.input_types.bootstraps { + for addr in bootstraps { // picking icarus over daedalus for fake witness generation shouldn't matter result.add(&make_icarus_bootstrap_witness( &TransactionHash::from([0u8; TransactionHash::BYTE_COUNT]), @@ -167,111 +172,6 @@ fn min_fee(tx_builder: &TransactionBuilder) -> Result { fees::min_fee(&full_tx, &tx_builder.config.fee_algo) } - -#[wasm_bindgen] -#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] -pub struct PlutusWitnesses(Vec); - -#[wasm_bindgen] -impl PlutusWitnesses { - pub fn new() -> Self { - Self(Vec::new()) - } - - pub fn len(&self) -> usize { - self.0.len() - } - - pub fn get(&self, index: usize) -> PlutusWitness { - self.0[index].clone() - } - - pub fn add(&mut self, elem: &PlutusWitness) { - self.0.push(elem.clone()); - } - - fn collect(&self) -> (PlutusScripts, PlutusList, Redeemers) { - let mut s = PlutusScripts::new(); - let mut d = PlutusList::new(); - let mut r = Redeemers::new(); - self.0.iter().for_each(|w| { - s.add(&w.script); - d.add(&w.datum); - r.add(&w.redeemer); - }); - (s, d, r) - } -} - -impl From> for PlutusWitnesses { - fn from(scripts: Vec) -> Self { - scripts.iter().fold(PlutusWitnesses::new(), |mut scripts, s| { - scripts.add(s); - scripts - }) - } -} - -#[wasm_bindgen] -#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] -pub struct PlutusWitness { - script: PlutusScript, - datum: PlutusData, - redeemer: Redeemer, -} - -#[wasm_bindgen] -impl PlutusWitness { - - pub fn new(script: &PlutusScript, datum: &PlutusData, redeemer: &Redeemer) -> Self { - Self { - script: script.clone(), - datum: datum.clone(), - redeemer: redeemer.clone(), - } - } - - pub fn script(&self) -> PlutusScript { - self.script.clone() - } - - pub fn datum(&self) -> PlutusData { - self.datum.clone() - } - - pub fn redeemer(&self) -> Redeemer { - self.redeemer.clone() - } - - fn clone_with_redeemer_index(&self, index: &BigNum) -> Self { - Self { - script: self.script.clone(), - datum: self.datum.clone(), - redeemer: self.redeemer.clone_with_index(index), - } - } -} - -#[derive(Clone, Debug)] -enum ScriptWitnessType { - NativeScriptWitness(NativeScript), - PlutusScriptWitness(PlutusWitness), -} - -// We need to know how many of each type of witness will be in the transaction so we can calculate the tx fee -#[derive(Clone, Debug)] -struct MockWitnessSet { - vkeys: BTreeSet, - scripts: LinkedHashMap>, - bootstraps: BTreeSet>, -} - -#[derive(Clone, Debug)] -struct TxBuilderInput { - input: TransactionInput, - amount: Value, // we need to keep track of the amount in the inputs for input selection -} - #[wasm_bindgen] pub enum CoinSelectionStrategyCIP2 { /// Performs CIP2's Largest First ada-only selection. Will error if outputs contain non-ADA assets. @@ -382,7 +282,8 @@ impl TransactionBuilderConfigBuilder { #[derive(Clone, Debug)] pub struct TransactionBuilder { config: TransactionBuilderConfig, - inputs: Vec<(TxBuilderInput, Option)>, + inputs: TxInputsBuilder, + collateral: TxInputsBuilder, outputs: TransactionOutputs, fee: Option, ttl: Option, // absolute slot number @@ -390,7 +291,6 @@ pub struct TransactionBuilder { withdrawals: Option, auxiliary_data: Option, validity_start_interval: Option, - input_types: MockWitnessSet, mint: Option, mint_scripts: Option, script_data_hash: Option, @@ -649,16 +549,20 @@ impl TransactionBuilder { Ok(()) } + pub fn set_inputs(&mut self, inputs: &TxInputsBuilder) { + self.inputs = inputs.clone(); + } + + pub fn set_collateral(&mut self, collateral: &TxInputsBuilder) { + self.collateral = collateral.clone(); + } + /// We have to know what kind of inputs these are to know what kind of mock witnesses to create since /// 1) mock witnesses have different lengths depending on the type which changes the expecting fee /// 2) Witnesses are a set so we need to get rid of duplicates to avoid over-estimating the fee + #[deprecated(since = "10.2.0", note="Use `.set_inputs`")] pub fn add_key_input(&mut self, hash: &Ed25519KeyHash, input: &TransactionInput, amount: &Value) { - let inp = TxBuilderInput { - input: input.clone(), - amount: amount.clone(), - }; - self.inputs.push((inp, None)); - self.input_types.vkeys.insert(hash.clone()); + self.inputs.add_key_input(hash, input, amount); } /// This method adds the input to the builder BUT leaves a missing spot for the witness native script @@ -668,178 +572,72 @@ impl TransactionBuilder { /// /// Or instead use `.add_native_script_input` and `.add_plutus_script_input` /// to add inputs right along with the script, instead of the script hash + #[deprecated(since = "10.2.0", note="Use `.set_inputs`")] pub fn add_script_input(&mut self, hash: &ScriptHash, input: &TransactionInput, amount: &Value) { - let inp = TxBuilderInput { - input: input.clone(), - amount: amount.clone(), - }; - self.inputs.push((inp, Some(hash.clone()))); - if !self.input_types.scripts.contains_key(hash) { - self.input_types.scripts.insert(hash.clone(), None); - } + self.inputs.add_script_input(hash, input, amount); } /// This method will add the input to the builder and also register the required native script witness + #[deprecated(since = "10.2.0", note="Use `.set_inputs`")] pub fn add_native_script_input(&mut self, script: &NativeScript, input: &TransactionInput, amount: &Value) { - let hash = script.hash(); - self.add_script_input(&hash, input, amount); - self.input_types.scripts.insert( - hash, - Some(ScriptWitnessType::NativeScriptWitness(script.clone())), - ); + self.inputs.add_native_script_input(script, input, amount); } /// This method will add the input to the builder and also register the required plutus witness + #[deprecated(since = "10.2.0", note="Use `.set_inputs`")] pub fn add_plutus_script_input(&mut self, witness: &PlutusWitness, input: &TransactionInput, amount: &Value) { - let hash = witness.script.hash(); - self.add_script_input(&hash, input, amount); - self.input_types.scripts.insert( - hash, - Some(ScriptWitnessType::PlutusScriptWitness(witness.clone())), - ); + self.inputs.add_plutus_script_input(witness, input, amount); } + #[deprecated(since = "10.2.0", note="Use `.set_inputs`")] pub fn add_bootstrap_input(&mut self, hash: &ByronAddress, input: &TransactionInput, amount: &Value) { - let inp = TxBuilderInput { - input: input.clone(), - amount: amount.clone(), - }; - self.inputs.push((inp, None)); - self.input_types.bootstraps.insert(hash.to_bytes()); + self.inputs.add_bootstrap_input(hash, input, amount); } /// Note that for script inputs this method will use underlying generic `.add_script_input` /// which leaves a required empty spot for the script witness (or witnesses in case of Plutus). /// You can use `.add_native_script_input` or `.add_plutus_script_input` directly to register the input along with the witness. + #[deprecated(since = "10.2.0", note="Use `.set_inputs`")] pub fn add_input(&mut self, address: &Address, input: &TransactionInput, amount: &Value) { - match &BaseAddress::from_address(address) { - Some(addr) => { - match &addr.payment_cred().to_keyhash() { - Some(hash) => return self.add_key_input(hash, input, amount), - None => (), - } - match &addr.payment_cred().to_scripthash() { - Some(hash) => return self.add_script_input(hash, input, amount), - None => (), - } - }, - None => (), - } - match &EnterpriseAddress::from_address(address) { - Some(addr) => { - match &addr.payment_cred().to_keyhash() { - Some(hash) => return self.add_key_input(hash, input, amount), - None => (), - } - match &addr.payment_cred().to_scripthash() { - Some(hash) => return self.add_script_input(hash, input, amount), - None => (), - } - }, - None => (), - } - match &PointerAddress::from_address(address) { - Some(addr) => { - match &addr.payment_cred().to_keyhash() { - Some(hash) => return self.add_key_input(hash, input, amount), - None => (), - } - match &addr.payment_cred().to_scripthash() { - Some(hash) => return self.add_script_input(hash, input, amount), - None => (), - } - }, - None => (), - } - match &ByronAddress::from_address(address) { - Some(addr) => { - return self.add_bootstrap_input(addr, input, amount); - }, - None => (), - } + self.inputs.add_input(address, input, amount); } /// Returns the number of still missing input scripts (either native or plutus) /// Use `.add_required_native_input_scripts` or `.add_required_plutus_input_scripts` to add the missing scripts + #[deprecated(since = "10.2.0", note="Use `.set_inputs`")] pub fn count_missing_input_scripts(&self) -> usize { - self.input_types.scripts.values().filter(|s| { s.is_none() }).count() + self.inputs.count_missing_input_scripts() } /// Try adding the specified scripts as witnesses for ALREADY ADDED script inputs /// Any scripts that don't match any of the previously added inputs will be ignored /// Returns the number of remaining required missing witness scripts /// Use `.count_missing_input_scripts` to find the number of still missing scripts + #[deprecated(since = "10.2.0", note="Use `.set_inputs`")] pub fn add_required_native_input_scripts(&mut self, scripts: &NativeScripts) -> usize { - scripts.0.iter().for_each(|s: &NativeScript| { - let hash = s.hash(); - if self.input_types.scripts.contains_key(&hash) { - self.input_types.scripts.insert( - hash, - Some(ScriptWitnessType::NativeScriptWitness(s.clone())), - ); - } - }); - self.count_missing_input_scripts() + self.inputs.add_required_native_input_scripts(scripts) } /// Try adding the specified scripts as witnesses for ALREADY ADDED script inputs /// Any scripts that don't match any of the previously added inputs will be ignored /// Returns the number of remaining required missing witness scripts /// Use `.count_missing_input_scripts` to find the number of still missing scripts + #[deprecated(since = "10.2.0", note="Use `.set_inputs`")] pub fn add_required_plutus_input_scripts(&mut self, scripts: &PlutusWitnesses) -> usize { - scripts.0.iter().for_each(|s: &PlutusWitness| { - let hash = s.script.hash(); - if self.input_types.scripts.contains_key(&hash) { - self.input_types.scripts.insert( - hash, - Some(ScriptWitnessType::PlutusScriptWitness(s.clone())), - ); - } - }); - self.count_missing_input_scripts() + self.inputs.add_required_plutus_input_scripts(scripts) } /// Returns a copy of the current script input witness scripts in the builder + #[deprecated(since = "10.2.0", note="Use `.set_inputs`")] pub fn get_native_input_scripts(&self) -> Option { - let mut scripts = NativeScripts::new(); - self.input_types.scripts.values().for_each(|option| { - if let Some(ScriptWitnessType::NativeScriptWitness(s)) = option { - scripts.add(&s); - } - }); - if scripts.len() > 0 { Some(scripts) } else { None } + self.inputs.get_native_input_scripts() } /// Returns a copy of the current plutus input witness scripts in the builder. /// NOTE: each plutus witness will be cloned with a specific corresponding input index + #[deprecated(since = "10.2.0", note="Use `.set_inputs`")] pub fn get_plutus_input_scripts(&self) -> Option { - /* - * === EXPLANATION === - * The `Redeemer` object contains the `.index` field which is supposed to point - * exactly to the index of the corresponding input in the inputs array. We want to - * simplify and automate this as much as possible for the user to not have to care about it. - * - * For this we store the script hash along with the input, when it was registered, and - * now we create a map of script hashes to their input indexes. - * - * The registered witnesses are then each cloned with the new correct redeemer input index. - */ - let script_hash_index_map: BTreeMap<&ScriptHash, BigNum> = self.inputs.iter().enumerate() - .fold(BTreeMap::new(), |mut m, (i, (_, hash_option))| { - if let Some(hash) = hash_option { - m.insert(hash, to_bignum(i as u64)); - } - m - }); - let mut scripts = PlutusWitnesses::new(); - self.input_types.scripts.iter().for_each(|(hash, option)| { - if let Some(ScriptWitnessType::PlutusScriptWitness(s)) = option { - if let Some(idx) = script_hash_index_map.get(&hash) { - scripts.add(&s.clone_with_redeemer_index(&idx)); - } - } - }); - if scripts.len() > 0 { Some(scripts) } else { None } + self.inputs.get_plutus_input_scripts() } /// calculates how much the fee would increase if you added a given output @@ -937,14 +735,14 @@ impl TransactionBuilder { pub fn set_certs(&mut self, certs: &Certificates) { self.certs = Some(certs.clone()); for cert in &certs.0 { - witness_keys_for_cert(cert, &mut self.input_types.vkeys); + self.inputs.add_required_signers(&witness_keys_for_cert(cert)) }; } pub fn set_withdrawals(&mut self, withdrawals: &Withdrawals) { self.withdrawals = Some(withdrawals.clone()); for (withdrawal, _coin) in &withdrawals.0 { - self.input_types.vkeys.insert(withdrawal.payment_cred().to_keyhash().unwrap().clone()); + self.inputs.add_required_signer(&withdrawal.payment_cred().to_keyhash().unwrap()) }; } @@ -1120,18 +918,14 @@ impl TransactionBuilder { pub fn new(cfg: &TransactionBuilderConfig) -> Self { Self { config: cfg.clone(), - inputs: Vec::new(), + inputs: TxInputsBuilder::new(), + collateral: TxInputsBuilder::new(), outputs: TransactionOutputs::new(), fee: None, ttl: None, certs: None, withdrawals: None, auxiliary_data: None, - input_types: MockWitnessSet { - vkeys: BTreeSet::new(), - scripts: LinkedHashMap::new(), - bootstraps: BTreeSet::new(), - }, validity_start_interval: None, mint: None, mint_scripts: None, @@ -1143,7 +937,7 @@ impl TransactionBuilder { pub fn get_explicit_input(&self) -> Result { self.inputs .iter() - .try_fold(Value::zero(), |acc, (ref tx_builder_input, _)| { + .try_fold(Value::zero(), |acc, ref tx_builder_input| { acc.checked_add(&tx_builder_input.amount) }) } @@ -1486,10 +1280,8 @@ impl TransactionBuilder { fn build_and_size(&self) -> Result<(TransactionBody, usize), JsError> { let fee = self.fee.ok_or_else(|| JsError::from_str("Fee not specified"))?; - let inputs = self.inputs.iter() - .map(|(ref tx_builder_input, _)| tx_builder_input.input.clone()).collect(); let built = TransactionBody { - inputs: TransactionInputs(inputs), + inputs: self.inputs.inputs(), outputs: self.outputs.clone(), fee, ttl: self.ttl, @@ -1503,7 +1295,7 @@ impl TransactionBuilder { validity_start_interval: self.validity_start_interval, mint: self.mint.clone(), script_data_hash: self.script_data_hash.clone(), - collateral: None, + collateral: self.collateral.inputs_option(), required_signers: None, network_id: None, }; @@ -1609,7 +1401,7 @@ mod tests { use super::*; use fees::*; use crate::tx_builder_constants::TxBuilderConstants; - use super::output_builder::{TransactionOutputBuilder}; + use super::output_builder::TransactionOutputBuilder; const MAX_VALUE_SIZE: u32 = 4000; const MAX_TX_SIZE: u32 = 8000; // might be out of date but suffices for our tests diff --git a/rust/src/tx_builder/tx_inputs_builder.rs b/rust/src/tx_builder/tx_inputs_builder.rs new file mode 100644 index 00000000..4441d200 --- /dev/null +++ b/rust/src/tx_builder/tx_inputs_builder.rs @@ -0,0 +1,363 @@ +use itertools::Itertools; +use super::*; + +#[derive(Clone, Debug)] +pub(crate) struct TxBuilderInput { + pub(crate) input: TransactionInput, + pub(crate) amount: Value, // we need to keep track of the amount in the inputs for input selection +} + +#[wasm_bindgen] +#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] +pub struct PlutusWitness { + script: PlutusScript, + datum: PlutusData, + redeemer: Redeemer, +} + +#[wasm_bindgen] +impl PlutusWitness { + + pub fn new(script: &PlutusScript, datum: &PlutusData, redeemer: &Redeemer) -> Self { + Self { + script: script.clone(), + datum: datum.clone(), + redeemer: redeemer.clone(), + } + } + + pub fn script(&self) -> PlutusScript { + self.script.clone() + } + + pub fn datum(&self) -> PlutusData { + self.datum.clone() + } + + pub fn redeemer(&self) -> Redeemer { + self.redeemer.clone() + } + + fn clone_with_redeemer_index(&self, index: &BigNum) -> Self { + Self { + script: self.script.clone(), + datum: self.datum.clone(), + redeemer: self.redeemer.clone_with_index(index), + } + } +} + + +#[wasm_bindgen] +#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] +pub struct PlutusWitnesses(Vec); + +#[wasm_bindgen] +impl PlutusWitnesses { + pub fn new() -> Self { + Self(Vec::new()) + } + + pub fn len(&self) -> usize { + self.0.len() + } + + pub fn get(&self, index: usize) -> PlutusWitness { + self.0[index].clone() + } + + pub fn add(&mut self, elem: &PlutusWitness) { + self.0.push(elem.clone()); + } + + pub(crate) fn collect(&self) -> (PlutusScripts, PlutusList, Redeemers) { + let mut s = PlutusScripts::new(); + let mut d = PlutusList::new(); + let mut r = Redeemers::new(); + self.0.iter().for_each(|w| { + s.add(&w.script); + d.add(&w.datum); + r.add(&w.redeemer); + }); + (s, d, r) + } +} + +impl From> for PlutusWitnesses { + fn from(scripts: Vec) -> Self { + scripts.iter().fold(PlutusWitnesses::new(), |mut scripts, s| { + scripts.add(s); + scripts + }) + } +} + +#[derive(Clone, Debug)] +pub enum ScriptWitnessType { + NativeScriptWitness(NativeScript), + PlutusScriptWitness(PlutusWitness), +} + +// We need to know how many of each type of witness will be in the transaction so we can calculate the tx fee +#[derive(Clone, Debug)] +pub struct MockWitnessSet { + vkeys: RequiredSignersSet, + scripts: LinkedHashMap>, + bootstraps: BTreeSet>, +} + +#[wasm_bindgen] +#[derive(Clone, Debug)] +pub struct TxInputsBuilder { + inputs: Vec<(TxBuilderInput, Option)>, + input_types: MockWitnessSet, +} + +#[wasm_bindgen] +impl TxInputsBuilder { + + pub fn new() -> Self { + Self { + inputs: Vec::new(), + input_types: MockWitnessSet { + vkeys: BTreeSet::new(), + scripts: LinkedHashMap::new(), + bootstraps: BTreeSet::new(), + }, + } + } + + /// We have to know what kind of inputs these are to know what kind of mock witnesses to create since + /// 1) mock witnesses have different lengths depending on the type which changes the expecting fee + /// 2) Witnesses are a set so we need to get rid of duplicates to avoid over-estimating the fee + pub fn add_key_input(&mut self, hash: &Ed25519KeyHash, input: &TransactionInput, amount: &Value) { + let inp = TxBuilderInput { + input: input.clone(), + amount: amount.clone(), + }; + self.inputs.push((inp, None)); + self.input_types.vkeys.insert(hash.clone()); + } + + /// This method adds the input to the builder BUT leaves a missing spot for the witness native script + /// + /// After adding the input with this method, use `.add_required_native_input_scripts` + /// and `.add_required_plutus_input_scripts` to add the witness scripts + /// + /// Or instead use `.add_native_script_input` and `.add_plutus_script_input` + /// to add inputs right along with the script, instead of the script hash + pub fn add_script_input(&mut self, hash: &ScriptHash, input: &TransactionInput, amount: &Value) { + let inp = TxBuilderInput { + input: input.clone(), + amount: amount.clone(), + }; + self.inputs.push((inp, Some(hash.clone()))); + if !self.input_types.scripts.contains_key(hash) { + self.input_types.scripts.insert(hash.clone(), None); + } + } + + /// This method will add the input to the builder and also register the required native script witness + pub fn add_native_script_input(&mut self, script: &NativeScript, input: &TransactionInput, amount: &Value) { + let hash = script.hash(); + self.add_script_input(&hash, input, amount); + self.input_types.scripts.insert( + hash, + Some(ScriptWitnessType::NativeScriptWitness(script.clone())), + ); + } + + /// This method will add the input to the builder and also register the required plutus witness + pub fn add_plutus_script_input(&mut self, witness: &PlutusWitness, input: &TransactionInput, amount: &Value) { + let hash = witness.script.hash(); + self.add_script_input(&hash, input, amount); + self.input_types.scripts.insert( + hash, + Some(ScriptWitnessType::PlutusScriptWitness(witness.clone())), + ); + } + + pub fn add_bootstrap_input(&mut self, hash: &ByronAddress, input: &TransactionInput, amount: &Value) { + let inp = TxBuilderInput { + input: input.clone(), + amount: amount.clone(), + }; + self.inputs.push((inp, None)); + self.input_types.bootstraps.insert(hash.to_bytes()); + } + + /// Note that for script inputs this method will use underlying generic `.add_script_input` + /// which leaves a required empty spot for the script witness (or witnesses in case of Plutus). + /// You can use `.add_native_script_input` or `.add_plutus_script_input` directly to register the input along with the witness. + pub fn add_input(&mut self, address: &Address, input: &TransactionInput, amount: &Value) { + match &BaseAddress::from_address(address) { + Some(addr) => { + match &addr.payment_cred().to_keyhash() { + Some(hash) => return self.add_key_input(hash, input, amount), + None => (), + } + match &addr.payment_cred().to_scripthash() { + Some(hash) => return self.add_script_input(hash, input, amount), + None => (), + } + }, + None => (), + } + match &EnterpriseAddress::from_address(address) { + Some(addr) => { + match &addr.payment_cred().to_keyhash() { + Some(hash) => return self.add_key_input(hash, input, amount), + None => (), + } + match &addr.payment_cred().to_scripthash() { + Some(hash) => return self.add_script_input(hash, input, amount), + None => (), + } + }, + None => (), + } + match &PointerAddress::from_address(address) { + Some(addr) => { + match &addr.payment_cred().to_keyhash() { + Some(hash) => return self.add_key_input(hash, input, amount), + None => (), + } + match &addr.payment_cred().to_scripthash() { + Some(hash) => return self.add_script_input(hash, input, amount), + None => (), + } + }, + None => (), + } + match &ByronAddress::from_address(address) { + Some(addr) => { + return self.add_bootstrap_input(addr, input, amount); + }, + None => (), + } + } + + /// Returns the number of still missing input scripts (either native or plutus) + /// Use `.add_required_native_input_scripts` or `.add_required_plutus_input_scripts` to add the missing scripts + pub fn count_missing_input_scripts(&self) -> usize { + self.input_types.scripts.values().filter(|s| { s.is_none() }).count() + } + + /// Try adding the specified scripts as witnesses for ALREADY ADDED script inputs + /// Any scripts that don't match any of the previously added inputs will be ignored + /// Returns the number of remaining required missing witness scripts + /// Use `.count_missing_input_scripts` to find the number of still missing scripts + pub fn add_required_native_input_scripts(&mut self, scripts: &NativeScripts) -> usize { + scripts.0.iter().for_each(|s: &NativeScript| { + let hash = s.hash(); + if self.input_types.scripts.contains_key(&hash) { + self.input_types.scripts.insert( + hash, + Some(ScriptWitnessType::NativeScriptWitness(s.clone())), + ); + } + }); + self.count_missing_input_scripts() + } + + /// Try adding the specified scripts as witnesses for ALREADY ADDED script inputs + /// Any scripts that don't match any of the previously added inputs will be ignored + /// Returns the number of remaining required missing witness scripts + /// Use `.count_missing_input_scripts` to find the number of still missing scripts + pub fn add_required_plutus_input_scripts(&mut self, scripts: &PlutusWitnesses) -> usize { + scripts.0.iter().for_each(|s: &PlutusWitness| { + let hash = s.script.hash(); + if self.input_types.scripts.contains_key(&hash) { + self.input_types.scripts.insert( + hash, + Some(ScriptWitnessType::PlutusScriptWitness(s.clone())), + ); + } + }); + self.count_missing_input_scripts() + } + + /// Returns a copy of the current script input witness scripts in the builder + pub fn get_native_input_scripts(&self) -> Option { + let mut scripts = NativeScripts::new(); + self.input_types.scripts.values().for_each(|option| { + if let Some(ScriptWitnessType::NativeScriptWitness(s)) = option { + scripts.add(&s); + } + }); + if scripts.len() > 0 { Some(scripts) } else { None } + } + + /// Returns a copy of the current plutus input witness scripts in the builder. + /// NOTE: each plutus witness will be cloned with a specific corresponding input index + pub fn get_plutus_input_scripts(&self) -> Option { + /* + * === EXPLANATION === + * The `Redeemer` object contains the `.index` field which is supposed to point + * exactly to the index of the corresponding input in the inputs array. We want to + * simplify and automate this as much as possible for the user to not have to care about it. + * + * For this we store the script hash along with the input, when it was registered, and + * now we create a map of script hashes to their input indexes. + * + * The registered witnesses are then each cloned with the new correct redeemer input index. + */ + let script_hash_index_map: BTreeMap<&ScriptHash, BigNum> = self.inputs.iter().enumerate() + .fold(BTreeMap::new(), |mut m, (i, (_, hash_option))| { + if let Some(hash) = hash_option { + m.insert(hash, to_bignum(i as u64)); + } + m + }); + let mut scripts = PlutusWitnesses::new(); + self.input_types.scripts.iter().for_each(|(hash, option)| { + if let Some(ScriptWitnessType::PlutusScriptWitness(s)) = option { + if let Some(idx) = script_hash_index_map.get(&hash) { + scripts.add(&s.clone_with_redeemer_index(&idx)); + } + } + }); + if scripts.len() > 0 { Some(scripts) } else { None } + } + + pub(crate) fn iter(&self) -> impl std::iter::Iterator + '_ { + self.inputs.iter().map(|(i, _)| i) + } + + pub fn len(&self) -> usize { + self.inputs.len() + } + + pub fn required_signers(&self) -> RequiredSignersSet { + let mut set = self.input_types.vkeys.clone(); + self.input_types.scripts.values().for_each(|swt: &Option| { + if let Some(ScriptWitnessType::NativeScriptWitness(s)) = swt { + RequiredSignersSet::from(s).iter().for_each(|k| { set.insert(k.clone()); }); + } + }); + set + } + + pub fn add_required_signer(&mut self, key: &Ed25519KeyHash) { + self.input_types.vkeys.insert(key.clone()); + } + + pub fn add_required_signers(&mut self, keys: &RequiredSignersSet) { + keys.iter().for_each(|k| self.add_required_signer(k)); + } + + pub(crate) fn bootstraps(&self) -> BTreeSet> { + self.input_types.bootstraps.clone() + } + + pub fn inputs(&self) -> TransactionInputs { + TransactionInputs( + self.inputs.iter() + .map(|(ref tx_builder_input, _)| tx_builder_input.input.clone()).collect() + ) + } + + pub fn inputs_option(&self) -> Option { + if self.len() > 0 { Some(self.inputs()) } else { None } + } +} From 517481f982eeb35bfe4a23f42e5465fabf9494fb Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Thu, 26 May 2022 17:09:59 +0300 Subject: [PATCH 02/22] Added collateral assertion on `.build_tx` --- rust/src/tx_builder.rs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/rust/src/tx_builder.rs b/rust/src/tx_builder.rs index 582e1ba8..4566e9ec 100644 --- a/rust/src/tx_builder.rs +++ b/rust/src/tx_builder.rs @@ -1368,10 +1368,17 @@ impl TransactionBuilder { "There are some script inputs added that don't have the corresponding script provided as a witness!", )); } - if self.script_data_hash.is_none() && self.get_plutus_input_scripts().is_some() { - return Err(JsError::from_str( - "Plutus inputs are present, but script data hash is not specified", - )); + if self.get_plutus_input_scripts().is_some() { + if self.script_data_hash.is_none() { + return Err(JsError::from_str( + "Plutus inputs are present, but script data hash is not specified", + )); + } + if self.collateral.len() == 0 { + return Err(JsError::from_str( + "Plutus inputs are present, but no collateral inputs are added", + )); + } } self.build_tx_unsafe() } From 5b08d2b8441addad0493644c7c787f1ac0b1a2c0 Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Thu, 26 May 2022 17:25:27 +0300 Subject: [PATCH 03/22] Fixing tests --- rust/src/tx_builder.rs | 33 ++++++++++++++++++++++-- rust/src/tx_builder/tx_inputs_builder.rs | 2 +- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/rust/src/tx_builder.rs b/rust/src/tx_builder.rs index 4566e9ec..a69f2d5a 100644 --- a/rust/src/tx_builder.rs +++ b/rust/src/tx_builder.rs @@ -115,7 +115,7 @@ fn fake_full_tx(tx_builder: &TransactionBuilder, body: TransactionBody) -> Resul }, }; let (plutus_scripts, plutus_data, redeemers) = { - if let Some(s) = tx_builder.get_plutus_input_scripts() { + if let Some(s) = tx_builder.get_combined_plutus_scripts() { let (s, d, r) = s.collect(); (Some(s), Some(d), Some(r)) } else { @@ -1331,7 +1331,10 @@ impl TransactionBuilder { fn get_combined_native_scripts(&self) -> Option { let mut ns = NativeScripts::new(); - if let Some(input_scripts) = self.get_native_input_scripts() { + if let Some(input_scripts) = self.inputs.get_native_input_scripts() { + input_scripts.0.iter().for_each(|s| { ns.add(s); }); + } + if let Some(input_scripts) = self.collateral.get_native_input_scripts() { input_scripts.0.iter().for_each(|s| { ns.add(s); }); } if let Some(mint_scripts) = &self.mint_scripts { @@ -1340,6 +1343,17 @@ impl TransactionBuilder { if ns.len() > 0 { Some(ns) } else { None } } + fn get_combined_plutus_scripts(&self) -> Option { + let mut res = PlutusWitnesses::new(); + if let Some(scripts) = self.inputs.get_plutus_input_scripts() { + scripts.0.iter().for_each(|s| { res.add(s); }) + } + if let Some(scripts) = self.collateral.get_plutus_input_scripts() { + scripts.0.iter().for_each(|s| { res.add(s); }) + } + if res.len() > 0 { Some(res) } else { None } + } + // This function should be producing the total witness-set // that is created by the tx-builder itself, // before the transaction is getting signed by the actual wallet. @@ -4598,10 +4612,21 @@ mod tests { assert_eq!(redeems.get(1), redeemer2); } + fn create_collateral() -> TxInputsBuilder { + let mut collateral_builder = TxInputsBuilder::new(); + collateral_builder.add_input( + &byron_address(), + &TransactionInput::new(&genesis_id(), 0), + &Value::new(&to_bignum(1_000_000)), + ); + collateral_builder + } + #[test] fn test_existing_plutus_scripts_require_data_hash() { let mut tx_builder = create_reallistic_tx_builder(); tx_builder.set_fee(&to_bignum(42)); + tx_builder.set_collateral(&create_collateral()); let (script1, _) = plutus_script_and_hash(0); let datum = PlutusData::new_bytes(fake_bytes(1)); let redeemer_datum = PlutusData::new_bytes(fake_bytes(2)); @@ -4643,6 +4668,8 @@ mod tests { fn test_calc_script_hash_data() { let mut tx_builder = create_reallistic_tx_builder(); tx_builder.set_fee(&to_bignum(42)); + tx_builder.set_collateral(&create_collateral()); + let (script1, _) = plutus_script_and_hash(0); let datum = PlutusData::new_bytes(fake_bytes(1)); let redeemer_datum = PlutusData::new_bytes(fake_bytes(2)); @@ -4679,6 +4706,7 @@ mod tests { fn test_plutus_witness_redeemer_index_auto_changing() { let mut tx_builder = create_reallistic_tx_builder(); tx_builder.set_fee(&to_bignum(42)); + tx_builder.set_collateral(&create_collateral()); let (script1, _) = plutus_script_and_hash(0); let (script2, _) = plutus_script_and_hash(1); let datum1 = PlutusData::new_bytes(fake_bytes(10)); @@ -4748,6 +4776,7 @@ mod tests { fn test_native_and_plutus_scripts_together() { let mut tx_builder = create_reallistic_tx_builder(); tx_builder.set_fee(&to_bignum(42)); + tx_builder.set_collateral(&create_collateral()); let (pscript1, _) = plutus_script_and_hash(0); let (pscript2, phash2) = plutus_script_and_hash(1); let (nscript1, _) = mint_script_and_policy(0); diff --git a/rust/src/tx_builder/tx_inputs_builder.rs b/rust/src/tx_builder/tx_inputs_builder.rs index 4441d200..cbaf41c2 100644 --- a/rust/src/tx_builder/tx_inputs_builder.rs +++ b/rust/src/tx_builder/tx_inputs_builder.rs @@ -50,7 +50,7 @@ impl PlutusWitness { #[wasm_bindgen] #[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] -pub struct PlutusWitnesses(Vec); +pub struct PlutusWitnesses(pub(crate) Vec); #[wasm_bindgen] impl PlutusWitnesses { From 86e1cfa68ebe21e437045e0176691a289121cf6c Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Thu, 26 May 2022 19:29:42 +0300 Subject: [PATCH 04/22] test --- rust/src/lib.rs | 1 + rust/src/tx_builder.rs | 156 ++++++++++++++++++++++- rust/src/tx_builder/tx_inputs_builder.rs | 1 - 3 files changed, 154 insertions(+), 4 deletions(-) diff --git a/rust/src/lib.rs b/rust/src/lib.rs index cb9475d2..2e9ddfbc 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -1,4 +1,5 @@ #![cfg_attr(feature = "with-bench", feature(test))] +#![allow(deprecated)] #[macro_use] extern crate cfg_if; diff --git a/rust/src/tx_builder.rs b/rust/src/tx_builder.rs index a69f2d5a..50b3245f 100644 --- a/rust/src/tx_builder.rs +++ b/rust/src/tx_builder.rs @@ -1,3 +1,5 @@ +#![allow(deprecated)] + mod tx_inputs_builder; use super::*; @@ -6,8 +8,8 @@ use super::utils; use super::output_builder::TransactionOutputAmountBuilder; use std::collections::{BTreeMap, BTreeSet, HashSet}; use linked_hash_map::LinkedHashMap; -use tx_inputs_builder::{MockWitnessSet, PlutusWitness, PlutusWitnesses, ScriptWitnessType}; -use crate::tx_builder::tx_inputs_builder::{TxBuilderInput, TxInputsBuilder}; +use tx_inputs_builder::{PlutusWitness, PlutusWitnesses}; +use crate::tx_builder::tx_inputs_builder::TxInputsBuilder; // comes from witsVKeyNeeded in the Ledger spec fn witness_keys_for_cert(cert_enum: &Certificate) -> RequiredSignersSet { @@ -1363,7 +1365,7 @@ impl TransactionBuilder { if let Some(scripts) = self.get_combined_native_scripts() { wit.set_native_scripts(&scripts); } - if let Some(pw) = self.get_plutus_input_scripts() { + if let Some(pw) = self.get_combined_plutus_scripts() { let (scripts, datums, redeemers) = pw.collect(); wit.set_plutus_scripts(&scripts); wit.set_plutus_data(&datums); @@ -4328,6 +4330,14 @@ mod tests { assert_eq!(ma2_output.get(&policy_id2).unwrap().get(&name).unwrap(), to_bignum(40)); } + fn create_base_address(x: u8) -> Address { + BaseAddress::new( + NetworkInfo::testnet().network_id(), + &StakeCredential::from_keyhash(&fake_key_hash(x)), + &StakeCredential::from_keyhash(&fake_key_hash(0)) + ).to_address() + } + fn create_base_address_from_script_hash(sh: &ScriptHash) -> Address { BaseAddress::new( NetworkInfo::testnet().network_id(), @@ -4878,5 +4888,145 @@ mod tests { // because it was added on the third position assert_eq!(redeems.get(1), redeemer2.clone_with_index(&to_bignum(2))); } + + #[test] + fn test_regular_and_collateral_inputs_same_keyhash() { + + let mut input_builder = TxInputsBuilder::new(); + let mut collateral_builder = TxInputsBuilder::new(); + + // Add a single input of both kinds with the SAME keyhash + input_builder.add_input( + &create_base_address(0), + &TransactionInput::new(&genesis_id(), 0), + &Value::new(&to_bignum(1_000_000)), + ); + collateral_builder.add_input( + &create_base_address(0), + &TransactionInput::new(&genesis_id(), 0), + &Value::new(&to_bignum(1_000_000)), + ); + + fn get_fake_vkeys_count(i: &TxInputsBuilder, c: &TxInputsBuilder) -> usize { + let mut tx_builder = create_reallistic_tx_builder(); + tx_builder.set_fee(&to_bignum(42)); + tx_builder.set_inputs(i); + tx_builder.set_collateral(c); + let tx: Transaction = fake_full_tx(&tx_builder, tx_builder.build().unwrap()).unwrap(); + tx.witness_set.vkeys.unwrap().len() + } + + // There's only one fake witness in the builder + // because a regular and a collateral inputs both use the same keyhash + assert_eq!(get_fake_vkeys_count(&input_builder, &collateral_builder), 1); + + // Add a new input of each kind with DIFFERENT keyhashes + input_builder.add_input( + &create_base_address(1), + &TransactionInput::new(&genesis_id(), 0), + &Value::new(&to_bignum(1_000_000)), + ); + collateral_builder.add_input( + &create_base_address(2), + &TransactionInput::new(&genesis_id(), 0), + &Value::new(&to_bignum(1_000_000)), + ); + + + // There are now three fake witnesses in the builder + // because all three unique keyhashes got combined + assert_eq!(get_fake_vkeys_count(&input_builder, &collateral_builder), 3); + } + + #[test] + fn test_regular_and_collateral_inputs_together() { + let mut tx_builder = create_reallistic_tx_builder(); + tx_builder.set_fee(&to_bignum(42)); + let (pscript1, _) = plutus_script_and_hash(0); + let (pscript2, _) = plutus_script_and_hash(1); + let (nscript1, _) = mint_script_and_policy(0); + let (nscript2, _) = mint_script_and_policy(1); + let datum1 = PlutusData::new_bytes(fake_bytes(10)); + let datum2 = PlutusData::new_bytes(fake_bytes(11)); + // Creating redeemers with indexes ZERO + let redeemer1 = Redeemer::new( + &RedeemerTag::new_spend(), + &to_bignum(0), + &PlutusData::new_bytes(fake_bytes(20)), + &ExUnits::new(&to_bignum(1), &to_bignum(2)), + ); + let redeemer2 = Redeemer::new( + &RedeemerTag::new_spend(), + &to_bignum(0), + &PlutusData::new_bytes(fake_bytes(21)), + &ExUnits::new(&to_bignum(1), &to_bignum(2)), + ); + + let mut input_builder = TxInputsBuilder::new(); + let mut collateral_builder = TxInputsBuilder::new(); + + input_builder.add_native_script_input( + &nscript1, + &TransactionInput::new(&genesis_id(), 0), + &Value::new(&to_bignum(1_000_000)), + ); + collateral_builder.add_native_script_input( + &nscript2, + &TransactionInput::new(&genesis_id(), 0), + &Value::new(&to_bignum(1_000_000)), + ); + + input_builder.add_plutus_script_input( + &PlutusWitness::new( + &pscript1, + &datum1, + &redeemer1, + ), + &TransactionInput::new(&genesis_id(), 0), + &Value::new(&to_bignum(1_000_000)), + ); + collateral_builder.add_plutus_script_input( + &PlutusWitness::new( + &pscript2, + &datum2, + &redeemer2, + ), + &TransactionInput::new(&genesis_id(), 0), + &Value::new(&to_bignum(1_000_000)), + ); + + tx_builder.set_inputs(&input_builder); + tx_builder.set_collateral(&collateral_builder); + + tx_builder.calc_script_data_hash( + &TxBuilderConstants::plutus_default_cost_models(), + ); + + let w: &TransactionWitnessSet = &tx_builder.build_tx().unwrap().witness_set; + + assert!(w.native_scripts.is_some()); + let nscripts = w.native_scripts.as_ref().unwrap(); + assert_eq!(nscripts.len(), 2); + assert_eq!(nscripts.get(0), nscript1); + assert_eq!(nscripts.get(1), nscript2); + + assert!(w.plutus_scripts.is_some()); + let pscripts = w.plutus_scripts.as_ref().unwrap(); + assert_eq!(pscripts.len(), 2); + assert_eq!(pscripts.get(0), pscript1); + assert_eq!(pscripts.get(1), pscript2); + + assert!(w.plutus_data.is_some()); + let datums = w.plutus_data.as_ref().unwrap(); + assert_eq!(datums.len(), 2); + assert_eq!(datums.get(0), datum1); + assert_eq!(datums.get(1), datum2); + + assert!(w.redeemers.is_some()); + let redeemers = w.redeemers.as_ref().unwrap(); + assert_eq!(redeemers.len(), 2); + assert_eq!(redeemers.get(0), redeemer1.clone_with_index(&to_bignum(1))); + assert_eq!(redeemers.get(1), redeemer2.clone_with_index(&to_bignum(1))); + } } diff --git a/rust/src/tx_builder/tx_inputs_builder.rs b/rust/src/tx_builder/tx_inputs_builder.rs index cbaf41c2..8f0ba707 100644 --- a/rust/src/tx_builder/tx_inputs_builder.rs +++ b/rust/src/tx_builder/tx_inputs_builder.rs @@ -1,4 +1,3 @@ -use itertools::Itertools; use super::*; #[derive(Clone, Debug)] From 05c1a023cc6c9145a7869ea467d13009638cf478 Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Tue, 31 May 2022 13:25:22 +0300 Subject: [PATCH 05/22] Added `ex_unit_prices` in the tx_builder config --- rust/src/tx_builder.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/rust/src/tx_builder.rs b/rust/src/tx_builder.rs index 50b3245f..ff662fe2 100644 --- a/rust/src/tx_builder.rs +++ b/rust/src/tx_builder.rs @@ -195,6 +195,7 @@ pub struct TransactionBuilderConfig { max_value_size: u32, // protocol parameter max_tx_size: u32, // protocol parameter coins_per_utxo_word: Coin, // protocol parameter + ex_unit_prices: Option, // protocol parameter prefer_pure_change: bool, } @@ -207,6 +208,7 @@ pub struct TransactionBuilderConfigBuilder { max_value_size: Option, // protocol parameter max_tx_size: Option, // protocol parameter coins_per_utxo_word: Option, // protocol parameter + ex_unit_prices: Option, // protocol parameter prefer_pure_change: bool, } @@ -220,6 +222,7 @@ impl TransactionBuilderConfigBuilder { max_value_size: None, max_tx_size: None, coins_per_utxo_word: None, + ex_unit_prices: None, prefer_pure_change: false, } } @@ -236,6 +239,12 @@ impl TransactionBuilderConfigBuilder { cfg } + pub fn ex_unit_prices(&self, ex_unit_prices: &ExUnitPrices) -> Self { + let mut cfg = self.clone(); + cfg.ex_unit_prices = Some(ex_unit_prices.clone()); + cfg + } + pub fn pool_deposit(&self, pool_deposit: &BigNum) -> Self { let mut cfg = self.clone(); cfg.pool_deposit = Some(pool_deposit.clone()); @@ -267,7 +276,7 @@ impl TransactionBuilderConfigBuilder { } pub fn build(&self) -> Result { - let cfg = self.clone(); + let cfg: Self = self.clone(); Ok(TransactionBuilderConfig { fee_algo: cfg.fee_algo.ok_or(JsError::from_str("uninitialized field: fee_algo"))?, pool_deposit: cfg.pool_deposit.ok_or(JsError::from_str("uninitialized field: pool_deposit"))?, @@ -275,6 +284,7 @@ impl TransactionBuilderConfigBuilder { max_value_size: cfg.max_value_size.ok_or(JsError::from_str("uninitialized field: max_value_size"))?, max_tx_size: cfg.max_tx_size.ok_or(JsError::from_str("uninitialized field: max_tx_size"))?, coins_per_utxo_word: cfg.coins_per_utxo_word.ok_or(JsError::from_str("uninitialized field: coins_per_utxo_word"))?, + ex_unit_prices: cfg.ex_unit_prices, prefer_pure_change: cfg.prefer_pure_change, }) } From 5280e442b4be0f1ab7935b77d34313e54f15bd71 Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Tue, 31 May 2022 13:52:04 +0300 Subject: [PATCH 06/22] Added `Redeemers.total_ex_units` --- rust/src/plutus.rs | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/rust/src/plutus.rs b/rust/src/plutus.rs index 601bccf3..89500d96 100644 --- a/rust/src/plutus.rs +++ b/rust/src/plutus.rs @@ -253,7 +253,6 @@ impl ExUnitPrices { #[wasm_bindgen] #[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] pub struct ExUnits { - // TODO: should these be u32 or BigNum? mem: BigNum, steps: BigNum, } @@ -619,6 +618,17 @@ impl Redeemers { pub fn add(&mut self, elem: &Redeemer) { self.0.push(elem.clone()); } + + pub fn total_ex_units(&self) -> Result { + let mut tot_mem = BigNum::zero(); + let mut tot_steps = BigNum::zero(); + for i in 0..self.0.len() { + let r: &Redeemer = &self.0[i]; + tot_mem = tot_mem.checked_add(&r.ex_units().mem())?; + tot_steps = tot_steps.checked_add(&r.ex_units().steps())?; + } + Ok(ExUnits::new(&tot_mem, &tot_steps)) + } } impl From> for Redeemers { @@ -1363,4 +1373,30 @@ mod tests { ).unwrap(); assert_eq!(script.hash(), hash); } + + fn redeemer_with_ex_units(mem: &BigNum, steps: &BigNum) -> Redeemer { + Redeemer::new( + &RedeemerTag::new_spend(), + &BigNum::zero(), + &PlutusData::new_integer(&BigInt::from_str("0").unwrap()), + &ExUnits::new(mem, steps), + ) + } + + #[test] + fn test_total_ex_units() { + let mut r = Redeemers::new(); + + fn assert_ex_units(eu: &ExUnits, exp_mem: u64, exp_steps: u64) { + assert_eq!(eu.mem, to_bignum(exp_mem)); + assert_eq!(eu.steps, to_bignum(exp_steps)); + } + + r.add(&redeemer_with_ex_units(&to_bignum(10), &to_bignum(100))); + assert_ex_units(&r.total_ex_units().unwrap(), 10, 100); + r.add(&redeemer_with_ex_units(&to_bignum(20), &to_bignum(200))); + assert_ex_units(&r.total_ex_units().unwrap(), 30, 300); + r.add(&redeemer_with_ex_units(&to_bignum(30), &to_bignum(300))); + assert_ex_units(&r.total_ex_units().unwrap(), 60, 600); + } } From ea4eaf0ea776ed572a03e77cf3040ab633a92947 Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Tue, 31 May 2022 14:12:04 +0300 Subject: [PATCH 07/22] Added `BigInt.div_ceil` --- rust/Cargo.lock | 5 +-- rust/Cargo.toml | 1 + rust/src/utils.rs | 90 +++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 92 insertions(+), 4 deletions(-) diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 847ce662..78000138 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -68,6 +68,7 @@ dependencies = [ "linked-hash-map", "noop_proc_macro", "num-bigint", + "num-integer", "quickcheck", "quickcheck_macros", "rand 0.8.4", @@ -288,9 +289,9 @@ dependencies = [ [[package]] name = "num-integer" -version = "0.1.44" +version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" dependencies = [ "autocfg 1.0.1", "num-traits", diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 970c93ac..c4c4673d 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -27,6 +27,7 @@ cfg-if = "1" linked-hash-map = "0.5.3" serde_json = "1.0.57" num-bigint = "0.4.0" +num-integer = "0.1.45" # The default can't be compiled to wasm, so it's necessary to use either the 'nightly' # feature or this one clear_on_drop = { version = "0.2", features = ["no_cc"] } diff --git a/rust/src/utils.rs b/rust/src/utils.rs index 1ea74e8a..c502bcb0 100644 --- a/rust/src/utils.rs +++ b/rust/src/utils.rs @@ -275,6 +275,11 @@ pub fn from_bignum(val: &BigNum) -> u64 { val.0 } + +pub fn to_bigint(val: u64) -> BigInt { + BigInt::from_str(&val.to_string()).unwrap() +} + // Specifies an amount of ADA in terms of lovelace pub type Coin = BigNum; @@ -724,16 +729,41 @@ impl BigInt { } } - pub fn from_str(text: &str) -> Result { + pub fn from_str(text: &str) -> Result { use std::str::FromStr; num_bigint::BigInt::from_str(text) .map_err(|e| JsError::from_str(&format! {"{:?}", e})) - .map(BigInt) + .map(Self) } pub fn to_str(&self) -> String { self.0.to_string() } + + pub fn add(&self, other: &Self) -> Self { + Self(&self.0 + &other.0) + } + + pub fn mul(&self, other: &Self) -> Self { + Self(&self.0 * &other.0) + } + + pub fn one() -> Self { + use std::str::FromStr; + Self(num_bigint::BigInt::from_str("1").unwrap()) + } + + pub fn increment(&self) -> Self { + self.add(&Self::one()) + } + + pub fn div_ceil(&self, other: &Self) -> Self { + use num_integer::Integer; + let (res, rem) = self.0.div_rem(&other.0); + let result = Self(res); + let no_remainder = rem.sign() == num_bigint::Sign::NoSign; + if no_remainder { result } else { result.increment() } + } } impl cbor_event::se::Serialize for BigInt { @@ -2366,4 +2396,60 @@ mod tests { assert_eq!(y.to_str(), "-18446744073709551616"); assert_eq!(bytes_y, y.to_bytes()); } + + #[test] + fn test_bigint_add() { + assert_eq!( + to_bigint(10).add(&to_bigint(20)), + to_bigint(30), + ); + assert_eq!( + to_bigint(500).add(&to_bigint(800)), + to_bigint(1300), + ); + } + + #[test] + fn test_bigint_mul() { + assert_eq!( + to_bigint(10).mul(&to_bigint(20)), + to_bigint(200), + ); + assert_eq!( + to_bigint(500).mul(&to_bigint(800)), + to_bigint(400000), + ); + assert_eq!( + to_bigint(12).mul(&to_bigint(22)), + to_bigint(264), + ); + } + + #[test] + fn test_bigint_div_ceil() { + assert_eq!( + to_bigint(20).div_ceil(&to_bigint(10)), + to_bigint(2), + ); + assert_eq!( + to_bigint(20).div_ceil(&to_bigint(2)), + to_bigint(10), + ); + assert_eq!( + to_bigint(21).div_ceil(&to_bigint(2)), + to_bigint(11), + ); + assert_eq!( + to_bigint(6).div_ceil(&to_bigint(3)), + to_bigint(2), + ); + assert_eq!( + to_bigint(5).div_ceil(&to_bigint(3)), + to_bigint(2), + ); + assert_eq!( + to_bigint(7).div_ceil(&to_bigint(3)), + to_bigint(3), + ); + } } From b49e053e3f3a1232de1c2ae26e4d8f035b854e04 Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Tue, 31 May 2022 14:33:45 +0300 Subject: [PATCH 08/22] Added `utils.calculate_ex_units_ceil_cost` and `utils.min_script_fee` functions --- rust/src/fees.rs | 89 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/rust/src/fees.rs b/rust/src/fees.rs index 41dd0a25..8142a027 100644 --- a/rust/src/fees.rs +++ b/rust/src/fees.rs @@ -33,6 +33,50 @@ pub fn min_fee(tx: &Transaction, linear_fee: &LinearFee) -> Result Result { + type Ratio = (BigInt, BigInt); + fn mult(sc: &SubCoin, x: &BigNum) -> Result { + let n: BigInt = BigInt::from_str(&sc.numerator.to_str())?; + let d: BigInt = BigInt::from_str(&sc.denominator.to_str())?; + let m: BigInt = BigInt::from_str(&x.to_str())?; + Ok((n.mul(&m), d)) + } + fn sum(a: &Ratio, b: &Ratio) -> Ratio { + // Ratio Addition: a/x + b/y = ((a*y) + (b*x))/(x*y) + let (a_num, a_denum) = &a; + let (b_num, b_denum) = &b; + let a_num_fixed = &a_num.mul(b_denum); + let b_num_fixed = &b_num.mul(a_denum); + let a_b_num_sum = a_num_fixed.add(b_num_fixed); + let common_denum = a_denum.mul(b_denum); + (a_b_num_sum, common_denum) + } + let mem_ratio: Ratio = mult(&ex_unit_prices.mem_price(), &ex_units.mem())?; + let steps_ratio: Ratio = mult(&ex_unit_prices.step_price(), &ex_units.steps())?; + let (total_num, total_denum) = sum(&mem_ratio, &steps_ratio); + match total_num.div_ceil(&total_denum).as_u64() { + Some(coin) => Ok(coin), + _ => Err(JsError::from_str(&format!( + "Failed to calculate ceil from ratio {}/{}", + total_num.to_str(), + total_denum.to_str(), + ))) + } +} + +#[wasm_bindgen] +pub fn min_script_fee(tx: &Transaction, ex_unit_prices: &ExUnitPrices) -> Result { + if let Some(redeemers) = &tx.witness_set.redeemers { + let total_ex_units: ExUnits = redeemers.total_ex_units()?; + return calculate_ex_units_ceil_cost(&total_ex_units, ex_unit_prices); + } + Ok(Coin::zero()) +} + #[cfg(test)] mod tests { use super::*; @@ -517,4 +561,49 @@ mod tests { "163002" // todo: compare to Haskell fee to make sure the diff is not too big ); } + + fn exunits(mem: u64, steps: u64) -> ExUnits { + ExUnits::new(&to_bignum(mem), &to_bignum(steps)) + } + + fn subcoin(num: u64, denum: u64) -> SubCoin { + SubCoin::new(&to_bignum(num), &to_bignum(denum)) + } + + fn exunit_prices(mem_prices: (u64, u64), step_prices: (u64, u64)) -> ExUnitPrices { + ExUnitPrices::new( + &subcoin(mem_prices.0, mem_prices.1), + &subcoin(step_prices.0, step_prices.1), + ) + } + + fn _calculate_ex_units_ceil_cost(mem: u64, steps: u64, mem_prices: (u64, u64), step_prices: (u64, u64)) -> Coin { + let ex_units = exunits(mem, steps); + let ex_unit_prices = exunit_prices(mem_prices, step_prices); + calculate_ex_units_ceil_cost(&ex_units, &ex_unit_prices).unwrap() + } + + #[test] + fn test_calc_ex_units_cost() { + // 10 * (2/1) + 20 * (3/1) = 10 * 2 + 20 * 3 = 20 + 60 + assert_eq!( + _calculate_ex_units_ceil_cost(10, 20, (2, 1), (3, 1)), + to_bignum(80), + ); + // 22 * (12/6) + 33 * (33/11) = 22 * 2 + 33 * 3 = 44 + 99 = 143 + assert_eq!( + _calculate_ex_units_ceil_cost(22, 33, (12, 6), (33, 11)), + to_bignum(143), + ); + // 10 * (5/7) + 20 * (9/13) = 50/7 + 180/13 = 650/91 + 1260/91 = 1910/91 = ceil(20.98) = 21 + assert_eq!( + _calculate_ex_units_ceil_cost(10, 20, (5, 7), (9, 13)), + to_bignum(21), + ); + // 22 * (7/5) + 33 * (13/9) = 154/5 + 429/9 = 1386/45 + 2145/45 = 3531/45 = ceil(78.46) = 79 + assert_eq!( + _calculate_ex_units_ceil_cost(22, 33, (7, 5), (13, 9)), + to_bignum(79), + ); + } } \ No newline at end of file From 12afb957d0a17d6e89f74c556968c3b9f094df00 Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Tue, 31 May 2022 14:50:02 +0300 Subject: [PATCH 09/22] Fixing types and updating flowgen --- rust/pkg/cardano_serialization_lib.js.flow | 221 +++++++++++++++++++++ rust/src/tx_builder.rs | 30 +-- rust/src/tx_builder/tx_inputs_builder.rs | 34 ++-- rust/src/utils.rs | 12 +- 4 files changed, 260 insertions(+), 37 deletions(-) diff --git a/rust/pkg/cardano_serialization_lib.js.flow b/rust/pkg/cardano_serialization_lib.js.flow index 9955fb1f..6bc82638 100644 --- a/rust/pkg/cardano_serialization_lib.js.flow +++ b/rust/pkg/cardano_serialization_lib.js.flow @@ -172,6 +172,26 @@ declare export function decode_metadatum_to_json_str( */ declare export function min_fee(tx: Transaction, linear_fee: LinearFee): BigNum; +/** + * @param {ExUnits} ex_units + * @param {ExUnitPrices} ex_unit_prices + * @returns {BigNum} + */ +declare export function calculate_ex_units_ceil_cost( + ex_units: ExUnits, + ex_unit_prices: ExUnitPrices +): BigNum; + +/** + * @param {Transaction} tx + * @param {ExUnitPrices} ex_unit_prices + * @returns {BigNum} + */ +declare export function min_script_fee( + tx: Transaction, + ex_unit_prices: ExUnitPrices +): BigNum; + /** * @param {string} password * @param {string} salt @@ -663,6 +683,34 @@ declare export class BigInt { * @returns {string} */ to_str(): string; + + /** + * @param {BigInt} other + * @returns {BigInt} + */ + add(other: BigInt): BigInt; + + /** + * @param {BigInt} other + * @returns {BigInt} + */ + mul(other: BigInt): BigInt; + + /** + * @returns {BigInt} + */ + static one(): BigInt; + + /** + * @returns {BigInt} + */ + increment(): BigInt; + + /** + * @param {BigInt} other + * @returns {BigInt} + */ + div_ceil(other: BigInt): BigInt; } /** */ @@ -4258,6 +4306,11 @@ declare export class Redeemers { * @param {Redeemer} elem */ add(elem: Redeemer): void; + + /** + * @returns {ExUnits} + */ + total_ex_units(): ExUnits; } /** */ @@ -5292,6 +5345,16 @@ declare export class TransactionBuilder { */ add_inputs_from(inputs: TransactionUnspentOutputs, strategy: number): void; + /** + * @param {TxInputsBuilder} inputs + */ + set_inputs(inputs: TxInputsBuilder): void; + + /** + * @param {TxInputsBuilder} collateral + */ + set_collateral(collateral: TxInputsBuilder): void; + /** * We have to know what kind of inputs these are to know what kind of mock witnesses to create since * 1) mock witnesses have different lengths depending on the type which changes the expecting fee @@ -5754,6 +5817,12 @@ declare export class TransactionBuilderConfigBuilder { coins_per_utxo_word: BigNum ): TransactionBuilderConfigBuilder; + /** + * @param {ExUnitPrices} ex_unit_prices + * @returns {TransactionBuilderConfigBuilder} + */ + ex_unit_prices(ex_unit_prices: ExUnitPrices): TransactionBuilderConfigBuilder; + /** * @param {BigNum} pool_deposit * @returns {TransactionBuilderConfigBuilder} @@ -6346,6 +6415,158 @@ declare export class TxBuilderConstants { */ static plutus_default_cost_models(): Costmdls; } +/** + */ +declare export class TxInputsBuilder { + free(): void; + + /** + * @returns {TxInputsBuilder} + */ + static new(): TxInputsBuilder; + + /** + * We have to know what kind of inputs these are to know what kind of mock witnesses to create since + * 1) mock witnesses have different lengths depending on the type which changes the expecting fee + * 2) Witnesses are a set so we need to get rid of duplicates to avoid over-estimating the fee + * @param {Ed25519KeyHash} hash + * @param {TransactionInput} input + * @param {Value} amount + */ + add_key_input( + hash: Ed25519KeyHash, + input: TransactionInput, + amount: Value + ): void; + + /** + * This method adds the input to the builder BUT leaves a missing spot for the witness native script + * + * After adding the input with this method, use `.add_required_native_input_scripts` + * and `.add_required_plutus_input_scripts` to add the witness scripts + * + * Or instead use `.add_native_script_input` and `.add_plutus_script_input` + * to add inputs right along with the script, instead of the script hash + * @param {ScriptHash} hash + * @param {TransactionInput} input + * @param {Value} amount + */ + add_script_input( + hash: ScriptHash, + input: TransactionInput, + amount: Value + ): void; + + /** + * This method will add the input to the builder and also register the required native script witness + * @param {NativeScript} script + * @param {TransactionInput} input + * @param {Value} amount + */ + add_native_script_input( + script: NativeScript, + input: TransactionInput, + amount: Value + ): void; + + /** + * This method will add the input to the builder and also register the required plutus witness + * @param {PlutusWitness} witness + * @param {TransactionInput} input + * @param {Value} amount + */ + add_plutus_script_input( + witness: PlutusWitness, + input: TransactionInput, + amount: Value + ): void; + + /** + * @param {ByronAddress} hash + * @param {TransactionInput} input + * @param {Value} amount + */ + add_bootstrap_input( + hash: ByronAddress, + input: TransactionInput, + amount: Value + ): void; + + /** + * Note that for script inputs this method will use underlying generic `.add_script_input` + * which leaves a required empty spot for the script witness (or witnesses in case of Plutus). + * You can use `.add_native_script_input` or `.add_plutus_script_input` directly to register the input along with the witness. + * @param {Address} address + * @param {TransactionInput} input + * @param {Value} amount + */ + add_input(address: Address, input: TransactionInput, amount: Value): void; + + /** + * Returns the number of still missing input scripts (either native or plutus) + * Use `.add_required_native_input_scripts` or `.add_required_plutus_input_scripts` to add the missing scripts + * @returns {number} + */ + count_missing_input_scripts(): number; + + /** + * Try adding the specified scripts as witnesses for ALREADY ADDED script inputs + * Any scripts that don't match any of the previously added inputs will be ignored + * Returns the number of remaining required missing witness scripts + * Use `.count_missing_input_scripts` to find the number of still missing scripts + * @param {NativeScripts} scripts + * @returns {number} + */ + add_required_native_input_scripts(scripts: NativeScripts): number; + + /** + * Try adding the specified scripts as witnesses for ALREADY ADDED script inputs + * Any scripts that don't match any of the previously added inputs will be ignored + * Returns the number of remaining required missing witness scripts + * Use `.count_missing_input_scripts` to find the number of still missing scripts + * @param {PlutusWitnesses} scripts + * @returns {number} + */ + add_required_plutus_input_scripts(scripts: PlutusWitnesses): number; + + /** + * Returns a copy of the current script input witness scripts in the builder + * @returns {NativeScripts | void} + */ + get_native_input_scripts(): NativeScripts | void; + + /** + * Returns a copy of the current plutus input witness scripts in the builder. + * NOTE: each plutus witness will be cloned with a specific corresponding input index + * @returns {PlutusWitnesses | void} + */ + get_plutus_input_scripts(): PlutusWitnesses | void; + + /** + * @returns {number} + */ + len(): number; + + /** + * @param {Ed25519KeyHash} key + */ + add_required_signer(key: Ed25519KeyHash): void; + + /** + * @param {Ed25519KeyHashes} keys + */ + add_required_signers(keys: Ed25519KeyHashes): void; + + /** + * @returns {TransactionInputs} + */ + inputs(): TransactionInputs; + + /** + * @returns {TransactionInputs | void} + */ + inputs_option(): TransactionInputs | void; +} /** */ declare export class URL { diff --git a/rust/src/tx_builder.rs b/rust/src/tx_builder.rs index ff662fe2..5a6b0b05 100644 --- a/rust/src/tx_builder.rs +++ b/rust/src/tx_builder.rs @@ -9,36 +9,36 @@ use super::output_builder::TransactionOutputAmountBuilder; use std::collections::{BTreeMap, BTreeSet, HashSet}; use linked_hash_map::LinkedHashMap; use tx_inputs_builder::{PlutusWitness, PlutusWitnesses}; -use crate::tx_builder::tx_inputs_builder::TxInputsBuilder; +use crate::tx_builder::tx_inputs_builder::{get_bootstraps, TxInputsBuilder}; // comes from witsVKeyNeeded in the Ledger spec -fn witness_keys_for_cert(cert_enum: &Certificate) -> RequiredSignersSet { - let mut set = RequiredSignersSet::new(); +fn witness_keys_for_cert(cert_enum: &Certificate) -> RequiredSigners { + let mut set = RequiredSigners::new(); match &cert_enum.0 { // stake key registrations do not require a witness CertificateEnum::StakeRegistration(_cert) => {}, CertificateEnum::StakeDeregistration(cert) => { - set.insert(cert.stake_credential().to_keyhash().unwrap()); + set.add(&cert.stake_credential().to_keyhash().unwrap()); }, CertificateEnum::StakeDelegation(cert) => { - set.insert(cert.stake_credential().to_keyhash().unwrap()); + set.add(&cert.stake_credential().to_keyhash().unwrap()); }, CertificateEnum::PoolRegistration(cert) => { for owner in &cert.pool_params().pool_owners().0 { - set.insert(owner.clone()); + set.add(&owner.clone()); } - set.insert( - Ed25519KeyHash::from_bytes(cert.pool_params().operator().to_bytes()).unwrap() + set.add( + &Ed25519KeyHash::from_bytes(cert.pool_params().operator().to_bytes()).unwrap() ); }, CertificateEnum::PoolRetirement(cert) => { - set.insert( - Ed25519KeyHash::from_bytes(cert.pool_keyhash().to_bytes()).unwrap() + set.add( + &Ed25519KeyHash::from_bytes(cert.pool_keyhash().to_bytes()).unwrap() ); }, CertificateEnum::GenesisKeyDelegation(cert) => { - set.insert( - Ed25519KeyHash::from_bytes(cert.genesis_delegate_hash().to_bytes()).unwrap() + set.add( + &Ed25519KeyHash::from_bytes(cert.genesis_delegate_hash().to_bytes()).unwrap() ); }, // not witness as there is no single core node or genesis key that posts the certificate @@ -69,8 +69,8 @@ fn fake_raw_key_public() -> PublicKey { } fn count_needed_vkeys(tx_builder: &TransactionBuilder) -> usize { - let mut input_hashes: RequiredSignersSet = tx_builder.inputs.required_signers(); - input_hashes.extend(tx_builder.collateral.required_signers()); + let mut input_hashes: RequiredSignersSet = RequiredSignersSet::from(&tx_builder.inputs); + input_hashes.extend(RequiredSignersSet::from(&tx_builder.collateral)); if let Some(scripts) = &tx_builder.mint_scripts { input_hashes.extend(RequiredSignersSet::from(scripts)); } @@ -100,7 +100,7 @@ fn fake_full_tx(tx_builder: &TransactionBuilder, body: TransactionBody) -> Resul Some(result) }, }; - let bootstraps = &tx_builder.inputs.bootstraps(); + let bootstraps = get_bootstraps(&tx_builder.inputs); let bootstrap_keys = match bootstraps.len() { 0 => None, _x => { diff --git a/rust/src/tx_builder/tx_inputs_builder.rs b/rust/src/tx_builder/tx_inputs_builder.rs index 8f0ba707..259a8ee4 100644 --- a/rust/src/tx_builder/tx_inputs_builder.rs +++ b/rust/src/tx_builder/tx_inputs_builder.rs @@ -112,6 +112,10 @@ pub struct TxInputsBuilder { input_types: MockWitnessSet, } +pub(crate) fn get_bootstraps(inputs: &TxInputsBuilder) -> BTreeSet> { + inputs.input_types.bootstraps.clone() +} + #[wasm_bindgen] impl TxInputsBuilder { @@ -327,26 +331,12 @@ impl TxInputsBuilder { self.inputs.len() } - pub fn required_signers(&self) -> RequiredSignersSet { - let mut set = self.input_types.vkeys.clone(); - self.input_types.scripts.values().for_each(|swt: &Option| { - if let Some(ScriptWitnessType::NativeScriptWitness(s)) = swt { - RequiredSignersSet::from(s).iter().for_each(|k| { set.insert(k.clone()); }); - } - }); - set - } - pub fn add_required_signer(&mut self, key: &Ed25519KeyHash) { self.input_types.vkeys.insert(key.clone()); } - pub fn add_required_signers(&mut self, keys: &RequiredSignersSet) { - keys.iter().for_each(|k| self.add_required_signer(k)); - } - - pub(crate) fn bootstraps(&self) -> BTreeSet> { - self.input_types.bootstraps.clone() + pub fn add_required_signers(&mut self, keys: &RequiredSigners) { + keys.0.iter().for_each(|k| self.add_required_signer(k)); } pub fn inputs(&self) -> TransactionInputs { @@ -360,3 +350,15 @@ impl TxInputsBuilder { if self.len() > 0 { Some(self.inputs()) } else { None } } } + +impl From<&TxInputsBuilder> for RequiredSignersSet { + fn from(inputs: &TxInputsBuilder) -> Self { + let mut set = inputs.input_types.vkeys.clone(); + inputs.input_types.scripts.values().for_each(|swt: &Option| { + if let Some(ScriptWitnessType::NativeScriptWitness(s)) = swt { + RequiredSignersSet::from(s).iter().for_each(|k| { set.insert(k.clone()); }); + } + }); + set + } +} diff --git a/rust/src/utils.rs b/rust/src/utils.rs index c502bcb0..d885f7bf 100644 --- a/rust/src/utils.rs +++ b/rust/src/utils.rs @@ -729,7 +729,7 @@ impl BigInt { } } - pub fn from_str(text: &str) -> Result { + pub fn from_str(text: &str) -> Result { use std::str::FromStr; num_bigint::BigInt::from_str(text) .map_err(|e| JsError::from_str(&format! {"{:?}", e})) @@ -740,24 +740,24 @@ impl BigInt { self.0.to_string() } - pub fn add(&self, other: &Self) -> Self { + pub fn add(&self, other: &BigInt) -> BigInt { Self(&self.0 + &other.0) } - pub fn mul(&self, other: &Self) -> Self { + pub fn mul(&self, other: &BigInt) -> BigInt { Self(&self.0 * &other.0) } - pub fn one() -> Self { + pub fn one() -> BigInt { use std::str::FromStr; Self(num_bigint::BigInt::from_str("1").unwrap()) } - pub fn increment(&self) -> Self { + pub fn increment(&self) -> BigInt { self.add(&Self::one()) } - pub fn div_ceil(&self, other: &Self) -> Self { + pub fn div_ceil(&self, other: &BigInt) -> BigInt { use num_integer::Integer; let (res, rem) = self.0.div_rem(&other.0); let result = Self(res); From aaa6c79a1675c48cf514f0ba50f42fecdfc67978 Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Tue, 31 May 2022 15:09:56 +0300 Subject: [PATCH 10/22] Updated `tx_builder.min_fee` to include ex-unit cost --- rust/src/tx_builder.rs | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/rust/src/tx_builder.rs b/rust/src/tx_builder.rs index 5a6b0b05..97fb15f1 100644 --- a/rust/src/tx_builder.rs +++ b/rust/src/tx_builder.rs @@ -171,7 +171,19 @@ fn min_fee(tx_builder: &TransactionBuilder) -> Result { // assert_required_mint_scripts(mint, tx_builder.mint_scripts.as_ref())?; // } let full_tx = fake_full_tx(tx_builder, tx_builder.build()?)?; - fees::min_fee(&full_tx, &tx_builder.config.fee_algo) + let fee: Coin = fees::min_fee(&full_tx, &tx_builder.config.fee_algo)?; + if let Some(ex_unit_prices) = &tx_builder.config.ex_unit_prices { + let script_fee: Coin = fees::min_script_fee(&full_tx, &ex_unit_prices)?; + return fee.checked_add(&script_fee); + } + if tx_builder.has_plutus_inputs() { + return Err( + JsError::from_str( + "Plutus inputs are present but ex unit prices are missing in the config!" + ) + ); + } + Ok(fee) } #[wasm_bindgen] @@ -1384,6 +1396,10 @@ impl TransactionBuilder { wit } + fn has_plutus_inputs(&self) -> bool { + self.inputs.get_plutus_input_scripts().is_some() + } + /// Returns full Transaction object with the body and the auxiliary data /// NOTE: witness_set will contain all mint_scripts if any been added or set /// NOTE: is_valid set to true @@ -1394,7 +1410,7 @@ impl TransactionBuilder { "There are some script inputs added that don't have the corresponding script provided as a witness!", )); } - if self.get_plutus_input_scripts().is_some() { + if self.has_plutus_inputs() { if self.script_data_hash.is_none() { return Err(JsError::from_str( "Plutus inputs are present, but script data hash is not specified", From 96971537ec76c4152a48ab5346f7d950343201b1 Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Tue, 31 May 2022 19:38:15 +0300 Subject: [PATCH 11/22] tests --- rust/pkg/cardano_serialization_lib.js.flow | 5 ++ rust/src/fees.rs | 6 +++ rust/src/tx_builder.rs | 59 ++++++++++++++++++++++ rust/src/utils.rs | 7 +++ 4 files changed, 77 insertions(+) diff --git a/rust/pkg/cardano_serialization_lib.js.flow b/rust/pkg/cardano_serialization_lib.js.flow index 6bc82638..6081681e 100644 --- a/rust/pkg/cardano_serialization_lib.js.flow +++ b/rust/pkg/cardano_serialization_lib.js.flow @@ -668,6 +668,11 @@ declare export class BigInt { */ static from_bytes(bytes: Uint8Array): BigInt; + /** + * @returns {boolean} + */ + is_zero(): boolean; + /** * @returns {BigNum | void} */ diff --git a/rust/src/fees.rs b/rust/src/fees.rs index 8142a027..9760aafe 100644 --- a/rust/src/fees.rs +++ b/rust/src/fees.rs @@ -49,6 +49,12 @@ pub fn calculate_ex_units_ceil_cost( // Ratio Addition: a/x + b/y = ((a*y) + (b*x))/(x*y) let (a_num, a_denum) = &a; let (b_num, b_denum) = &b; + if a_num.is_zero() { + return b.clone(); + } + if b_num.is_zero() { + return a.clone(); + } let a_num_fixed = &a_num.mul(b_denum); let b_num_fixed = &b_num.mul(a_denum); let a_b_num_sum = a_num_fixed.add(b_num_fixed); diff --git a/rust/src/tx_builder.rs b/rust/src/tx_builder.rs index 97fb15f1..1a380abf 100644 --- a/rust/src/tx_builder.rs +++ b/rust/src/tx_builder.rs @@ -1518,6 +1518,12 @@ mod tests { .max_value_size(max_val_size) .max_tx_size(MAX_TX_SIZE) .coins_per_utxo_word(&to_bignum(coins_per_utxo_word)) + .ex_unit_prices( + &ExUnitPrices::new( + &SubCoin::new(&to_bignum(577), &to_bignum(10000)), + &SubCoin::new(&to_bignum(721), &to_bignum(10000000)), + ), + ) .build() .unwrap(); TransactionBuilder::new(&cfg) @@ -5054,5 +5060,58 @@ mod tests { assert_eq!(redeemers.get(0), redeemer1.clone_with_index(&to_bignum(1))); assert_eq!(redeemers.get(1), redeemer2.clone_with_index(&to_bignum(1))); } + + #[test] + fn test_ex_unit_costs_are_added_to_the_fees() { + + fn calc_fee_with_ex_units(mem: u64, step: u64) -> Coin { + let mut input_builder = TxInputsBuilder::new(); + let mut collateral_builder = TxInputsBuilder::new(); + + // Add a single input of both kinds with the SAME keyhash + input_builder.add_input( + &create_base_address(0), + &TransactionInput::new(&genesis_id(), 0), + &Value::new(&to_bignum(1_000_000)), + ); + collateral_builder.add_input( + &create_base_address(0), + &TransactionInput::new(&genesis_id(), 0), + &Value::new(&to_bignum(1_000_000)), + ); + + let (pscript1, _) = plutus_script_and_hash(0); + let datum1 = PlutusData::new_bytes(fake_bytes(10)); + let redeemer1 = Redeemer::new( + &RedeemerTag::new_spend(), + &to_bignum(0), + &PlutusData::new_bytes(fake_bytes(20)), + &ExUnits::new(&to_bignum(mem), &to_bignum(step)), + ); + input_builder.add_plutus_script_input( + &PlutusWitness::new( + &pscript1, + &datum1, + &redeemer1, + ), + &TransactionInput::new(&genesis_id(), 0), + &Value::new(&to_bignum(1_000_000)), + ); + + let mut tx_builder = create_reallistic_tx_builder(); + tx_builder.set_inputs(&input_builder); + tx_builder.set_collateral(&collateral_builder); + + tx_builder.add_change_if_needed( + &create_base_address(42), + ).unwrap(); + + tx_builder.get_fee_if_set().unwrap() + } + + assert_eq!(calc_fee_with_ex_units(0, 0), to_bignum(173509)); + assert_eq!(calc_fee_with_ex_units(10000, 0), to_bignum(174174)); + assert_eq!(calc_fee_with_ex_units(0, 10000000), to_bignum(174406)); + } } diff --git a/rust/src/utils.rs b/rust/src/utils.rs index d885f7bf..a0c11ca4 100644 --- a/rust/src/utils.rs +++ b/rust/src/utils.rs @@ -4,6 +4,7 @@ use serde_json; use std::{collections::HashMap, io::{BufRead, Seek, Write}}; use std::convert::{TryFrom}; use itertools::Itertools; +use num_bigint::Sign; use std::ops::{Rem, Div, Sub}; use super::*; @@ -718,12 +719,18 @@ to_from_bytes!(BigInt); #[wasm_bindgen] impl BigInt { + + pub fn is_zero(&self) -> bool { + self.0.sign() == Sign::NoSign + } + pub fn as_u64(&self) -> Option { let (sign, u64_digits) = self.0.to_u64_digits(); if sign == num_bigint::Sign::Minus { return None; } match u64_digits.len() { + 0 => Some(to_bignum(0)), 1 => Some(to_bignum(*u64_digits.first().unwrap())), _ => None, } From 466b354a1cff90a485e75f16a718ee9ed9bef80c Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Tue, 31 May 2022 21:30:33 +0300 Subject: [PATCH 12/22] tests + fixing warnings --- rust/src/plutus.rs | 16 ++++++++-------- rust/src/tx_builder.rs | 5 ++--- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/rust/src/plutus.rs b/rust/src/plutus.rs index 89500d96..dcc8f100 100644 --- a/rust/src/plutus.rs +++ b/rust/src/plutus.rs @@ -1290,14 +1290,14 @@ mod tests { ); let constr_0_hash = hex::encode(hash_plutus_data(&constr_0).to_bytes()); assert_eq!(constr_0_hash, "923918e403bf43c34b4ef6b48eb2ee04babed17320d8d1b9ff9ad086e86f44ec"); - let constr_0_roundtrip = PlutusData::from_bytes(constr_0.to_bytes()).unwrap(); + // let constr_0_roundtrip = PlutusData::from_bytes(constr_0.to_bytes()).unwrap(); // TODO: do we want semantic equality or bytewise equality? - //assert_eq!(constr_0, constr_0_roundtrip); - let constr_1854 = PlutusData::new_constr_plutus_data( - &ConstrPlutusData::new(&to_bignum(1854), &PlutusList::new()) - ); - let constr_1854_roundtrip = PlutusData::from_bytes(constr_1854.to_bytes()).unwrap(); - //assert_eq!(constr_1854, constr_1854_roundtrip); + // assert_eq!(constr_0, constr_0_roundtrip); + // let constr_1854 = PlutusData::new_constr_plutus_data( + // &ConstrPlutusData::new(&to_bignum(1854), &PlutusList::new()) + // ); + // let constr_1854_roundtrip = PlutusData::from_bytes(constr_1854.to_bytes()).unwrap(); + // assert_eq!(constr_1854, constr_1854_roundtrip); } #[test] @@ -1352,7 +1352,7 @@ mod tests { 32, 150000, 32, 3345831, 1, 1, ]; let cm = arr.iter().fold((CostModel::new(), 0), |(mut cm, i), x| { - cm.set(i, &Int::new_i32(x.clone())); + cm.set(i, &Int::new_i32(x.clone())).unwrap(); (cm, i + 1) }).0; let mut cms = Costmdls::new(); diff --git a/rust/src/tx_builder.rs b/rust/src/tx_builder.rs index 1a380abf..74b8583f 100644 --- a/rust/src/tx_builder.rs +++ b/rust/src/tx_builder.rs @@ -2995,7 +2995,6 @@ mod tests { #[test] fn tx_builder_cip2_largest_first_multiasset() { // we have a = 0 so we know adding inputs/outputs doesn't change the fee so we can analyze more - let linear_fee = LinearFee::new(&to_bignum(0), &to_bignum(0)); let mut tx_builder = create_tx_builder_with_fee(&create_linear_fee(0, 0)); let pid1 = PolicyID::from([1u8; 28]); let pid2 = PolicyID::from([2u8; 28]); @@ -3100,7 +3099,6 @@ mod tests { #[test] fn tx_builder_cip2_random_improve_multiasset() { - let linear_fee = LinearFee::new(&to_bignum(0), &to_bignum(0)); let mut tx_builder = create_tx_builder_with_fee(&create_linear_fee(0, 0)); let pid1 = PolicyID::from([1u8; 28]); let pid2 = PolicyID::from([2u8; 28]); @@ -4589,7 +4587,7 @@ mod tests { tx_builder.set_fee(&to_bignum(42)); let (script1, hash1) = plutus_script_and_hash(0); let (script2, hash2) = plutus_script_and_hash(1); - let (script3, hash3) = plutus_script_and_hash(3); + let (script3, _hash3) = plutus_script_and_hash(3); let datum1 = PlutusData::new_bytes(fake_bytes(10)); let datum2 = PlutusData::new_bytes(fake_bytes(11)); let redeemer1 = Redeemer::new( @@ -5112,6 +5110,7 @@ mod tests { assert_eq!(calc_fee_with_ex_units(0, 0), to_bignum(173509)); assert_eq!(calc_fee_with_ex_units(10000, 0), to_bignum(174174)); assert_eq!(calc_fee_with_ex_units(0, 10000000), to_bignum(174406)); + assert_eq!(calc_fee_with_ex_units(10000, 10000000), to_bignum(175071)); } } From 0adcdc8b8c9ae5daa5860b242320bb6a4c89feb9 Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Tue, 31 May 2022 21:31:43 +0300 Subject: [PATCH 13/22] Version bump: 10.2.0-beta.1 --- package-lock.json | 2 +- package.json | 2 +- rust/Cargo.lock | 2 +- rust/Cargo.toml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 30ba3b23..d8f14010 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "cardano-serialization-lib", - "version": "10.1.0", + "version": "10.2.0-beta.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 3e48fe20..5e38fac2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cardano-serialization-lib", - "version": "10.1.0", + "version": "10.2.0-beta.1", "description": "(De)serialization functions for the Cardano blockchain along with related utility functions", "scripts": { "rust:build-nodejs": "(rimraf ./rust/pkg && cd rust; wasm-pack build --target=nodejs; wasm-pack pack) && npm run js:flowgen", diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 78000138..8491a97d 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -52,7 +52,7 @@ checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820" [[package]] name = "cardano-serialization-lib" -version = "10.1.0" +version = "10.2.0-beta.1" dependencies = [ "bech32", "cbor_event", diff --git a/rust/Cargo.toml b/rust/Cargo.toml index c4c4673d..8ab6eb5e 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cardano-serialization-lib" -version = "10.1.0" +version = "10.2.0-beta.1" edition = "2018" authors = ["EMURGO"] license = "MIT" From bffac1aadd4dd3d40ff6e02c92f7f59b2f012bd7 Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Tue, 31 May 2022 22:11:38 +0300 Subject: [PATCH 14/22] tiny readability --- rust/src/utils.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/rust/src/utils.rs b/rust/src/utils.rs index a0c11ca4..3af38ed7 100644 --- a/rust/src/utils.rs +++ b/rust/src/utils.rs @@ -768,8 +768,7 @@ impl BigInt { use num_integer::Integer; let (res, rem) = self.0.div_rem(&other.0); let result = Self(res); - let no_remainder = rem.sign() == num_bigint::Sign::NoSign; - if no_remainder { result } else { result.increment() } + if Self(rem).is_zero() { result } else { result.increment() } } } From 5ef52819c515a5794e2e748f5a98ab26d75c12c8 Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Wed, 1 Jun 2022 16:40:42 +0300 Subject: [PATCH 15/22] Canonical inputs sorting --- rust/src/tx_builder.rs | 82 ++++++++++++++++++++++-- rust/src/tx_builder/tx_inputs_builder.rs | 11 +++- 2 files changed, 85 insertions(+), 8 deletions(-) diff --git a/rust/src/tx_builder.rs b/rust/src/tx_builder.rs index 74b8583f..00092dfc 100644 --- a/rust/src/tx_builder.rs +++ b/rust/src/tx_builder.rs @@ -2918,9 +2918,13 @@ mod tests { assert!(tx_builder.add_change_if_needed(&change_addr).is_err()) } + fn fake_tx_hash(input_hash_byte: u8) -> TransactionHash { + TransactionHash::from([input_hash_byte; 32]) + } + fn make_input(input_hash_byte: u8, value: Value) -> TransactionUnspentOutput { TransactionUnspentOutput::new( - &TransactionInput::new(&TransactionHash::from([input_hash_byte; 32]), 0), + &TransactionInput::new(&fake_tx_hash(input_hash_byte), 0), &TransactionOutputBuilder::new() .with_address(&Address::from_bech32("addr1vyy6nhfyks7wdu3dudslys37v252w2nwhv0fw2nfawemmnqs6l44z").unwrap()) .next().unwrap() @@ -2956,12 +2960,11 @@ mod tests { assert_eq!(2, tx.outputs().len()); assert_eq!(3, tx.inputs().len()); // confirm order of only what is necessary - assert_eq!(2u8, tx.inputs().get(0).transaction_id().0[0]); - assert_eq!(3u8, tx.inputs().get(1).transaction_id().0[0]); - assert_eq!(1u8, tx.inputs().get(2).transaction_id().0[0]); + assert_eq!(1u8, tx.inputs().get(0).transaction_id().0[0]); + assert_eq!(2u8, tx.inputs().get(1).transaction_id().0[0]); + assert_eq!(3u8, tx.inputs().get(2).transaction_id().0[0]); } - #[test] fn tx_builder_cip2_largest_first_static_fees() { // we have a = 0 so we know adding inputs/outputs doesn't change the fee so we can analyze more @@ -5112,5 +5115,74 @@ mod tests { assert_eq!(calc_fee_with_ex_units(0, 10000000), to_bignum(174406)); assert_eq!(calc_fee_with_ex_units(10000, 10000000), to_bignum(175071)); } + + #[test] + fn test_script_inputs_ordering() { + let mut tx_builder = create_reallistic_tx_builder(); + tx_builder.set_fee(&to_bignum(42)); + let (nscript1, _) = mint_script_and_policy(0); + let (pscript1, _) = plutus_script_and_hash(0); + let (pscript2, _) = plutus_script_and_hash(1); + let datum1 = PlutusData::new_bytes(fake_bytes(10)); + let datum2 = PlutusData::new_bytes(fake_bytes(11)); + // Creating redeemers with indexes ZERO + let pdata1 = PlutusData::new_bytes(fake_bytes(20)); + let pdata2 = PlutusData::new_bytes(fake_bytes(21)); + let redeemer1 = Redeemer::new( + &RedeemerTag::new_spend(), + &to_bignum(0), + &pdata1, + &ExUnits::new(&to_bignum(1), &to_bignum(2)), + ); + let redeemer2 = Redeemer::new( + &RedeemerTag::new_spend(), + &to_bignum(0), + &pdata2, + &ExUnits::new(&to_bignum(1), &to_bignum(2)), + ); + + tx_builder.add_plutus_script_input( + &PlutusWitness::new( + &pscript1, + &datum1, + &redeemer1, + ), + &TransactionInput::new(&fake_tx_hash(3), 0), + &Value::new(&to_bignum(1_000_000)), + ); + tx_builder.add_native_script_input( + &nscript1, + &TransactionInput::new(&fake_tx_hash(1), 0), + &Value::new(&to_bignum(1_000_000)), + ); + tx_builder.add_plutus_script_input( + &PlutusWitness::new( + &pscript2, + &datum2, + &redeemer2, + ), + &TransactionInput::new(&fake_tx_hash(2), 0), + &Value::new(&to_bignum(1_000_000)), + ); + + let tx: Transaction = tx_builder.build_tx_unsafe().unwrap(); + + let ins = tx.body.inputs; + assert_eq!(ins.len(), 3); + assert_eq!(ins.get(0).transaction_id.0[0], 1); + assert_eq!(ins.get(1).transaction_id.0[0], 2); + assert_eq!(ins.get(2).transaction_id.0[0], 3); + + let r: Redeemers = tx.witness_set.redeemers.unwrap(); + assert_eq!(r.len(), 2); + + // Redeemer1 now has the index 2 even tho the input was added first + assert_eq!(r.get(0).data(), pdata1); + assert_eq!(r.get(0).index(), to_bignum(2)); + + // Redeemer1 now has the index 1 even tho the input was added last + assert_eq!(r.get(1).data(), pdata2); + assert_eq!(r.get(1).index(), to_bignum(1)); + } } diff --git a/rust/src/tx_builder/tx_inputs_builder.rs b/rust/src/tx_builder/tx_inputs_builder.rs index 259a8ee4..4d9dfbe8 100644 --- a/rust/src/tx_builder/tx_inputs_builder.rs +++ b/rust/src/tx_builder/tx_inputs_builder.rs @@ -130,6 +130,11 @@ impl TxInputsBuilder { } } + fn push_input(&mut self, e: (TxBuilderInput, Option)) { + self.inputs.push(e); + self.inputs.sort_by(|(a, _), (b, _)| a.input.cmp(&b.input)); + } + /// We have to know what kind of inputs these are to know what kind of mock witnesses to create since /// 1) mock witnesses have different lengths depending on the type which changes the expecting fee /// 2) Witnesses are a set so we need to get rid of duplicates to avoid over-estimating the fee @@ -138,7 +143,7 @@ impl TxInputsBuilder { input: input.clone(), amount: amount.clone(), }; - self.inputs.push((inp, None)); + self.push_input((inp, None)); self.input_types.vkeys.insert(hash.clone()); } @@ -154,7 +159,7 @@ impl TxInputsBuilder { input: input.clone(), amount: amount.clone(), }; - self.inputs.push((inp, Some(hash.clone()))); + self.push_input((inp, Some(hash.clone()))); if !self.input_types.scripts.contains_key(hash) { self.input_types.scripts.insert(hash.clone(), None); } @@ -185,7 +190,7 @@ impl TxInputsBuilder { input: input.clone(), amount: amount.clone(), }; - self.inputs.push((inp, None)); + self.push_input((inp, None)); self.input_types.bootstraps.insert(hash.to_bytes()); } From 14337342f5c5c8381f6df87678f0e3ca4b3510c8 Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Wed, 1 Jun 2022 16:44:35 +0300 Subject: [PATCH 16/22] Canonical inputs sorting test fix --- rust/src/tx_builder.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/rust/src/tx_builder.rs b/rust/src/tx_builder.rs index 00092dfc..82e19410 100644 --- a/rust/src/tx_builder.rs +++ b/rust/src/tx_builder.rs @@ -5147,7 +5147,7 @@ mod tests { &datum1, &redeemer1, ), - &TransactionInput::new(&fake_tx_hash(3), 0), + &TransactionInput::new(&fake_tx_hash(2), 1), &Value::new(&to_bignum(1_000_000)), ); tx_builder.add_native_script_input( @@ -5171,7 +5171,9 @@ mod tests { assert_eq!(ins.len(), 3); assert_eq!(ins.get(0).transaction_id.0[0], 1); assert_eq!(ins.get(1).transaction_id.0[0], 2); - assert_eq!(ins.get(2).transaction_id.0[0], 3); + assert_eq!(ins.get(1).index, 0); + assert_eq!(ins.get(2).transaction_id.0[0], 2); + assert_eq!(ins.get(2).index, 1); let r: Redeemers = tx.witness_set.redeemers.unwrap(); assert_eq!(r.len(), 2); From 8f17f6612c27281800f0a2102e541026068e10fa Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Wed, 1 Jun 2022 16:46:29 +0300 Subject: [PATCH 17/22] Version bump: 10.2.0-beta.2 --- package-lock.json | 2 +- package.json | 2 +- rust/Cargo.lock | 2 +- rust/Cargo.toml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index d8f14010..2aa3ab80 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "cardano-serialization-lib", - "version": "10.2.0-beta.1", + "version": "10.2.0-beta.2", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 5e38fac2..b80f4460 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cardano-serialization-lib", - "version": "10.2.0-beta.1", + "version": "10.2.0-beta.2", "description": "(De)serialization functions for the Cardano blockchain along with related utility functions", "scripts": { "rust:build-nodejs": "(rimraf ./rust/pkg && cd rust; wasm-pack build --target=nodejs; wasm-pack pack) && npm run js:flowgen", diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 8491a97d..1243fb90 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -52,7 +52,7 @@ checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820" [[package]] name = "cardano-serialization-lib" -version = "10.2.0-beta.1" +version = "10.2.0-beta.2" dependencies = [ "bech32", "cbor_event", diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 8ab6eb5e..c532cd91 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cardano-serialization-lib" -version = "10.2.0-beta.1" +version = "10.2.0-beta.2" edition = "2018" authors = ["EMURGO"] license = "MIT" From e9ed9127e33fc944efdff11eaf57922a7c592146 Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Wed, 1 Jun 2022 18:29:27 +0300 Subject: [PATCH 18/22] Adding `PlutusData::new_empty_constr_plutus_data` --- rust/pkg/cardano_serialization_lib.js.flow | 12 +++++++++++ rust/src/plutus.rs | 24 ++++++++++++++++++++++ rust/src/tx_builder.rs | 2 +- rust/src/utils.rs | 4 ++++ 4 files changed, 41 insertions(+), 1 deletion(-) diff --git a/rust/pkg/cardano_serialization_lib.js.flow b/rust/pkg/cardano_serialization_lib.js.flow index 6081681e..1c03d51b 100644 --- a/rust/pkg/cardano_serialization_lib.js.flow +++ b/rust/pkg/cardano_serialization_lib.js.flow @@ -749,6 +749,11 @@ declare export class BigNum { */ static zero(): BigNum; + /** + * @returns {BigNum} + */ + static one(): BigNum; + /** * @returns {boolean} */ @@ -3105,6 +3110,13 @@ declare export class PlutusData { constr_plutus_data: ConstrPlutusData ): PlutusData; + /** + * Same as `.new_constr_plutus_data` but creates constr with empty data list + * @param {BigNum} alternative + * @returns {PlutusData} + */ + static new_empty_constr_plutus_data(alternative: BigNum): PlutusData; + /** * @param {PlutusMap} map * @returns {PlutusData} diff --git a/rust/src/plutus.rs b/rust/src/plutus.rs index dcc8f100..0d514d2d 100644 --- a/rust/src/plutus.rs +++ b/rust/src/plutus.rs @@ -387,6 +387,7 @@ to_from_bytes!(PlutusData); #[wasm_bindgen] impl PlutusData { + pub fn new_constr_plutus_data(constr_plutus_data: &ConstrPlutusData) -> Self { Self { datum: PlutusDataEnum::ConstrPlutusData(constr_plutus_data.clone()), @@ -394,6 +395,16 @@ impl PlutusData { } } + /// Same as `.new_constr_plutus_data` but creates constr with empty data list + pub fn new_empty_constr_plutus_data(alternative: &BigNum) -> Self { + Self::new_constr_plutus_data( + &ConstrPlutusData::new( + alternative, + &PlutusList::new(), + ), + ) + } + pub fn new_map(map: &PlutusMap) -> Self { Self { datum: PlutusDataEnum::Map(map.clone()), @@ -1399,4 +1410,17 @@ mod tests { r.add(&redeemer_with_ex_units(&to_bignum(30), &to_bignum(300))); assert_ex_units(&r.total_ex_units().unwrap(), 60, 600); } + + #[test] + fn test_empty_constr_data() { + assert_eq!( + PlutusData::new_empty_constr_plutus_data(&BigNum::one()), + PlutusData::new_constr_plutus_data( + &ConstrPlutusData::new( + &BigNum::from_str("1").unwrap(), + &PlutusList::new(), + ), + ), + ) + } } diff --git a/rust/src/tx_builder.rs b/rust/src/tx_builder.rs index 82e19410..d3cdac35 100644 --- a/rust/src/tx_builder.rs +++ b/rust/src/tx_builder.rs @@ -1282,7 +1282,7 @@ impl TransactionBuilder { /// In case there are no plutus input witnesses present - nothing will change /// You can set specific hash value using `.set_script_data_hash` pub fn calc_script_data_hash(&mut self, cost_models: &Costmdls) { - if let Some(pw) = self.get_plutus_input_scripts() { + if let Some(pw) = self.inputs.get_plutus_input_scripts() { let (_, datums, redeemers) = pw.collect(); self.script_data_hash = Some(hash_script_data(&redeemers, cost_models, Some(datums))); diff --git a/rust/src/utils.rs b/rust/src/utils.rs index 3af38ed7..7705e860 100644 --- a/rust/src/utils.rs +++ b/rust/src/utils.rs @@ -188,6 +188,10 @@ impl BigNum { Self(0) } + pub fn one() -> Self { + Self(1) + } + pub fn is_zero(&self) -> bool { self.0 == 0 } From afebcb34a98ea4c08c99336f01508b0f050800ab Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Wed, 1 Jun 2022 19:23:51 +0300 Subject: [PATCH 19/22] Version bump 10.2.0 --- package-lock.json | 2 +- package.json | 2 +- rust/Cargo.lock | 2 +- rust/Cargo.toml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2aa3ab80..c7f19d85 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "cardano-serialization-lib", - "version": "10.2.0-beta.2", + "version": "10.2.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index b80f4460..b31fb3b5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cardano-serialization-lib", - "version": "10.2.0-beta.2", + "version": "10.2.0", "description": "(De)serialization functions for the Cardano blockchain along with related utility functions", "scripts": { "rust:build-nodejs": "(rimraf ./rust/pkg && cd rust; wasm-pack build --target=nodejs; wasm-pack pack) && npm run js:flowgen", diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 1243fb90..90dd06e0 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -52,7 +52,7 @@ checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820" [[package]] name = "cardano-serialization-lib" -version = "10.2.0-beta.2" +version = "10.2.0" dependencies = [ "bech32", "cbor_event", diff --git a/rust/Cargo.toml b/rust/Cargo.toml index c532cd91..a850d072 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cardano-serialization-lib" -version = "10.2.0-beta.2" +version = "10.2.0" edition = "2018" authors = ["EMURGO"] license = "MIT" From d5b93bba9440208f6bfc56710d9dfaf76a40cb69 Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Thu, 2 Jun 2022 22:20:41 +0300 Subject: [PATCH 20/22] Changed the tx-inputs-builder to store inputs in a sorted map. Added `tx_builder.add_required_signer`. Fixed tests. Beta version bump. --- package-lock.json | 2 +- package.json | 2 +- rust/Cargo.lock | 2 +- rust/Cargo.toml | 2 +- rust/pkg/cardano_serialization_lib.js.flow | 10 +++ rust/src/lib.rs | 4 + rust/src/tx_builder.rs | 85 +++++++++++++++------- rust/src/tx_builder/tx_inputs_builder.rs | 14 ++-- 8 files changed, 85 insertions(+), 36 deletions(-) diff --git a/package-lock.json b/package-lock.json index c7f19d85..a1301d59 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "cardano-serialization-lib", - "version": "10.2.0", + "version": "10.2.0-beta.4", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index b31fb3b5..de6ac658 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cardano-serialization-lib", - "version": "10.2.0", + "version": "10.2.0-beta.4", "description": "(De)serialization functions for the Cardano blockchain along with related utility functions", "scripts": { "rust:build-nodejs": "(rimraf ./rust/pkg && cd rust; wasm-pack build --target=nodejs; wasm-pack pack) && npm run js:flowgen", diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 90dd06e0..9063ae74 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -52,7 +52,7 @@ checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820" [[package]] name = "cardano-serialization-lib" -version = "10.2.0" +version = "10.2.0-beta.4" dependencies = [ "bech32", "cbor_event", diff --git a/rust/Cargo.toml b/rust/Cargo.toml index a850d072..1ba2a731 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cardano-serialization-lib" -version = "10.2.0" +version = "10.2.0-beta.4" edition = "2018" authors = ["EMURGO"] license = "MIT" diff --git a/rust/pkg/cardano_serialization_lib.js.flow b/rust/pkg/cardano_serialization_lib.js.flow index 1c03d51b..9b0de798 100644 --- a/rust/pkg/cardano_serialization_lib.js.flow +++ b/rust/pkg/cardano_serialization_lib.js.flow @@ -1591,6 +1591,11 @@ declare export class Ed25519KeyHashes { * @param {Ed25519KeyHash} elem */ add(elem: Ed25519KeyHash): void; + + /** + * @returns {Ed25519KeyHashes | void} + */ + to_option(): Ed25519KeyHashes | void; } /** */ @@ -5764,6 +5769,11 @@ declare export class TransactionBuilder { */ remove_script_data_hash(): void; + /** + * @param {Ed25519KeyHash} key + */ + add_required_signer(key: Ed25519KeyHash): void; + /** * @returns {number} */ diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 2e9ddfbc..a503b380 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -629,6 +629,10 @@ impl Ed25519KeyHashes { pub fn add(&mut self, elem: &Ed25519KeyHash) { self.0.push(elem.clone()); } + + pub fn to_option(&self) -> Option { + if self.len() > 0 { Some(self.clone()) } else { None } + } } #[wasm_bindgen] diff --git a/rust/src/tx_builder.rs b/rust/src/tx_builder.rs index d3cdac35..b789a145 100644 --- a/rust/src/tx_builder.rs +++ b/rust/src/tx_builder.rs @@ -318,6 +318,7 @@ pub struct TransactionBuilder { mint: Option, mint_scripts: Option, script_data_hash: Option, + required_signers: Ed25519KeyHashes, } #[wasm_bindgen] @@ -954,6 +955,7 @@ impl TransactionBuilder { mint: None, mint_scripts: None, script_data_hash: None, + required_signers: Ed25519KeyHashes::new(), } } @@ -1302,6 +1304,10 @@ impl TransactionBuilder { self.script_data_hash = None; } + pub fn add_required_signer(&mut self, key: &Ed25519KeyHash) { + self.required_signers.add(key); + } + fn build_and_size(&self) -> Result<(TransactionBody, usize), JsError> { let fee = self.fee.ok_or_else(|| JsError::from_str("Fee not specified"))?; let built = TransactionBody { @@ -1320,7 +1326,7 @@ impl TransactionBuilder { mint: self.mint.clone(), script_data_hash: self.script_data_hash.clone(), collateral: self.collateral.inputs_option(), - required_signers: None, + required_signers: self.required_signers.to_option(), network_id: None, }; // we must build a tx with fake data (of correct size) to check the final Transaction size @@ -1971,7 +1977,7 @@ mod tests { &spend_cred, &stake_cred ).to_address(), - &TransactionInput::new(&genesis_id(), 0), + &TransactionInput::new(&genesis_id(), 1), &Value::new(&to_bignum(1_000_000)) ); tx_builder.add_input( @@ -1984,14 +1990,14 @@ mod tests { &to_bignum(0) ) ).to_address(), - &TransactionInput::new(&genesis_id(), 0), + &TransactionInput::new(&genesis_id(), 2), &Value::new(&to_bignum(1_000_000)) ); tx_builder.add_input( &ByronAddress::icarus_from_key( &spend, NetworkInfo::testnet().protocol_magic() ).to_address(), - &TransactionInput::new(&genesis_id(), 0), + &TransactionInput::new(&genesis_id(), 3), &Value::new(&to_bignum(1_000_000)) ); @@ -2226,16 +2232,17 @@ mod tests { }) .collect::>(); - for (multiasset, ada) in multiassets + for (i, (multiasset, ada)) in multiassets .iter() .zip([100u64, 100].iter().cloned().map(to_bignum)) + .enumerate() { let mut input_amount = Value::new(&ada); input_amount.set_multiasset(multiasset); tx_builder.add_key_input( &&spend.to_raw_key().hash(), - &TransactionInput::new(&genesis_id(), 0), + &TransactionInput::new(&genesis_id(), i as u32), &input_amount, ); } @@ -2339,16 +2346,17 @@ mod tests { }) .collect::>(); - for (multiasset, ada) in multiassets + for (i, (multiasset, ada)) in multiassets .iter() .zip([100u64, 100].iter().cloned().map(to_bignum)) + .enumerate() { let mut input_amount = Value::new(&ada); input_amount.set_multiasset(multiasset); tx_builder.add_key_input( &&spend.to_raw_key().hash(), - &TransactionInput::new(&genesis_id(), 0), + &TransactionInput::new(&genesis_id(), i as u32), &input_amount, ); } @@ -2469,16 +2477,17 @@ mod tests { }) .collect::>(); - for (multiasset, ada) in multiassets + for (i, (multiasset, ada)) in multiassets .iter() .zip([300u64, 300].iter().cloned().map(to_bignum)) + .enumerate() { let mut input_amount = Value::new(&ada); input_amount.set_multiasset(multiasset); tx_builder.add_key_input( &&spend.to_raw_key().hash(), - &TransactionInput::new(&genesis_id(), 0), + &TransactionInput::new(&genesis_id(), i as u32), &input_amount, ); } @@ -4154,7 +4163,7 @@ mod tests { // One input from same address as mint tx_builder.add_key_input( &hash1, - &TransactionInput::new(&genesis_id(), 0), + &TransactionInput::new(&genesis_id(), 1), &Value::new(&to_bignum(10_000_000)), ); @@ -4312,16 +4321,17 @@ mod tests { }) .collect::>(); - for (multiasset, ada) in multiassets + for (i, (multiasset, ada)) in multiassets .iter() .zip([100u64, 100, 100].iter().cloned().map(to_bignum)) + .enumerate() { let mut input_amount = Value::new(&ada); input_amount.set_multiasset(multiasset); tx_builder.add_key_input( &&spend.to_raw_key().hash(), - &TransactionInput::new(&genesis_id(), 0), + &TransactionInput::new(&genesis_id(), i as u32), &input_amount, ); } @@ -4495,7 +4505,7 @@ mod tests { let tx_len_before_new_script_input = unsafe_tx_len(&tx_builder); tx_builder.add_input( &create_base_address_from_script_hash(&hash2), - &TransactionInput::new(&genesis_id(), 0), + &TransactionInput::new(&genesis_id(), 1), &Value::new(&to_bignum(1_000_000)) ); let tx_len_after_new_script_input = unsafe_tx_len(&tx_builder); @@ -4612,7 +4622,7 @@ mod tests { ); tx_builder.add_input( &create_base_address_from_script_hash(&hash2), - &TransactionInput::new(&genesis_id(), 0), + &TransactionInput::new(&genesis_id(), 1), &Value::new(&to_bignum(1_000_000)), ); // There are TWO missing script witnesses @@ -4780,12 +4790,12 @@ mod tests { // both have redeemers with index ZERO tx_builder.add_plutus_script_input( &PlutusWitness::new(&script1, &datum1, &redeemer1), - &TransactionInput::new(&genesis_id(), 0), + &TransactionInput::new(&genesis_id(), 1), &Value::new(&to_bignum(1_000_000)), ); tx_builder.add_plutus_script_input( &PlutusWitness::new(&script2, &datum2, &redeemer2), - &TransactionInput::new(&genesis_id(), 0), + &TransactionInput::new(&genesis_id(), 2), &Value::new(&to_bignum(1_000_000)), ); @@ -4849,19 +4859,19 @@ mod tests { // Add one native input directly with witness tx_builder.add_native_script_input( &nscript1, - &TransactionInput::new(&genesis_id(), 0), + &TransactionInput::new(&genesis_id(), 1), &Value::new(&to_bignum(1_000_000)), ); // Add one plutus input generically without witness tx_builder.add_input( &create_base_address_from_script_hash(&phash2), - &TransactionInput::new(&genesis_id(), 0), + &TransactionInput::new(&genesis_id(), 2), &Value::new(&to_bignum(1_000_000)), ); // Add one native input generically without witness tx_builder.add_input( &create_base_address_from_script_hash(&nhash2), - &TransactionInput::new(&genesis_id(), 0), + &TransactionInput::new(&genesis_id(), 3), &Value::new(&to_bignum(1_000_000)), ); @@ -5005,7 +5015,7 @@ mod tests { ); collateral_builder.add_native_script_input( &nscript2, - &TransactionInput::new(&genesis_id(), 0), + &TransactionInput::new(&genesis_id(), 1), &Value::new(&to_bignum(1_000_000)), ); @@ -5015,7 +5025,7 @@ mod tests { &datum1, &redeemer1, ), - &TransactionInput::new(&genesis_id(), 0), + &TransactionInput::new(&genesis_id(), 2), &Value::new(&to_bignum(1_000_000)), ); collateral_builder.add_plutus_script_input( @@ -5024,7 +5034,7 @@ mod tests { &datum2, &redeemer2, ), - &TransactionInput::new(&genesis_id(), 0), + &TransactionInput::new(&genesis_id(), 3), &Value::new(&to_bignum(1_000_000)), ); @@ -5077,7 +5087,7 @@ mod tests { ); collateral_builder.add_input( &create_base_address(0), - &TransactionInput::new(&genesis_id(), 0), + &TransactionInput::new(&genesis_id(), 1), &Value::new(&to_bignum(1_000_000)), ); @@ -5095,7 +5105,7 @@ mod tests { &datum1, &redeemer1, ), - &TransactionInput::new(&genesis_id(), 0), + &TransactionInput::new(&genesis_id(), 2), &Value::new(&to_bignum(1_000_000)), ); @@ -5186,5 +5196,30 @@ mod tests { assert_eq!(r.get(1).data(), pdata2); assert_eq!(r.get(1).index(), to_bignum(1)); } + + #[test] + fn test_required_signers() { + let mut tx_builder = create_reallistic_tx_builder(); + tx_builder.set_fee(&to_bignum(42)); + let tx1: TransactionBody = tx_builder.build().unwrap(); + assert!(tx1.required_signers.is_none()); + + let s1 = fake_key_hash(1); + let s2 = fake_key_hash(22); + let s3 = fake_key_hash(133); + + tx_builder.add_required_signer(&s1); + tx_builder.add_required_signer(&s3); + tx_builder.add_required_signer(&s2); + + let tx1: TransactionBody = tx_builder.build().unwrap(); + assert!(tx1.required_signers.is_some()); + + let rs: RequiredSigners = tx1.required_signers.unwrap(); + assert_eq!(rs.len(), 3); + assert_eq!(rs.get(0), s1); + assert_eq!(rs.get(1), s3); + assert_eq!(rs.get(2), s2); + } } diff --git a/rust/src/tx_builder/tx_inputs_builder.rs b/rust/src/tx_builder/tx_inputs_builder.rs index 4d9dfbe8..0762802e 100644 --- a/rust/src/tx_builder/tx_inputs_builder.rs +++ b/rust/src/tx_builder/tx_inputs_builder.rs @@ -1,4 +1,5 @@ use super::*; +use std::collections::{BTreeMap, BTreeSet}; #[derive(Clone, Debug)] pub(crate) struct TxBuilderInput { @@ -108,7 +109,7 @@ pub struct MockWitnessSet { #[wasm_bindgen] #[derive(Clone, Debug)] pub struct TxInputsBuilder { - inputs: Vec<(TxBuilderInput, Option)>, + inputs: BTreeMap)>, input_types: MockWitnessSet, } @@ -121,7 +122,7 @@ impl TxInputsBuilder { pub fn new() -> Self { Self { - inputs: Vec::new(), + inputs: BTreeMap::new(), input_types: MockWitnessSet { vkeys: BTreeSet::new(), scripts: LinkedHashMap::new(), @@ -131,8 +132,7 @@ impl TxInputsBuilder { } fn push_input(&mut self, e: (TxBuilderInput, Option)) { - self.inputs.push(e); - self.inputs.sort_by(|(a, _), (b, _)| a.input.cmp(&b.input)); + self.inputs.insert(e.0.input.clone(), e); } /// We have to know what kind of inputs these are to know what kind of mock witnesses to create since @@ -310,7 +310,7 @@ impl TxInputsBuilder { * * The registered witnesses are then each cloned with the new correct redeemer input index. */ - let script_hash_index_map: BTreeMap<&ScriptHash, BigNum> = self.inputs.iter().enumerate() + let script_hash_index_map: BTreeMap<&ScriptHash, BigNum> = self.inputs.values().enumerate() .fold(BTreeMap::new(), |mut m, (i, (_, hash_option))| { if let Some(hash) = hash_option { m.insert(hash, to_bignum(i as u64)); @@ -329,7 +329,7 @@ impl TxInputsBuilder { } pub(crate) fn iter(&self) -> impl std::iter::Iterator + '_ { - self.inputs.iter().map(|(i, _)| i) + self.inputs.values().map(|(i, _)| i) } pub fn len(&self) -> usize { @@ -346,7 +346,7 @@ impl TxInputsBuilder { pub fn inputs(&self) -> TransactionInputs { TransactionInputs( - self.inputs.iter() + self.inputs.values() .map(|(ref tx_builder_input, _)| tx_builder_input.input.clone()).collect() ) } From 10bc388952de22f48efcec55c8be74e5ebd262cd Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Sat, 4 Jun 2022 23:00:23 +0300 Subject: [PATCH 21/22] Fixed tx size calculation when required signers are used. Beta version bump. --- package-lock.json | 2 +- package.json | 2 +- rust/Cargo.lock | 2 +- rust/Cargo.toml | 2 +- rust/src/lib.rs | 14 +++++++++++++ rust/src/tx_builder.rs | 45 ++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 63 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index a1301d59..041eb50a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "cardano-serialization-lib", - "version": "10.2.0-beta.4", + "version": "10.2.0-beta.5", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index de6ac658..a20a8056 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cardano-serialization-lib", - "version": "10.2.0-beta.4", + "version": "10.2.0-beta.5", "description": "(De)serialization functions for the Cardano blockchain along with related utility functions", "scripts": { "rust:build-nodejs": "(rimraf ./rust/pkg && cd rust; wasm-pack build --target=nodejs; wasm-pack pack) && npm run js:flowgen", diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 9063ae74..e2928dca 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -52,7 +52,7 @@ checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820" [[package]] name = "cardano-serialization-lib" -version = "10.2.0-beta.4" +version = "10.2.0-beta.5" dependencies = [ "bech32", "cbor_event", diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 1ba2a731..54cb4b96 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cardano-serialization-lib" -version = "10.2.0-beta.4" +version = "10.2.0-beta.5" edition = "2018" authors = ["EMURGO"] license = "MIT" diff --git a/rust/src/lib.rs b/rust/src/lib.rs index a503b380..52b4b206 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -230,6 +230,15 @@ impl Certificates { pub type RequiredSigners = Ed25519KeyHashes; pub type RequiredSignersSet = BTreeSet; +impl From<&Ed25519KeyHashes> for RequiredSignersSet { + fn from(keys: &Ed25519KeyHashes) -> Self { + keys.0.iter().fold(BTreeSet::new(), |mut set, k| { + set.insert(k.clone()); + set + }) + } +} + #[wasm_bindgen] #[derive(Clone)] pub struct TransactionBody { @@ -614,10 +623,15 @@ to_from_bytes!(Ed25519KeyHashes); #[wasm_bindgen] impl Ed25519KeyHashes { + pub fn new() -> Self { Self(Vec::new()) } + pub(crate) fn from_vec(vec: Vec) -> Self { + Self(vec) + } + pub fn len(&self) -> usize { self.0.len() } diff --git a/rust/src/tx_builder.rs b/rust/src/tx_builder.rs index b789a145..d1fa8e55 100644 --- a/rust/src/tx_builder.rs +++ b/rust/src/tx_builder.rs @@ -71,6 +71,7 @@ fn fake_raw_key_public() -> PublicKey { fn count_needed_vkeys(tx_builder: &TransactionBuilder) -> usize { let mut input_hashes: RequiredSignersSet = RequiredSignersSet::from(&tx_builder.inputs); input_hashes.extend(RequiredSignersSet::from(&tx_builder.collateral)); + input_hashes.extend(RequiredSignersSet::from(&tx_builder.required_signers)); if let Some(scripts) = &tx_builder.mint_scripts { input_hashes.extend(RequiredSignersSet::from(scripts)); } @@ -5221,5 +5222,49 @@ mod tests { assert_eq!(rs.get(1), s3); assert_eq!(rs.get(2), s2); } + + #[test] + fn test_required_signers_are_added_to_the_witness_estimate() { + + fn count_fake_witnesses_with_required_signers(keys: &Ed25519KeyHashes) -> usize { + let mut tx_builder = create_reallistic_tx_builder(); + tx_builder.set_fee(&to_bignum(42)); + tx_builder.add_input( + &create_base_address(0), + &TransactionInput::new(&fake_tx_hash(0), 0), + &Value::new(&to_bignum(10_000_000)), + ); + + keys.0.iter().for_each(|k| { + tx_builder.add_required_signer(k); + }); + + let tx: Transaction = fake_full_tx(&tx_builder, tx_builder.build().unwrap()).unwrap(); + tx.witness_set.vkeys.unwrap().len() + } + + assert_eq!(count_fake_witnesses_with_required_signers( + &Ed25519KeyHashes::new(), + ), 1); + + assert_eq!(count_fake_witnesses_with_required_signers( + &Ed25519KeyHashes::from_vec(vec![fake_key_hash(1)]), + ), 2); + + assert_eq!(count_fake_witnesses_with_required_signers( + &Ed25519KeyHashes::from_vec(vec![fake_key_hash(1), fake_key_hash(2)]), + ), 3); + + // This case still produces only 3 fake signatures, because the same key is already used in the input address + assert_eq!(count_fake_witnesses_with_required_signers( + &Ed25519KeyHashes::from_vec(vec![fake_key_hash(1), fake_key_hash(2), fake_key_hash(0)]), + ), 3); + + // When a different key is used - 4 fake witnesses are produced + assert_eq!(count_fake_witnesses_with_required_signers( + &Ed25519KeyHashes::from_vec(vec![fake_key_hash(1), fake_key_hash(2), fake_key_hash(3)]), + ), 4); + } + } From 3129b3c9d32dc7bac115c1e77c245d907155838e Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Sun, 5 Jun 2022 09:45:13 +0300 Subject: [PATCH 22/22] Warning fixes --- rust/src/lib.rs | 4 ---- rust/src/tx_builder.rs | 8 ++++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 52b4b206..fa5dba8e 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -628,10 +628,6 @@ impl Ed25519KeyHashes { Self(Vec::new()) } - pub(crate) fn from_vec(vec: Vec) -> Self { - Self(vec) - } - pub fn len(&self) -> usize { self.0.len() } diff --git a/rust/src/tx_builder.rs b/rust/src/tx_builder.rs index d1fa8e55..a0e72ae0 100644 --- a/rust/src/tx_builder.rs +++ b/rust/src/tx_builder.rs @@ -5248,21 +5248,21 @@ mod tests { ), 1); assert_eq!(count_fake_witnesses_with_required_signers( - &Ed25519KeyHashes::from_vec(vec![fake_key_hash(1)]), + &Ed25519KeyHashes(vec![fake_key_hash(1)]), ), 2); assert_eq!(count_fake_witnesses_with_required_signers( - &Ed25519KeyHashes::from_vec(vec![fake_key_hash(1), fake_key_hash(2)]), + &Ed25519KeyHashes(vec![fake_key_hash(1), fake_key_hash(2)]), ), 3); // This case still produces only 3 fake signatures, because the same key is already used in the input address assert_eq!(count_fake_witnesses_with_required_signers( - &Ed25519KeyHashes::from_vec(vec![fake_key_hash(1), fake_key_hash(2), fake_key_hash(0)]), + &Ed25519KeyHashes(vec![fake_key_hash(1), fake_key_hash(2), fake_key_hash(0)]), ), 3); // When a different key is used - 4 fake witnesses are produced assert_eq!(count_fake_witnesses_with_required_signers( - &Ed25519KeyHashes::from_vec(vec![fake_key_hash(1), fake_key_hash(2), fake_key_hash(3)]), + &Ed25519KeyHashes(vec![fake_key_hash(1), fake_key_hash(2), fake_key_hash(3)]), ), 4); }