From 36185064d0152dbc4b932164182917ee9513c5f9 Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Sun, 28 Nov 2021 14:13:00 +0300 Subject: [PATCH 01/17] Reworked mint helper function to accept minting script instead of just the hash, and added storing these scripts in the resulting witness set --- rust/src/tx_builder.rs | 352 +++++++++++++++++++++++++++-------------- 1 file changed, 229 insertions(+), 123 deletions(-) diff --git a/rust/src/tx_builder.rs b/rust/src/tx_builder.rs index 0a7d99eb..42c48697 100644 --- a/rust/src/tx_builder.rs +++ b/rust/src/tx_builder.rs @@ -1,7 +1,7 @@ use super::*; use super::fees; use super::utils; -use std::collections::{BTreeMap, BTreeSet}; +use std::collections::{BTreeMap, BTreeSet, HashSet}; // comes from witsVKeyNeeded in the Ledger spec fn witness_keys_for_cert(cert_enum: &Certificate, keys: &mut BTreeSet) { @@ -46,9 +46,9 @@ fn fake_private_key() -> Bip32PrivateKey { ).unwrap() } -fn fake_key_hash() -> Ed25519KeyHash { +fn fake_key_hash(x: u8) -> Ed25519KeyHash { Ed25519KeyHash::from_bytes( - vec![142, 239, 181, 120, 142, 135, 19, 200, 68, 223, 211, 43, 46, 145, 222, 30, 48, 159, 239, 255, 213, 85, 248, 39, 204, 158, 225, 100] + vec![x, 239, 181, 120, 142, 135, 19, 200, 68, 223, 211, 43, 46, 145, 222, 30, 48, 159, 239, 255, 213, 85, 248, 39, 204, 158, 225, 100] ).unwrap() } @@ -57,8 +57,6 @@ fn fake_key_hash() -> Ed25519KeyHash { // for use in calculating the size of the final Transaction fn fake_full_tx(tx_builder: &TransactionBuilder, body: TransactionBody) -> Result { let fake_key_root = fake_private_key(); - let fake_key_hash = fake_key_hash(); - // recall: this includes keys for input, certs and withdrawals let vkeys = match tx_builder.input_types.vkeys.len() { 0 => None, @@ -96,16 +94,13 @@ fn fake_full_tx(tx_builder: &TransactionBuilder, body: TransactionBody) -> Resul Some(result) }, }; - let full_script_keys = match &tx_builder.mint { + let full_script_keys = match &tx_builder.mint_scripts { None => script_keys, - Some(mint) => { + Some(witness_scripts) => { let mut ns = script_keys .map(|sk| { sk.clone() }) .unwrap_or(NativeScripts::new()); - let spk = ScriptPubkey::new(&fake_key_hash); - mint.keys().0.iter().for_each(|_p| { - ns.add(&NativeScript::new_script_pubkey(&spk)); - }); + witness_scripts.0.iter().for_each(|s| { ns.add(s); }); Some(ns) } }; @@ -127,6 +122,20 @@ fn fake_full_tx(tx_builder: &TransactionBuilder, body: TransactionBody) -> Resul } fn min_fee(tx_builder: &TransactionBuilder) -> Result { + if let Some(mint) = tx_builder.mint.as_ref() { + if let Some(witness_scripts) = tx_builder.mint_scripts.as_ref() { + let witness_hashes: HashSet = witness_scripts.0.iter().map(|script| { + script.hash(ScriptHashNamespace::NativeScript) + }).collect(); + for mint_hash in mint.keys().0.iter() { + if !witness_hashes.contains(mint_hash) { + return Err(JsError::from_str(&format!("No witness script is found for mint policy '{:?}'! Impossible to estimate fee", hex::encode(mint_hash.to_bytes())))); + } + } + } else { + return Err(JsError::from_str("Impossible to estimate fee if mint is present in the builder, but witness scripts are not provided!")); + } + } let full_tx = fake_full_tx(tx_builder, tx_builder.build()?)?; fees::min_fee(&full_tx, &tx_builder.config.fee_algo) } @@ -260,6 +269,7 @@ pub struct TransactionBuilder { validity_start_interval: Option, input_types: MockWitnessSet, mint: Option, + mint_scripts: Option, inputs_auto_added: bool, } @@ -638,29 +648,66 @@ impl TransactionBuilder { /// Set explicit Mint object to this builder /// it will replace any previously existing mint + /// NOTE! If you use `set_mint` manually - you must use `set_mint_scripts` + /// to provide matching policy scripts or min-fee calculation will be rejected! pub fn set_mint(&mut self, mint: &Mint) { self.mint = Some(mint.clone()); } - /// Add a mint entry to this builder using a PolicyID and MintAssets object - /// It will be securely added to existing or new Mint in this builder - /// It will replace any existing mint assets with the same PolicyID - pub fn set_mint_asset(&mut self, policy_id: &PolicyID, mint_assets: &MintAssets) { + /// Returns a copy of the current mint state in the builder + pub fn get_mint(&self) -> Option { + self.mint.clone() + } + + /// Set explicit witness set to this builder + /// It will replace any previously existing witnesses + /// NOTE! Use carefully! If you are using `set_mint` - then you must be using + /// this setter as well to be able to calculate fee automatically! + pub fn set_mint_scripts(&mut self, mint_scripts: &NativeScripts) { + self.mint_scripts = Some(mint_scripts.clone()); + } + + /// Returns a copy of the current mint witness scripts in the builder + pub fn get_mint_scripts(&self) -> Option { + self.mint_scripts.clone() + } + + fn _set_mint_asset(&mut self, policy_id: &PolicyID, policy_script: &NativeScript, mint_assets: &MintAssets) { let mut mint = self.mint.as_ref().cloned().unwrap_or(Mint::new()); - mint.insert(policy_id, mint_assets); + let is_new_policy = mint.insert(&policy_id, mint_assets).is_none(); self.set_mint(&mint); + if is_new_policy { + // If policy has not been encountered before - insert the script into witnesses + let mut witness_scripts = self.mint_scripts.as_ref().cloned() + .unwrap_or(NativeScripts::new()); + witness_scripts.add(&policy_script.clone()); + self.set_mint_scripts(&witness_scripts); + } } - /// Add a mint entry to this builder using a PolicyID, AssetName, and Int object for amount + /// Add a mint entry to this builder using a PolicyID and MintAssets object /// It will be securely added to existing or new Mint in this builder - /// It will replace any previous existing amount same PolicyID and AssetName - pub fn add_mint_asset(&mut self, policy_id: &PolicyID, asset_name: &AssetName, amount: Int) { + /// It will replace any existing mint assets with the same PolicyID + pub fn set_mint_asset(&mut self, policy_script: &NativeScript, mint_assets: &MintAssets) { + let policy_id: PolicyID = policy_script.hash(ScriptHashNamespace::NativeScript); + self._set_mint_asset(&policy_id, policy_script, mint_assets); + } + + fn _add_mint_asset(&mut self, policy_id: &PolicyID, policy_script: &NativeScript, asset_name: &AssetName, amount: Int) { let mut asset = self.mint.as_ref() - .map(|m| { m.get(policy_id).as_ref().cloned() }) + .map(|m| { m.get(&policy_id).as_ref().cloned() }) .unwrap_or(None) .unwrap_or(MintAssets::new()); asset.insert(asset_name, amount); - self.set_mint_asset(policy_id, &asset); + self._set_mint_asset(&policy_id, policy_script, &asset); + } + + /// Add a mint entry to this builder using a PolicyID, AssetName, and Int object for amount + /// It will be securely added to existing or new Mint in this builder + /// It will replace any previous existing amount same PolicyID and AssetName + pub fn add_mint_asset(&mut self, policy_script: &NativeScript, asset_name: &AssetName, amount: Int) { + let policy_id: PolicyID = policy_script.hash(ScriptHashNamespace::NativeScript); + self._add_mint_asset(&policy_id, policy_script, asset_name, amount); } /// Add a mint entry together with an output to this builder @@ -669,7 +716,7 @@ impl TransactionBuilder { /// A new output will be added with the specified Address, the Coin value, and the minted asset pub fn add_mint_asset_and_output( &mut self, - policy_id: &PolicyID, + policy_script: &NativeScript, asset_name: &AssetName, amount: Int, address: &Address, @@ -678,9 +725,10 @@ impl TransactionBuilder { if !amount.is_positive() { return Err(JsError::from_str("Output value must be positive!")); } - self.add_mint_asset(policy_id, asset_name, amount.clone()); + let policy_id: PolicyID = policy_script.hash(ScriptHashNamespace::NativeScript); + self._add_mint_asset(&policy_id, policy_script, asset_name, amount.clone()); let multiasset = Mint::new_from_entry( - policy_id, + &policy_id, &MintAssets::new_from_entry(asset_name, amount.clone()) ).as_positive_multiasset(); self.add_output_coin_and_asset(address, output_coin, &multiasset) @@ -693,7 +741,7 @@ impl TransactionBuilder { /// The output will be set to contain the minimum required amount of Coin pub fn add_mint_asset_and_output_min_required_coin( &mut self, - policy_id: &PolicyID, + policy_script: &NativeScript, asset_name: &AssetName, amount: Int, address: &Address, @@ -701,9 +749,10 @@ impl TransactionBuilder { if !amount.is_positive() { return Err(JsError::from_str("Output value must be positive!")); } - self.add_mint_asset(policy_id, asset_name, amount.clone()); + let policy_id: PolicyID = policy_script.hash(ScriptHashNamespace::NativeScript); + self._add_mint_asset(&policy_id, policy_script, asset_name, amount.clone()); let multiasset = Mint::new_from_entry( - policy_id, + &policy_id, &MintAssets::new_from_entry(asset_name, amount.clone()) ).as_positive_multiasset(); self.add_output_asset_and_min_required_coin(address, &multiasset) @@ -726,6 +775,7 @@ impl TransactionBuilder { }, validity_start_interval: None, mint: None, + mint_scripts: None, inputs_auto_added: false, } } @@ -1049,7 +1099,7 @@ impl TransactionBuilder { /// Returns object the body of the new transaction /// Auxiliary data itself is not included - /// You can use `get_auxiliary_date` or `build_tx` + /// You can use `get_auxiliary_data` or `build_tx` pub fn build(&self) -> Result { let (body, full_tx_size) = self.build_and_size()?; if full_tx_size > self.config.max_tx_size as usize { @@ -1063,13 +1113,21 @@ impl TransactionBuilder { } } + fn get_witness_set(&self) -> TransactionWitnessSet { + let mut wit = TransactionWitnessSet::new(); + if let Some(scripts) = self.mint_scripts.as_ref() { + wit.set_native_scripts(scripts); + } + wit + } + /// Returns full Transaction object with the body and the auxiliary data - /// NOTE: witness_set is set to just empty set + /// NOTE: witness_set will contain all mint_scripts if any been added or set /// NOTE: is_valid set to true pub fn build_tx(&self) -> Result { Ok(Transaction { body: self.build()?, - witness_set: TransactionWitnessSet::new(), + witness_set: self.get_witness_set(), is_valid: true, auxiliary_data: self.auxiliary_data.clone(), }) @@ -1656,12 +1714,12 @@ mod tests { ) .to_address(); - let policy_id = PolicyID::from([0u8; 28]); + let (min_script, policy_id) = mint_script_and_policy(0); let name = AssetName::new(vec![0u8, 1, 2, 3]).unwrap(); let amount = to_bignum(1234); // Adding mint of the asset - which should work as an input - tx_builder.add_mint_asset(&policy_id, &name, Int::new(&amount)); + tx_builder.add_mint_asset(&min_script, &name, Int::new(&amount)); let mut ass = Assets::new(); ass.insert(&name, &amount); @@ -1739,14 +1797,14 @@ mod tests { ) .to_address(); - let policy_id = PolicyID::from([0u8; 28]); + let (min_script, policy_id) = mint_script_and_policy(0); let name = AssetName::new(vec![0u8, 1, 2, 3]).unwrap(); let amount_minted = to_bignum(1000); let amount_sent = to_bignum(500); // Adding mint of the asset - which should work as an input - tx_builder.add_mint_asset(&policy_id, &name, Int::new(&amount_minted)); + tx_builder.add_mint_asset(&min_script, &name, Int::new(&amount_minted)); let mut ass = Assets::new(); ass.insert(&name, &amount_sent); @@ -3135,72 +3193,104 @@ mod tests { assert_eq!(result_asset.get(&create_asset_name()).unwrap(), Int::new_i32(1234)); } + fn mint_script_and_policy(x: u8) -> (NativeScript, PolicyID) { + let mint_script = NativeScript::new_script_pubkey( + &ScriptPubkey::new(&fake_key_hash(x)) + ); + let policy_id = mint_script.hash(ScriptHashNamespace::NativeScript); + (mint_script, policy_id) + } + #[test] fn set_mint_asset_with_empty_mint() { let mut tx_builder = create_default_tx_builder(); - let policy_id = PolicyID::from([0u8; 28]); - tx_builder.set_mint_asset(&policy_id, &create_mint_asset()); + let (mint_script, policy_id) = mint_script_and_policy(0); + tx_builder.set_mint_asset(&mint_script, &create_mint_asset()); assert!(tx_builder.mint.is_some()); + assert!(tx_builder.mint_scripts.is_some()); let mint = tx_builder.mint.unwrap(); + let mint_scripts = tx_builder.mint_scripts.unwrap(); assert_eq!(mint.len(), 1); assert_mint_asset(&mint, &policy_id); + + assert_eq!(mint_scripts.len(), 1); + assert_eq!(mint_scripts.get(0), mint_script); } #[test] fn set_mint_asset_with_existing_mint() { let mut tx_builder = create_default_tx_builder(); - let policy_id1 = PolicyID::from([0u8; 28]); + let (_, policy_id1) = mint_script_and_policy(0); + let (mint_script2, policy_id2) = mint_script_and_policy(1); + tx_builder.set_mint(&create_mint_with_one_asset(&policy_id1)); - let policy_id2 = PolicyID::from([1u8; 28]); - tx_builder.set_mint_asset(&policy_id2, &create_mint_asset()); + tx_builder.set_mint_asset(&mint_script2, &create_mint_asset()); assert!(tx_builder.mint.is_some()); + assert!(tx_builder.mint_scripts.is_some()); let mint = tx_builder.mint.unwrap(); + let mint_scripts = tx_builder.mint_scripts.unwrap(); assert_eq!(mint.len(), 2); assert_mint_asset(&mint, &policy_id1); assert_mint_asset(&mint, &policy_id2); + + // Only second script is present in the scripts + assert_eq!(mint_scripts.len(), 1); + assert_eq!(mint_scripts.get(0), mint_script2); } #[test] fn add_mint_asset_with_empty_mint() { let mut tx_builder = create_default_tx_builder(); - let policy_id = PolicyID::from([0u8; 28]); - tx_builder.add_mint_asset(&policy_id, &create_asset_name(), Int::new_i32(1234)); + let (mint_script, policy_id) = mint_script_and_policy(0); + + tx_builder.add_mint_asset(&mint_script, &create_asset_name(), Int::new_i32(1234)); assert!(tx_builder.mint.is_some()); + assert!(tx_builder.mint_scripts.is_some()); let mint = tx_builder.mint.unwrap(); + let mint_scripts = tx_builder.mint_scripts.unwrap(); assert_eq!(mint.len(), 1); assert_mint_asset(&mint, &policy_id); + + assert_eq!(mint_scripts.len(), 1); + assert_eq!(mint_scripts.get(0), mint_script); } #[test] fn add_mint_asset_with_existing_mint() { let mut tx_builder = create_default_tx_builder(); - let policy_id1 = PolicyID::from([0u8; 28]); - tx_builder.set_mint(&create_mint_with_one_asset(&policy_id1)); + let (_, policy_id1) = mint_script_and_policy(0); + let (mint_script2, policy_id2) = mint_script_and_policy(1); - let policy_id2 = PolicyID::from([1u8; 28]); - tx_builder.add_mint_asset(&policy_id2, &create_asset_name(), Int::new_i32(1234)); + tx_builder.set_mint(&create_mint_with_one_asset(&policy_id1)); + tx_builder.add_mint_asset(&mint_script2, &create_asset_name(), Int::new_i32(1234)); assert!(tx_builder.mint.is_some()); + assert!(tx_builder.mint_scripts.is_some()); let mint = tx_builder.mint.unwrap(); + let mint_scripts = tx_builder.mint_scripts.unwrap(); assert_eq!(mint.len(), 2); assert_mint_asset(&mint, &policy_id1); assert_mint_asset(&mint, &policy_id2); + + // Only second script is present in the scripts + assert_eq!(mint_scripts.len(), 1); + assert_eq!(mint_scripts.get(0), mint_script2); } #[test] @@ -3280,8 +3370,9 @@ mod tests { fn add_mint_asset_and_output() { let mut tx_builder = create_default_tx_builder(); - let policy_id0 = PolicyID::from([0u8; 28]); - let policy_id1 = PolicyID::from([1u8; 28]); + let (mint_script0, policy_id0) = mint_script_and_policy(0); + let (mint_script1, policy_id1) = mint_script_and_policy(1); + let name = create_asset_name(); let amount = Int::new_i32(1234); @@ -3289,10 +3380,10 @@ mod tests { let coin = to_bignum(100); // Add unrelated mint first to check it is NOT added to output later - tx_builder.add_mint_asset(&policy_id0, &name, amount.clone()); + tx_builder.add_mint_asset(&mint_script0, &name, amount.clone()); tx_builder.add_mint_asset_and_output( - &policy_id1, + &mint_script1, &name, amount.clone(), &address, @@ -3300,14 +3391,20 @@ mod tests { ).unwrap(); assert!(tx_builder.mint.is_some()); + assert!(tx_builder.mint_scripts.is_some()); let mint = tx_builder.mint.as_ref().unwrap(); + let mint_scripts = tx_builder.mint_scripts.as_ref().unwrap(); // Mint contains two entries assert_eq!(mint.len(), 2); assert_mint_asset(mint, &policy_id0); assert_mint_asset(mint, &policy_id1); + assert_eq!(mint_scripts.len(), 2); + assert_eq!(mint_scripts.get(0), mint_script0); + assert_eq!(mint_scripts.get(1), mint_script1); + // One new output is created assert_eq!(tx_builder.outputs.len(), 1); let out = tx_builder.outputs.get(0); @@ -3331,32 +3428,40 @@ mod tests { fn add_mint_asset_and_min_required_coin() { let mut tx_builder = create_reallistic_tx_builder(); - let policy_id0 = PolicyID::from([0u8; 28]); - let policy_id1 = PolicyID::from([1u8; 28]); + let (mint_script0, policy_id0) = mint_script_and_policy(0); + let (mint_script1, policy_id1) = mint_script_and_policy(1); + let name = create_asset_name(); let amount = Int::new_i32(1234); let address = byron_address(); // Add unrelated mint first to check it is NOT added to output later - tx_builder.add_mint_asset(&policy_id0, &name, amount.clone()); + tx_builder.add_mint_asset(&mint_script0, &name, amount.clone()); tx_builder.add_mint_asset_and_output_min_required_coin( - &policy_id1, + &mint_script1, &name, amount.clone(), &address, ).unwrap(); assert!(tx_builder.mint.is_some()); + assert!(tx_builder.mint_scripts.is_some()); let mint = tx_builder.mint.as_ref().unwrap(); + let mint_scripts = tx_builder.mint_scripts.as_ref().unwrap(); // Mint contains two entries assert_eq!(mint.len(), 2); assert_mint_asset(mint, &policy_id0); assert_mint_asset(mint, &policy_id1); + + assert_eq!(mint_scripts.len(), 2); + assert_eq!(mint_scripts.get(0), mint_script0); + assert_eq!(mint_scripts.get(1), mint_script1); + // One new output is created assert_eq!(tx_builder.outputs.len(), 1); let out = tx_builder.outputs.get(0); @@ -3377,66 +3482,66 @@ mod tests { } - #[test] - fn add_mint_includes_witnesses_into_fee_estimation() { - let mut tx_builder = create_reallistic_tx_builder(); - - let original_tx_fee = tx_builder.min_fee().unwrap(); - assert_eq!(original_tx_fee, to_bignum(156217)); - - let policy_id1 = PolicyID::from([0u8; 28]); - let policy_id2 = PolicyID::from([1u8; 28]); - let policy_id3 = PolicyID::from([2u8; 28]); - let name1 = AssetName::new(vec![0u8, 1, 2, 3]).unwrap(); - let name2 = AssetName::new(vec![1u8, 1, 2, 3]).unwrap(); - let name3 = AssetName::new(vec![2u8, 1, 2, 3]).unwrap(); - let name4 = AssetName::new(vec![3u8, 1, 2, 3]).unwrap(); - let amount = Int::new_i32(1234); - - let mut mint = Mint::new(); - mint.insert( - &policy_id1, - &MintAssets::new_from_entry(&name1, amount.clone()), - ); - mint.insert( - &policy_id2, - &MintAssets::new_from_entry(&name2, amount.clone()), - ); - // Third policy with two asset names - let mut mass = MintAssets::new_from_entry(&name3, amount.clone()); - mass.insert(&name4, amount.clone()); - mint.insert(&policy_id3, &mass); - - let mint_len = mint.to_bytes().len(); - let fee_coefficient = tx_builder.config.fee_algo.coefficient(); - - let raw_mint_fee = fee_coefficient - .checked_mul(&to_bignum(mint_len as u64)) - .unwrap(); - - assert_eq!(raw_mint_fee, to_bignum(5544)); - - tx_builder.set_mint(&mint); - - let new_tx_fee = tx_builder.min_fee().unwrap(); - - let fee_diff_from_adding_mint = - new_tx_fee.checked_sub(&original_tx_fee) - .unwrap(); - - let witness_fee_increase = - fee_diff_from_adding_mint.checked_sub(&raw_mint_fee) - .unwrap(); - - assert_eq!(witness_fee_increase, to_bignum(4356)); - - let fee_increase_bytes = from_bignum(&witness_fee_increase) - .checked_div(from_bignum(&fee_coefficient)) - .unwrap(); - - // Three policy IDs of 32 bytes each + 3 byte overhead - assert_eq!(fee_increase_bytes, 99); - } + // #[test] + // fn add_mint_includes_witnesses_into_fee_estimation() { + // let mut tx_builder = create_reallistic_tx_builder(); + // + // let original_tx_fee = tx_builder.min_fee().unwrap(); + // assert_eq!(original_tx_fee, to_bignum(156217)); + // + // let policy_id1 = PolicyID::from([0u8; 28]); + // let policy_id2 = PolicyID::from([1u8; 28]); + // let policy_id3 = PolicyID::from([2u8; 28]); + // let name1 = AssetName::new(vec![0u8, 1, 2, 3]).unwrap(); + // let name2 = AssetName::new(vec![1u8, 1, 2, 3]).unwrap(); + // let name3 = AssetName::new(vec![2u8, 1, 2, 3]).unwrap(); + // let name4 = AssetName::new(vec![3u8, 1, 2, 3]).unwrap(); + // let amount = Int::new_i32(1234); + // + // let mut mint = Mint::new(); + // mint.insert( + // &policy_id1, + // &MintAssets::new_from_entry(&name1, amount.clone()), + // ); + // mint.insert( + // &policy_id2, + // &MintAssets::new_from_entry(&name2, amount.clone()), + // ); + // // Third policy with two asset names + // let mut mass = MintAssets::new_from_entry(&name3, amount.clone()); + // mass.insert(&name4, amount.clone()); + // mint.insert(&policy_id3, &mass); + // + // let mint_len = mint.to_bytes().len(); + // let fee_coefficient = tx_builder.config.fee_algo.coefficient(); + // + // let raw_mint_fee = fee_coefficient + // .checked_mul(&to_bignum(mint_len as u64)) + // .unwrap(); + // + // assert_eq!(raw_mint_fee, to_bignum(5544)); + // + // tx_builder.set_mint(&mint); + // + // let new_tx_fee = tx_builder.min_fee().unwrap(); + // + // let fee_diff_from_adding_mint = + // new_tx_fee.checked_sub(&original_tx_fee) + // .unwrap(); + // + // let witness_fee_increase = + // fee_diff_from_adding_mint.checked_sub(&raw_mint_fee) + // .unwrap(); + // + // assert_eq!(witness_fee_increase, to_bignum(4356)); + // + // let fee_increase_bytes = from_bignum(&witness_fee_increase) + // .checked_div(from_bignum(&fee_coefficient)) + // .unwrap(); + // + // // Three policy IDs of 32 bytes each + 3 byte overhead + // assert_eq!(fee_increase_bytes, 99); + // } #[test] fn total_input_with_mint_and_burn() { @@ -3449,8 +3554,9 @@ mod tests { .derive(0) .to_public(); - let policy_id1 = &PolicyID::from([0u8; 28]); - let policy_id2 = &PolicyID::from([1u8; 28]); + let (mint_script1, policy_id1) = mint_script_and_policy(0); + let (mint_script2, policy_id2) = mint_script_and_policy(1); + let name = AssetName::new(vec![0u8, 1, 2, 3]).unwrap(); let ma_input1 = 100; @@ -3461,12 +3567,12 @@ mod tests { .iter() .map(|input| { let mut multiasset = MultiAsset::new(); - multiasset.insert(policy_id1, &{ + multiasset.insert(&policy_id1, &{ let mut assets = Assets::new(); assets.insert(&name, &to_bignum(*input)); assets }); - multiasset.insert(policy_id2, &{ + multiasset.insert(&policy_id2, &{ let mut assets = Assets::new(); assets.insert(&name, &to_bignum(*input)); assets @@ -3493,18 +3599,18 @@ mod tests { assert_eq!(total_input_before_mint.coin, to_bignum(300)); let ma1 = total_input_before_mint.multiasset.unwrap(); - assert_eq!(ma1.get(policy_id1).unwrap().get(&name).unwrap(), to_bignum(360)); - assert_eq!(ma1.get(policy_id2).unwrap().get(&name).unwrap(), to_bignum(360)); + assert_eq!(ma1.get(&policy_id1).unwrap().get(&name).unwrap(), to_bignum(360)); + assert_eq!(ma1.get(&policy_id2).unwrap().get(&name).unwrap(), to_bignum(360)); - tx_builder.add_mint_asset(policy_id1, &name, Int::new_i32(40)); - tx_builder.add_mint_asset(policy_id2, &name, Int::new_i32(-40)); + tx_builder.add_mint_asset(&mint_script1, &name, Int::new_i32(40)); + tx_builder.add_mint_asset(&mint_script2, &name, Int::new_i32(-40)); let total_input_after_mint = tx_builder.get_total_input().unwrap(); assert_eq!(total_input_after_mint.coin, to_bignum(300)); let ma2 = total_input_after_mint.multiasset.unwrap(); - assert_eq!(ma2.get(policy_id1).unwrap().get(&name).unwrap(), to_bignum(400)); - assert_eq!(ma2.get(policy_id2).unwrap().get(&name).unwrap(), to_bignum(320)); + assert_eq!(ma2.get(&policy_id1).unwrap().get(&name).unwrap(), to_bignum(400)); + assert_eq!(ma2.get(&policy_id2).unwrap().get(&name).unwrap(), to_bignum(320)); } } From edbab5f1780cddd2ce9ce809dd9873cba728893d Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Sun, 28 Nov 2021 14:26:34 +0300 Subject: [PATCH 02/17] Fixed test for fee estimation --- rust/src/tx_builder.rs | 112 +++++++++++++++++++---------------------- 1 file changed, 52 insertions(+), 60 deletions(-) diff --git a/rust/src/tx_builder.rs b/rust/src/tx_builder.rs index 42c48697..3bcb2c75 100644 --- a/rust/src/tx_builder.rs +++ b/rust/src/tx_builder.rs @@ -3481,67 +3481,59 @@ mod tests { assert_eq!(asset.get(&name).unwrap(), to_bignum(1234)); } + #[test] + fn add_mint_includes_witnesses_into_fee_estimation() { + let mut tx_builder = create_reallistic_tx_builder(); + + let original_tx_fee = tx_builder.min_fee().unwrap(); + assert_eq!(original_tx_fee, to_bignum(156217)); + + let (mint_script1, policy_id1) = mint_script_and_policy(0); + let (mint_script2, policy_id2) = mint_script_and_policy(1); + let (mint_script3, policy_id3) = mint_script_and_policy(2); + + let name1 = AssetName::new(vec![0u8, 1, 2, 3]).unwrap(); + let name2 = AssetName::new(vec![1u8, 1, 2, 3]).unwrap(); + let name3 = AssetName::new(vec![2u8, 1, 2, 3]).unwrap(); + let name4 = AssetName::new(vec![3u8, 1, 2, 3]).unwrap(); + let amount = Int::new_i32(1234); + + tx_builder.add_mint_asset(&mint_script1, &name1, amount.clone()); + tx_builder.add_mint_asset(&mint_script2, &name2, amount.clone()); + tx_builder.add_mint_asset(&mint_script3, &name3, amount.clone()); + tx_builder.add_mint_asset(&mint_script3, &name4, amount.clone()); + + let mint = tx_builder.get_mint().unwrap(); + + let mint_len = mint.to_bytes().len(); + + let fee_coefficient = tx_builder.config.fee_algo.coefficient(); - // #[test] - // fn add_mint_includes_witnesses_into_fee_estimation() { - // let mut tx_builder = create_reallistic_tx_builder(); - // - // let original_tx_fee = tx_builder.min_fee().unwrap(); - // assert_eq!(original_tx_fee, to_bignum(156217)); - // - // let policy_id1 = PolicyID::from([0u8; 28]); - // let policy_id2 = PolicyID::from([1u8; 28]); - // let policy_id3 = PolicyID::from([2u8; 28]); - // let name1 = AssetName::new(vec![0u8, 1, 2, 3]).unwrap(); - // let name2 = AssetName::new(vec![1u8, 1, 2, 3]).unwrap(); - // let name3 = AssetName::new(vec![2u8, 1, 2, 3]).unwrap(); - // let name4 = AssetName::new(vec![3u8, 1, 2, 3]).unwrap(); - // let amount = Int::new_i32(1234); - // - // let mut mint = Mint::new(); - // mint.insert( - // &policy_id1, - // &MintAssets::new_from_entry(&name1, amount.clone()), - // ); - // mint.insert( - // &policy_id2, - // &MintAssets::new_from_entry(&name2, amount.clone()), - // ); - // // Third policy with two asset names - // let mut mass = MintAssets::new_from_entry(&name3, amount.clone()); - // mass.insert(&name4, amount.clone()); - // mint.insert(&policy_id3, &mass); - // - // let mint_len = mint.to_bytes().len(); - // let fee_coefficient = tx_builder.config.fee_algo.coefficient(); - // - // let raw_mint_fee = fee_coefficient - // .checked_mul(&to_bignum(mint_len as u64)) - // .unwrap(); - // - // assert_eq!(raw_mint_fee, to_bignum(5544)); - // - // tx_builder.set_mint(&mint); - // - // let new_tx_fee = tx_builder.min_fee().unwrap(); - // - // let fee_diff_from_adding_mint = - // new_tx_fee.checked_sub(&original_tx_fee) - // .unwrap(); - // - // let witness_fee_increase = - // fee_diff_from_adding_mint.checked_sub(&raw_mint_fee) - // .unwrap(); - // - // assert_eq!(witness_fee_increase, to_bignum(4356)); - // - // let fee_increase_bytes = from_bignum(&witness_fee_increase) - // .checked_div(from_bignum(&fee_coefficient)) - // .unwrap(); - // - // // Three policy IDs of 32 bytes each + 3 byte overhead - // assert_eq!(fee_increase_bytes, 99); - // } + let raw_mint_fee = fee_coefficient + .checked_mul(&to_bignum(mint_len as u64)) + .unwrap(); + + assert_eq!(raw_mint_fee, to_bignum(5544)); + + let new_tx_fee = tx_builder.min_fee().unwrap(); + + let fee_diff_from_adding_mint = + new_tx_fee.checked_sub(&original_tx_fee) + .unwrap(); + + let witness_fee_increase = + fee_diff_from_adding_mint.checked_sub(&raw_mint_fee) + .unwrap(); + + assert_eq!(witness_fee_increase, to_bignum(4356)); + + let fee_increase_bytes = from_bignum(&witness_fee_increase) + .checked_div(from_bignum(&fee_coefficient)) + .unwrap(); + + // Three pubkey scripts of 32 bytes each + 3 byte overhead + assert_eq!(fee_increase_bytes, 99); + } #[test] fn total_input_with_mint_and_burn() { From b6de84d85572c3604e73e3f409658c5f9f18d50a Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Mon, 29 Nov 2021 11:49:02 +0300 Subject: [PATCH 03/17] Added test that fee-estimation fails when mint scripts are not provided --- rust/src/tx_builder.rs | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/rust/src/tx_builder.rs b/rust/src/tx_builder.rs index 3bcb2c75..a640da95 100644 --- a/rust/src/tx_builder.rs +++ b/rust/src/tx_builder.rs @@ -3535,6 +3535,47 @@ mod tests { assert_eq!(fee_increase_bytes, 99); } + #[test] + fn fee_estimation_fails_on_missing_mint_scripts() { + let mut tx_builder = create_reallistic_tx_builder(); + + // No error estimating fee without mint + assert!(tx_builder.min_fee().is_ok()); + + let (mint_script1, policy_id1) = mint_script_and_policy(0); + let (mint_script2, policy_id2) = mint_script_and_policy(1); + + let name1 = AssetName::new(vec![0u8, 1, 2, 3]).unwrap(); + let amount = Int::new_i32(1234); + + let mut mint = Mint::new(); + mint.insert( + &policy_id1, + &MintAssets::new_from_entry(&name1, amount.clone()), + ); + + tx_builder.set_mint(&mint); + + // Mint exists but no witness scripts at all present + let est1 = tx_builder.min_fee(); + assert!(est1.is_err()); + assert!(est1.err().unwrap().to_string().contains("witness scripts are not provided")); + + tx_builder.add_mint_asset(&mint_script2, &name1, amount.clone()); + + // Now two different policies are minted but only one witness script is present + let est2 = tx_builder.min_fee(); + assert!(est2.is_err()); + assert!(est2.err().unwrap().to_string().contains(&format!("{:?}", hex::encode(policy_id1.to_bytes())))); + + let mut scripts = tx_builder.get_mint_scripts().unwrap(); + scripts.add(&mint_script1); + tx_builder.set_mint_scripts(&scripts); + + let est3 = tx_builder.min_fee(); + assert!(est3.is_ok()) + } + #[test] fn total_input_with_mint_and_burn() { let mut tx_builder = create_tx_builder_with_fee(&create_linear_fee(0, 1)); From 276334a4ca0dcf45766d8ebced8c6bc06c2d6bcd Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Mon, 29 Nov 2021 14:52:21 +0300 Subject: [PATCH 04/17] Fixed fee-estimation to add vkey witnesses for each minting policy and updated a test for it --- rust/src/tx_builder.rs | 101 +++++++++++++++++++++++++++++------------ 1 file changed, 73 insertions(+), 28 deletions(-) diff --git a/rust/src/tx_builder.rs b/rust/src/tx_builder.rs index a640da95..fc155ea4 100644 --- a/rust/src/tx_builder.rs +++ b/rust/src/tx_builder.rs @@ -57,17 +57,18 @@ fn fake_key_hash(x: u8) -> Ed25519KeyHash { // for use in calculating the size of the final Transaction fn fake_full_tx(tx_builder: &TransactionBuilder, body: TransactionBody) -> Result { let fake_key_root = fake_private_key(); + let raw_key = fake_key_root.to_raw_key(); + let fake_vkey_witness = Vkeywitness::new( + &Vkey::new(&raw_key.to_public()), + &raw_key.sign([1u8; 100].as_ref()) + ); // recall: this includes keys for input, certs and withdrawals let vkeys = match tx_builder.input_types.vkeys.len() { 0 => None, x => { let mut result = Vkeywitnesses::new(); - let raw_key = fake_key_root.to_raw_key(); for _i in 0..x { - result.add(&Vkeywitness::new( - &Vkey::new(&raw_key.to_public()), - &raw_key.sign([1u8; 100].as_ref()) - )); + result.add(&fake_vkey_witness.clone()); } Some(result) }, @@ -94,18 +95,24 @@ fn fake_full_tx(tx_builder: &TransactionBuilder, body: TransactionBody) -> Resul Some(result) }, }; - let full_script_keys = match &tx_builder.mint_scripts { - None => script_keys, + let (full_vkeys, full_script_keys) = match &tx_builder.mint_scripts { + None => (vkeys, script_keys), Some(witness_scripts) => { + let mut vw = vkeys + .map(|x| { x.clone() }) + .unwrap_or(Vkeywitnesses::new()); let mut ns = script_keys - .map(|sk| { sk.clone() }) + .map(|x| { x.clone() }) .unwrap_or(NativeScripts::new()); - witness_scripts.0.iter().for_each(|s| { ns.add(s); }); - Some(ns) + witness_scripts.0.iter().for_each(|s| { + vw.add(&fake_vkey_witness.clone()); + ns.add(s); + }); + (Some(vw), Some(ns)) } }; let witness_set = TransactionWitnessSet { - vkeys: vkeys, + vkeys: full_vkeys, native_scripts: full_script_keys, bootstraps: bootstrap_keys, // TODO: plutus support? @@ -3193,12 +3200,18 @@ mod tests { assert_eq!(result_asset.get(&create_asset_name()).unwrap(), Int::new_i32(1234)); } - fn mint_script_and_policy(x: u8) -> (NativeScript, PolicyID) { + fn mint_script_and_policy_and_hash(x: u8) -> (NativeScript, PolicyID, Ed25519KeyHash) { + let hash = fake_key_hash(x); let mint_script = NativeScript::new_script_pubkey( - &ScriptPubkey::new(&fake_key_hash(x)) + &ScriptPubkey::new(&hash) ); let policy_id = mint_script.hash(ScriptHashNamespace::NativeScript); - (mint_script, policy_id) + (mint_script, policy_id, hash) + } + + fn mint_script_and_policy(x: u8) -> (NativeScript, PolicyID) { + let (m, p, _) = mint_script_and_policy_and_hash(x); + (m, p) } #[test] @@ -3483,14 +3496,14 @@ mod tests { #[test] fn add_mint_includes_witnesses_into_fee_estimation() { + let mut tx_builder = create_reallistic_tx_builder(); - let original_tx_fee = tx_builder.min_fee().unwrap(); - assert_eq!(original_tx_fee, to_bignum(156217)); + let hash0 = fake_key_hash(0); - let (mint_script1, policy_id1) = mint_script_and_policy(0); - let (mint_script2, policy_id2) = mint_script_and_policy(1); - let (mint_script3, policy_id3) = mint_script_and_policy(2); + let (mint_script1, policy_id1, hash1) = mint_script_and_policy_and_hash(1); + let (mint_script2, policy_id2, hash2) = mint_script_and_policy_and_hash(2); + let (mint_script3, policy_id3, hash3) = mint_script_and_policy_and_hash(3); let name1 = AssetName::new(vec![0u8, 1, 2, 3]).unwrap(); let name2 = AssetName::new(vec![1u8, 1, 2, 3]).unwrap(); @@ -3498,41 +3511,73 @@ mod tests { let name4 = AssetName::new(vec![3u8, 1, 2, 3]).unwrap(); let amount = Int::new_i32(1234); + // One input from unrelated address + tx_builder.add_key_input( + &hash0, + &TransactionInput::new(&genesis_id(), 0), + &Value::new(&to_bignum(10_000_000)), + ); + + // One input from same address as mint + tx_builder.add_key_input( + &hash1, + &TransactionInput::new(&genesis_id(), 0), + &Value::new(&to_bignum(10_000_000)), + ); + + // Original tx fee now assumes two VKey signatures for two inputs + let original_tx_fee = tx_builder.min_fee().unwrap(); + assert_eq!(original_tx_fee, to_bignum(168361)); + + // Add minting four assets from three different policies tx_builder.add_mint_asset(&mint_script1, &name1, amount.clone()); tx_builder.add_mint_asset(&mint_script2, &name2, amount.clone()); tx_builder.add_mint_asset(&mint_script3, &name3, amount.clone()); tx_builder.add_mint_asset(&mint_script3, &name4, amount.clone()); let mint = tx_builder.get_mint().unwrap(); - let mint_len = mint.to_bytes().len(); + let mint_scripts = tx_builder.get_witness_set(); + let mint_scripts_len = mint_scripts.to_bytes().len() + - TransactionWitnessSet::new().to_bytes().len(); + let fee_coefficient = tx_builder.config.fee_algo.coefficient(); let raw_mint_fee = fee_coefficient .checked_mul(&to_bignum(mint_len as u64)) .unwrap(); + let raw_mint_script_fee = fee_coefficient + .checked_mul(&to_bignum(mint_scripts_len as u64)) + .unwrap(); + assert_eq!(raw_mint_fee, to_bignum(5544)); + assert_eq!(raw_mint_script_fee, to_bignum(4312)); let new_tx_fee = tx_builder.min_fee().unwrap(); - let fee_diff_from_adding_mint = - new_tx_fee.checked_sub(&original_tx_fee) + let fee_diff_from_adding_mint = new_tx_fee + .checked_sub(&original_tx_fee) .unwrap(); - let witness_fee_increase = - fee_diff_from_adding_mint.checked_sub(&raw_mint_fee) - .unwrap(); + let witness_fee_increase = fee_diff_from_adding_mint + .checked_sub(&raw_mint_fee).unwrap() + .checked_sub(&raw_mint_script_fee).unwrap(); - assert_eq!(witness_fee_increase, to_bignum(4356)); + assert_eq!(witness_fee_increase, to_bignum(13376)); let fee_increase_bytes = from_bignum(&witness_fee_increase) .checked_div(from_bignum(&fee_coefficient)) .unwrap(); - // Three pubkey scripts of 32 bytes each + 3 byte overhead - assert_eq!(fee_increase_bytes, 99); + // Three vkey witnesses 96 bytes each (32 byte pubkey + 64 byte signature) + // Plus 16 bytes overhead for CBOR wrappers + // This is happening because we have three different minting policies + // and we are assuming each one needs a separate signature even tho + // they might refer to the same address with another policy or one of the inputs + // + assert_eq!(fee_increase_bytes, 304); } #[test] From 91bbbb81df306564a576f9528fe672666af7aeaa Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Tue, 30 Nov 2021 11:57:46 +0300 Subject: [PATCH 05/17] Flowgen update. Warnings fixed. Version bump to beta7 --- package-lock.json | 2 +- package.json | 2 +- rust/Cargo.lock | 2 +- rust/Cargo.toml | 2 +- rust/pkg/cardano_serialization_lib.js.flow | 43 +++++++++++++++++----- rust/src/tx_builder.rs | 20 +++++----- 6 files changed, 47 insertions(+), 24 deletions(-) diff --git a/package-lock.json b/package-lock.json index c130067d..d25ec6bd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "cardano-serialization-lib", - "version": "9.1.2", + "version": "10.0.0-beta.7", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 16b5cd5f..823a0e86 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cardano-serialization-lib", - "version": "9.1.2", + "version": "10.0.0-beta.7", "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 17de1a4b..520ae10d 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -50,7 +50,7 @@ checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820" [[package]] name = "cardano-serialization-lib" -version = "9.1.2" +version = "10.0.0-beta.7" dependencies = [ "bech32", "cbor_event", diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 9db068ba..89aee6f7 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cardano-serialization-lib" -version = "9.1.2" +version = "10.0.0-beta.7" 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 2a66c8ec..b7652eab 100644 --- a/rust/pkg/cardano_serialization_lib.js.flow +++ b/rust/pkg/cardano_serialization_lib.js.flow @@ -5179,29 +5179,52 @@ declare export class TransactionBuilder { /** * Set explicit Mint object to this builder * it will replace any previously existing mint + * NOTE! If you use `set_mint` manually - you must use `set_mint_scripts` + * to provide matching policy scripts or min-fee calculation will be rejected! * @param {Mint} mint */ set_mint(mint: Mint): void; + /** + * Returns a copy of the current mint state in the builder + * @returns {Mint | void} + */ + get_mint(): Mint | void; + + /** + * Set explicit witness set to this builder + * It will replace any previously existing witnesses + * NOTE! Use carefully! If you are using `set_mint` - then you must be using + * this setter as well to be able to calculate fee automatically! + * @param {NativeScripts} mint_scripts + */ + set_mint_scripts(mint_scripts: NativeScripts): void; + + /** + * Returns a copy of the current mint witness scripts in the builder + * @returns {NativeScripts | void} + */ + get_mint_scripts(): NativeScripts | void; + /** * Add a mint entry to this builder using a PolicyID and MintAssets object * It will be securely added to existing or new Mint in this builder * It will replace any existing mint assets with the same PolicyID - * @param {ScriptHash} policy_id + * @param {NativeScript} policy_script * @param {MintAssets} mint_assets */ - set_mint_asset(policy_id: ScriptHash, mint_assets: MintAssets): void; + set_mint_asset(policy_script: NativeScript, mint_assets: MintAssets): void; /** * Add a mint entry to this builder using a PolicyID, AssetName, and Int object for amount * It will be securely added to existing or new Mint in this builder * It will replace any previous existing amount same PolicyID and AssetName - * @param {ScriptHash} policy_id + * @param {NativeScript} policy_script * @param {AssetName} asset_name * @param {Int} amount */ add_mint_asset( - policy_id: ScriptHash, + policy_script: NativeScript, asset_name: AssetName, amount: Int ): void; @@ -5211,14 +5234,14 @@ declare export class TransactionBuilder { * Using a PolicyID, AssetName, Int for amount, Address, and Coin (BigNum) objects * The asset will be securely added to existing or new Mint in this builder * A new output will be added with the specified Address, the Coin value, and the minted asset - * @param {ScriptHash} policy_id + * @param {NativeScript} policy_script * @param {AssetName} asset_name * @param {Int} amount * @param {Address} address * @param {BigNum} output_coin */ add_mint_asset_and_output( - policy_id: ScriptHash, + policy_script: NativeScript, asset_name: AssetName, amount: Int, address: Address, @@ -5231,13 +5254,13 @@ declare export class TransactionBuilder { * The asset will be securely added to existing or new Mint in this builder * A new output will be added with the specified Address and the minted asset * The output will be set to contain the minimum required amount of Coin - * @param {ScriptHash} policy_id + * @param {NativeScript} policy_script * @param {AssetName} asset_name * @param {Int} amount * @param {Address} address */ add_mint_asset_and_output_min_required_coin( - policy_id: ScriptHash, + policy_script: NativeScript, asset_name: AssetName, amount: Int, address: Address @@ -5300,14 +5323,14 @@ declare export class TransactionBuilder { /** * Returns object the body of the new transaction * Auxiliary data itself is not included - * You can use `get_auxiliary_date` or `build_tx` + * You can use `get_auxiliary_data` or `build_tx` * @returns {TransactionBody} */ build(): TransactionBody; /** * Returns full Transaction object with the body and the auxiliary data - * NOTE: witness_set is set to just empty set + * NOTE: witness_set will contain all mint_scripts if any been added or set * NOTE: is_valid set to true * @returns {Transaction} */ diff --git a/rust/src/tx_builder.rs b/rust/src/tx_builder.rs index fc155ea4..986071cf 100644 --- a/rust/src/tx_builder.rs +++ b/rust/src/tx_builder.rs @@ -46,12 +46,6 @@ fn fake_private_key() -> Bip32PrivateKey { ).unwrap() } -fn fake_key_hash(x: u8) -> Ed25519KeyHash { - Ed25519KeyHash::from_bytes( - vec![x, 239, 181, 120, 142, 135, 19, 200, 68, 223, 211, 43, 46, 145, 222, 30, 48, 159, 239, 255, 213, 85, 248, 39, 204, 158, 225, 100] - ).unwrap() -} - // tx_body must be the result of building from tx_builder // constructs the rest of the Transaction using fake witness data of the correct length // for use in calculating the size of the final Transaction @@ -1170,6 +1164,12 @@ mod tests { Bip32PrivateKey::from_bip39_entropy(&entropy, &[]) } + fn fake_key_hash(x: u8) -> Ed25519KeyHash { + Ed25519KeyHash::from_bytes( + vec![x, 239, 181, 120, 142, 135, 19, 200, 68, 223, 211, 43, 46, 145, 222, 30, 48, 159, 239, 255, 213, 85, 248, 39, 204, 158, 225, 100] + ).unwrap() + } + fn harden(index: u32) -> u32 { index | 0x80_00_00_00 } @@ -3501,9 +3501,9 @@ mod tests { let hash0 = fake_key_hash(0); - let (mint_script1, policy_id1, hash1) = mint_script_and_policy_and_hash(1); - let (mint_script2, policy_id2, hash2) = mint_script_and_policy_and_hash(2); - let (mint_script3, policy_id3, hash3) = mint_script_and_policy_and_hash(3); + let (mint_script1, _, hash1) = mint_script_and_policy_and_hash(1); + let (mint_script2, _, _) = mint_script_and_policy_and_hash(2); + let (mint_script3, _, _) = mint_script_and_policy_and_hash(3); let name1 = AssetName::new(vec![0u8, 1, 2, 3]).unwrap(); let name2 = AssetName::new(vec![1u8, 1, 2, 3]).unwrap(); @@ -3588,7 +3588,7 @@ mod tests { assert!(tx_builder.min_fee().is_ok()); let (mint_script1, policy_id1) = mint_script_and_policy(0); - let (mint_script2, policy_id2) = mint_script_and_policy(1); + let (mint_script2, _) = mint_script_and_policy(1); let name1 = AssetName::new(vec![0u8, 1, 2, 3]).unwrap(); let amount = Int::new_i32(1234); From 94460c2b8510883561b2539a428333fea9d64c29 Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Wed, 1 Dec 2021 23:29:49 +0300 Subject: [PATCH 06/17] Fixed unique vkey signatures for minting. Version bump to beta8 --- package-lock.json | 2 +- package.json | 2 +- rust/Cargo.lock | 2 +- rust/Cargo.toml | 2 +- rust/src/lib.rs | 103 +++++++++++++++++++++++++++++++++++++++++ rust/src/tx_builder.rs | 48 ++++++++++--------- 6 files changed, 134 insertions(+), 25 deletions(-) diff --git a/package-lock.json b/package-lock.json index d25ec6bd..11d70945 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "cardano-serialization-lib", - "version": "10.0.0-beta.7", + "version": "10.0.0-beta.8", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 823a0e86..4c1dd14a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cardano-serialization-lib", - "version": "10.0.0-beta.7", + "version": "10.0.0-beta.8", "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 520ae10d..ab208d1f 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -50,7 +50,7 @@ checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820" [[package]] name = "cardano-serialization-lib" -version = "10.0.0-beta.7" +version = "10.0.0-beta.8" dependencies = [ "bech32", "cbor_event", diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 89aee6f7..eaed2c5e 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cardano-serialization-lib" -version = "10.0.0-beta.7" +version = "10.0.0-beta.8" edition = "2018" authors = ["EMURGO"] license = "MIT" diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 600d91a4..248f355a 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -58,6 +58,7 @@ use plutus::*; use metadata::*; use utils::*; use std::cmp::Ordering; +use std::collections::BTreeSet; type DeltaCoin = Int; @@ -2752,6 +2753,35 @@ impl NetworkId { } } +pub fn get_all_pubkeys_from_script(script: &NativeScript) -> BTreeSet { + match &script.0 { + NativeScriptEnum::ScriptPubkey(spk) => { + let mut set = BTreeSet::new(); + set.insert(spk.addr_keyhash()); + set + }, + NativeScriptEnum::ScriptAll(all) => { + get_all_pubkeys_from_scripts(&all.native_scripts) + }, + NativeScriptEnum::ScriptAny(any) => { + get_all_pubkeys_from_scripts(&any.native_scripts) + }, + NativeScriptEnum::ScriptNOfK(ofk) => { + get_all_pubkeys_from_scripts(&ofk.native_scripts) + }, + _ => BTreeSet::new(), + } +} + +pub fn get_all_pubkeys_from_scripts(scripts: &NativeScripts) -> BTreeSet { + scripts.0.iter().fold(BTreeSet::new(), |mut set, s| { + get_all_pubkeys_from_script(s).iter().for_each(|pk| { + set.insert(pk.clone()); + }); + set + }) +} + #[cfg(test)] mod tests { use super::*; @@ -2933,4 +2963,77 @@ mod tests { assert_eq!(p_ass.get(&name1).unwrap(), amount1); assert_eq!(n_ass.get(&name1).unwrap(), amount1); } + + fn keyhash(x: u8) -> Ed25519KeyHash { + Ed25519KeyHash::from_bytes(vec![x, 180, 186, 93, 223, 42, 243, 7, 81, 98, 86, 125, 97, 69, 110, 52, 130, 243, 244, 98, 246, 13, 33, 212, 128, 168, 136, 40]).unwrap() + } + + fn pkscript(pk: &Ed25519KeyHash) -> NativeScript { + NativeScript::new_script_pubkey(&ScriptPubkey::new(pk)) + } + + fn scripts_vec(scripts: Vec<&NativeScript>) -> NativeScripts { + NativeScripts(scripts.iter().map(|s| { (*s).clone() }).collect()) + } + + #[test] + fn native_scripts_get_pubkeys() { + let keyhash1 = keyhash(1); + let keyhash2 = keyhash(2); + let keyhash3 = keyhash(3); + + let pks1 = get_all_pubkeys_from_script(&pkscript(&keyhash1)); + assert_eq!(pks1.len(), 1); + assert!(pks1.contains(&keyhash1)); + + let pks2 = get_all_pubkeys_from_script( + &NativeScript::new_timelock_start( + &TimelockStart::new(123), + ), + ); + assert_eq!(pks2.len(), 0); + + let pks3 = get_all_pubkeys_from_script( + &NativeScript::new_script_all( + &ScriptAll::new(&scripts_vec(vec![ + &pkscript(&keyhash1), + &pkscript(&keyhash2), + ])) + ), + ); + assert_eq!(pks3.len(), 2); + assert!(pks3.contains(&keyhash1)); + assert!(pks3.contains(&keyhash2)); + + let pks4 = get_all_pubkeys_from_script( + &NativeScript::new_script_any( + &ScriptAny::new(&scripts_vec(vec![ + &NativeScript::new_script_n_of_k(&ScriptNOfK::new( + 1, + &scripts_vec(vec![ + &NativeScript::new_timelock_start(&TimelockStart::new(132)), + &pkscript(&keyhash3), + ]), + )), + &NativeScript::new_script_all(&ScriptAll::new( + &scripts_vec(vec![ + &NativeScript::new_timelock_expiry(&TimelockExpiry::new(132)), + &pkscript(&keyhash1), + ]), + )), + &NativeScript::new_script_any(&ScriptAny::new( + &scripts_vec(vec![ + &pkscript(&keyhash1), + &pkscript(&keyhash2), + &pkscript(&keyhash3), + ]), + )), + ])) + ), + ); + assert_eq!(pks4.len(), 3); + assert!(pks4.contains(&keyhash1)); + assert!(pks4.contains(&keyhash2)); + assert!(pks4.contains(&keyhash3)); + } } diff --git a/rust/src/tx_builder.rs b/rust/src/tx_builder.rs index 986071cf..df27149e 100644 --- a/rust/src/tx_builder.rs +++ b/rust/src/tx_builder.rs @@ -46,20 +46,31 @@ fn fake_private_key() -> Bip32PrivateKey { ).unwrap() } +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(&get_all_pubkeys_from_scripts(scripts)).count() + } + } +} + // tx_body must be the result of building from tx_builder // constructs the rest of the Transaction using fake witness data of the correct length // for use in calculating the size of the final Transaction fn fake_full_tx(tx_builder: &TransactionBuilder, body: TransactionBody) -> Result { let fake_key_root = fake_private_key(); - let raw_key = fake_key_root.to_raw_key(); - let fake_vkey_witness = Vkeywitness::new( - &Vkey::new(&raw_key.to_public()), - &raw_key.sign([1u8; 100].as_ref()) - ); // recall: this includes keys for input, certs and withdrawals - let vkeys = match tx_builder.input_types.vkeys.len() { + let vkeys = match count_needed_vkeys(tx_builder) { 0 => None, x => { + let raw_key = fake_key_root.to_raw_key(); + let fake_vkey_witness = Vkeywitness::new( + &Vkey::new(&raw_key.to_public()), + &raw_key.sign([1u8; 100].as_ref()) + ); let mut result = Vkeywitnesses::new(); for _i in 0..x { result.add(&fake_vkey_witness.clone()); @@ -89,24 +100,20 @@ fn fake_full_tx(tx_builder: &TransactionBuilder, body: TransactionBody) -> Resul Some(result) }, }; - let (full_vkeys, full_script_keys) = match &tx_builder.mint_scripts { - None => (vkeys, script_keys), + let full_script_keys = match &tx_builder.mint_scripts { + None => script_keys, Some(witness_scripts) => { - let mut vw = vkeys - .map(|x| { x.clone() }) - .unwrap_or(Vkeywitnesses::new()); let mut ns = script_keys .map(|x| { x.clone() }) .unwrap_or(NativeScripts::new()); witness_scripts.0.iter().for_each(|s| { - vw.add(&fake_vkey_witness.clone()); ns.add(s); }); - (Some(vw), Some(ns)) + Some(ns) } }; let witness_set = TransactionWitnessSet { - vkeys: full_vkeys, + vkeys, native_scripts: full_script_keys, bootstraps: bootstrap_keys, // TODO: plutus support? @@ -3565,19 +3572,18 @@ mod tests { .checked_sub(&raw_mint_fee).unwrap() .checked_sub(&raw_mint_script_fee).unwrap(); - assert_eq!(witness_fee_increase, to_bignum(13376)); + assert_eq!(witness_fee_increase, to_bignum(8932)); let fee_increase_bytes = from_bignum(&witness_fee_increase) .checked_div(from_bignum(&fee_coefficient)) .unwrap(); - // Three vkey witnesses 96 bytes each (32 byte pubkey + 64 byte signature) - // Plus 16 bytes overhead for CBOR wrappers + // Two vkey witnesses 96 bytes each (32 byte pubkey + 64 byte signature) + // Plus 11 bytes overhead for CBOR wrappers // This is happening because we have three different minting policies - // and we are assuming each one needs a separate signature even tho - // they might refer to the same address with another policy or one of the inputs - // - assert_eq!(fee_increase_bytes, 304); + // but the same key-hash from one of them is already also used in inputs + // so no suplicate witness signature is require for that one + assert_eq!(fee_increase_bytes, 203); } #[test] From ed86ae83150442336be78edc669d658f6dc82310 Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Tue, 7 Dec 2021 01:23:39 +0300 Subject: [PATCH 07/17] Started changing API for mint-scripts --- rust/src/tx_builder.rs | 67 ++++++++++++++++++++++++++---------------- 1 file changed, 41 insertions(+), 26 deletions(-) diff --git a/rust/src/tx_builder.rs b/rust/src/tx_builder.rs index df27149e..9285f99d 100644 --- a/rust/src/tx_builder.rs +++ b/rust/src/tx_builder.rs @@ -129,20 +129,32 @@ fn fake_full_tx(tx_builder: &TransactionBuilder, body: TransactionBody) -> Resul }) } -fn min_fee(tx_builder: &TransactionBuilder) -> Result { - if let Some(mint) = tx_builder.mint.as_ref() { - if let Some(witness_scripts) = tx_builder.mint_scripts.as_ref() { - let witness_hashes: HashSet = witness_scripts.0.iter().map(|script| { - script.hash(ScriptHashNamespace::NativeScript) - }).collect(); - for mint_hash in mint.keys().0.iter() { - if !witness_hashes.contains(mint_hash) { - return Err(JsError::from_str(&format!("No witness script is found for mint policy '{:?}'! Impossible to estimate fee", hex::encode(mint_hash.to_bytes())))); - } +fn assert_required_mint_scripts(mint: &Mint, maybe_mint_scripts: Option<&NativeScripts>) -> Result<(), JsError> { + if let Some(mint_scripts) = maybe_mint_scripts { + let witness_hashes: HashSet = witness_scripts.0.iter().map(|script| { + script.hash(ScriptHashNamespace::NativeScript) + }).collect(); + for mint_hash in mint.keys().0.iter() { + if !witness_hashes.contains(mint_hash) { + return Err(JsError::from_str( + &format!( + "No witness script is found for mint policy '{:?}'! Script is required!", + hex::encode(mint_hash.to_bytes()), + )) + ); } - } else { - return Err(JsError::from_str("Impossible to estimate fee if mint is present in the builder, but witness scripts are not provided!")); } + } else { + return Err(JsError::from_str( + "Mint is present in the builder, but witness scripts are not provided!", + )); + } + Ok(()) +} + +fn min_fee(tx_builder: &TransactionBuilder) -> Result { + if let Some(mint) = tx_builder.mint.as_ref() { + 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) @@ -654,12 +666,14 @@ impl TransactionBuilder { Ok(()) } - /// Set explicit Mint object to this builder - /// it will replace any previously existing mint - /// NOTE! If you use `set_mint` manually - you must use `set_mint_scripts` - /// to provide matching policy scripts or min-fee calculation will be rejected! - pub fn set_mint(&mut self, mint: &Mint) { + /// Set explicit Mint object and the required witnesses to this builder + /// it will replace any previously existing mint and mint scripts + /// NOTE! Error will be returned in case a mint policy does not have a matching script + pub fn set_mint(&mut self, mint: &Mint, mint_scripts: &NativeScripts) -> Result<(), JsError> { + assert_required_mint_scripts(mint, Some(mint_scripts))?; self.mint = Some(mint.clone()); + self.mint_scripts = Some(mint_scripts.clone()); + Ok(()) } /// Returns a copy of the current mint state in the builder @@ -667,14 +681,6 @@ impl TransactionBuilder { self.mint.clone() } - /// Set explicit witness set to this builder - /// It will replace any previously existing witnesses - /// NOTE! Use carefully! If you are using `set_mint` - then you must be using - /// this setter as well to be able to calculate fee automatically! - pub fn set_mint_scripts(&mut self, mint_scripts: &NativeScripts) { - self.mint_scripts = Some(mint_scripts.clone()); - } - /// Returns a copy of the current mint witness scripts in the builder pub fn get_mint_scripts(&self) -> Option { self.mint_scripts.clone() @@ -683,7 +689,16 @@ impl TransactionBuilder { fn _set_mint_asset(&mut self, policy_id: &PolicyID, policy_script: &NativeScript, mint_assets: &MintAssets) { let mut mint = self.mint.as_ref().cloned().unwrap_or(Mint::new()); let is_new_policy = mint.insert(&policy_id, mint_assets).is_none(); - self.set_mint(&mint); + let mint_scripts = { + let mut witness_scripts = self.mint_scripts.as_ref().cloned() + .unwrap_or(NativeScripts::new()); + if is_new_policy { + // If policy has not been encountered before - insert the script into witnesses + witness_scripts.add(&policy_script.clone()); + } + witness_scripts + }; + self.set_mint(&mint, &mint_scripts); if is_new_policy { // If policy has not been encountered before - insert the script into witnesses let mut witness_scripts = self.mint_scripts.as_ref().cloned() From d968823f455dec44809ad98d5796425f37c2b9b6 Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Mon, 13 Dec 2021 01:59:06 +0300 Subject: [PATCH 08/17] Fixed tests for new minting API --- rust/src/lib.rs | 17 +++++++ rust/src/traits.rs | 15 ++++++ rust/src/tx_builder.rs | 104 ++++++++++++++++++++++++----------------- 3 files changed, 93 insertions(+), 43 deletions(-) create mode 100644 rust/src/traits.rs diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 248f355a..dc0c1023 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -34,6 +34,7 @@ use cbor_event::{ se::{Serialize, Serializer}, }; +pub mod traits; pub mod address; pub mod chain_core; pub mod chain_crypto; @@ -59,6 +60,7 @@ use metadata::*; use utils::*; use std::cmp::Ordering; use std::collections::BTreeSet; +use crate::traits::NoneOrEmpty; type DeltaCoin = Int; @@ -1735,6 +1737,21 @@ impl NativeScripts { } } +impl From> for NativeScripts { + fn from(scripts: Vec) -> Self { + scripts.iter().fold(NativeScripts::new(), |mut scripts, s| { + scripts.add(s); + scripts + }) + } +} + +impl NoneOrEmpty for NativeScripts { + fn is_none_or_empty(&self) -> bool { + self.0.is_empty() + } +} + #[wasm_bindgen] #[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] pub struct Update { diff --git a/rust/src/traits.rs b/rust/src/traits.rs new file mode 100644 index 00000000..8a1b2815 --- /dev/null +++ b/rust/src/traits.rs @@ -0,0 +1,15 @@ +pub trait NoneOrEmpty { + fn is_none_or_empty(&self) -> bool; +} + +impl NoneOrEmpty for &T { + fn is_none_or_empty(&self) -> bool { + (*self).is_none_or_empty() + } +} + +impl NoneOrEmpty for Option { + fn is_none_or_empty(&self) -> bool { + self.is_none() || self.as_ref().unwrap().is_none_or_empty() + } +} diff --git a/rust/src/tx_builder.rs b/rust/src/tx_builder.rs index 9285f99d..2de51dae 100644 --- a/rust/src/tx_builder.rs +++ b/rust/src/tx_builder.rs @@ -130,25 +130,25 @@ fn fake_full_tx(tx_builder: &TransactionBuilder, body: TransactionBody) -> Resul } fn assert_required_mint_scripts(mint: &Mint, maybe_mint_scripts: Option<&NativeScripts>) -> Result<(), JsError> { - if let Some(mint_scripts) = maybe_mint_scripts { - let witness_hashes: HashSet = witness_scripts.0.iter().map(|script| { - script.hash(ScriptHashNamespace::NativeScript) - }).collect(); - for mint_hash in mint.keys().0.iter() { - if !witness_hashes.contains(mint_hash) { - return Err(JsError::from_str( - &format!( - "No witness script is found for mint policy '{:?}'! Script is required!", - hex::encode(mint_hash.to_bytes()), - )) - ); - } - } - } else { + if maybe_mint_scripts.is_none_or_empty() { return Err(JsError::from_str( "Mint is present in the builder, but witness scripts are not provided!", )); } + let mint_scripts = maybe_mint_scripts.unwrap(); + let witness_hashes: HashSet = mint_scripts.0.iter().map(|script| { + script.hash(ScriptHashNamespace::NativeScript) + }).collect(); + for mint_hash in mint.keys().0.iter() { + if !witness_hashes.contains(mint_hash) { + return Err(JsError::from_str( + &format!( + "No witness script is found for mint policy '{:?}'! Script is required!", + hex::encode(mint_hash.to_bytes()), + )) + ); + } + } Ok(()) } @@ -698,14 +698,8 @@ impl TransactionBuilder { } witness_scripts }; - self.set_mint(&mint, &mint_scripts); - if is_new_policy { - // If policy has not been encountered before - insert the script into witnesses - let mut witness_scripts = self.mint_scripts.as_ref().cloned() - .unwrap_or(NativeScripts::new()); - witness_scripts.add(&policy_script.clone()); - self.set_mint_scripts(&witness_scripts); - } + self.mint = Some(mint.clone()); + self.mint_scripts = Some(mint_scripts.clone()); } /// Add a mint entry to this builder using a PolicyID and MintAssets object @@ -3260,10 +3254,13 @@ mod tests { fn set_mint_asset_with_existing_mint() { let mut tx_builder = create_default_tx_builder(); - let (_, policy_id1) = mint_script_and_policy(0); + let (mint_script1, policy_id1) = mint_script_and_policy(0); let (mint_script2, policy_id2) = mint_script_and_policy(1); - tx_builder.set_mint(&create_mint_with_one_asset(&policy_id1)); + tx_builder.set_mint( + &create_mint_with_one_asset(&policy_id1), + &NativeScripts::from(vec![mint_script1.clone()]), + ).unwrap(); tx_builder.set_mint_asset(&mint_script2, &create_mint_asset()); @@ -3278,8 +3275,9 @@ mod tests { assert_mint_asset(&mint, &policy_id2); // Only second script is present in the scripts - assert_eq!(mint_scripts.len(), 1); - assert_eq!(mint_scripts.get(0), mint_script2); + assert_eq!(mint_scripts.len(), 2); + assert_eq!(mint_scripts.get(0), mint_script1); + assert_eq!(mint_scripts.get(1), mint_script2); } #[test] @@ -3307,10 +3305,13 @@ mod tests { fn add_mint_asset_with_existing_mint() { let mut tx_builder = create_default_tx_builder(); - let (_, policy_id1) = mint_script_and_policy(0); + let (mint_script1, policy_id1) = mint_script_and_policy(0); let (mint_script2, policy_id2) = mint_script_and_policy(1); - tx_builder.set_mint(&create_mint_with_one_asset(&policy_id1)); + tx_builder.set_mint( + &create_mint_with_one_asset(&policy_id1), + &NativeScripts::from(vec![mint_script1.clone()]), + ).unwrap(); tx_builder.add_mint_asset(&mint_script2, &create_asset_name(), Int::new_i32(1234)); assert!(tx_builder.mint.is_some()); @@ -3323,9 +3324,9 @@ mod tests { assert_mint_asset(&mint, &policy_id1); assert_mint_asset(&mint, &policy_id2); - // Only second script is present in the scripts - assert_eq!(mint_scripts.len(), 1); - assert_eq!(mint_scripts.get(0), mint_script2); + assert_eq!(mint_scripts.len(), 2); + assert_eq!(mint_scripts.get(0), mint_script1); + assert_eq!(mint_scripts.get(1), mint_script2); } #[test] @@ -3620,26 +3621,43 @@ mod tests { &MintAssets::new_from_entry(&name1, amount.clone()), ); - tx_builder.set_mint(&mint); + tx_builder.set_mint( + &mint, + &NativeScripts::from(vec![mint_script1]), + ).unwrap(); - // Mint exists but no witness scripts at all present let est1 = tx_builder.min_fee(); - assert!(est1.is_err()); - assert!(est1.err().unwrap().to_string().contains("witness scripts are not provided")); + assert!(est1.is_ok()); tx_builder.add_mint_asset(&mint_script2, &name1, amount.clone()); - // Now two different policies are minted but only one witness script is present let est2 = tx_builder.min_fee(); - assert!(est2.is_err()); - assert!(est2.err().unwrap().to_string().contains(&format!("{:?}", hex::encode(policy_id1.to_bytes())))); + assert!(est2.is_ok()); - let mut scripts = tx_builder.get_mint_scripts().unwrap(); - scripts.add(&mint_script1); - tx_builder.set_mint_scripts(&scripts); + // Remove one mint script + tx_builder.mint_scripts = + Some(NativeScripts::from(vec![tx_builder.mint_scripts.unwrap().get(1)])); + // Now two different policies are minted but only one witness script is present let est3 = tx_builder.min_fee(); - assert!(est3.is_ok()) + assert!(est3.is_err()); + assert!(est3.err().unwrap().to_string().contains(&format!("{:?}", hex::encode(policy_id1.to_bytes())))); + + // Remove all mint scripts + tx_builder.mint_scripts = Some(NativeScripts::new()); + + // Mint exists but no witness scripts at all present + let est4 = tx_builder.min_fee(); + assert!(est4.is_err()); + assert!(est4.err().unwrap().to_string().contains("witness scripts are not provided")); + + // Remove all mint scripts + tx_builder.mint_scripts = None; + + // Mint exists but no witness scripts at all present + let est5 = tx_builder.min_fee(); + assert!(est5.is_err()); + assert!(est5.err().unwrap().to_string().contains("witness scripts are not provided")); } #[test] From f1eb5fd8e5dece98acea2a5191c960d239114177 Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Mon, 13 Dec 2021 02:25:08 +0300 Subject: [PATCH 09/17] Removed unused functions and variables (after merge) --- rust/src/tx_builder.rs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/rust/src/tx_builder.rs b/rust/src/tx_builder.rs index 0c5bc46c..e150d04e 100644 --- a/rust/src/tx_builder.rs +++ b/rust/src/tx_builder.rs @@ -46,12 +46,6 @@ fn fake_private_key() -> Bip32PrivateKey { ).unwrap() } -fn fake_key_hash() -> Ed25519KeyHash { - Ed25519KeyHash::from( - [142, 239, 181, 120, 142, 135, 19, 200, 68, 223, 211, 43, 46, 145, 222, 30, 48, 159, 239, 255, 213, 85, 248, 39, 204, 158, 225, 100] - ) -} - fn fake_raw_key_sig() -> Ed25519Signature { Ed25519Signature::from_bytes( vec![36, 248, 153, 211, 155, 23, 253, 93, 102, 193, 146, 196, 181, 13, 52, 62, 66, 247, 35, 91, 48, 80, 76, 138, 231, 97, 159, 147, 200, 40, 220, 109, 206, 69, 104, 221, 105, 23, 124, 85, 24, 40, 73, 45, 119, 122, 103, 39, 253, 102, 194, 251, 204, 189, 168, 194, 174, 237, 146, 3, 44, 153, 121, 10] @@ -80,7 +74,6 @@ fn count_needed_vkeys(tx_builder: &TransactionBuilder) -> usize { // for use in calculating the size of the final Transaction fn fake_full_tx(tx_builder: &TransactionBuilder, body: TransactionBody) -> Result { let fake_key_root = fake_private_key(); - let fake_key_hash = fake_key_hash(); let raw_key_public = fake_raw_key_public(); let fake_sig = fake_raw_key_sig(); @@ -88,7 +81,6 @@ fn fake_full_tx(tx_builder: &TransactionBuilder, body: TransactionBody) -> Resul let vkeys = match count_needed_vkeys(tx_builder) { 0 => None, x => { - let raw_key = fake_key_root.to_raw_key(); let fake_vkey_witness = Vkeywitness::new( &Vkey::new(&raw_key_public), &fake_sig From 2bbe1c4d523967fc4c143816d23131ffde29e118 Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Mon, 13 Dec 2021 02:31:36 +0300 Subject: [PATCH 10/17] Flowgen update --- rust/pkg/cardano_serialization_lib.js.flow | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/rust/pkg/cardano_serialization_lib.js.flow b/rust/pkg/cardano_serialization_lib.js.flow index b7652eab..bce079b1 100644 --- a/rust/pkg/cardano_serialization_lib.js.flow +++ b/rust/pkg/cardano_serialization_lib.js.flow @@ -5177,13 +5177,13 @@ declare export class TransactionBuilder { ): void; /** - * Set explicit Mint object to this builder - * it will replace any previously existing mint - * NOTE! If you use `set_mint` manually - you must use `set_mint_scripts` - * to provide matching policy scripts or min-fee calculation will be rejected! + * Set explicit Mint object and the required witnesses to this builder + * it will replace any previously existing mint and mint scripts + * NOTE! Error will be returned in case a mint policy does not have a matching script * @param {Mint} mint + * @param {NativeScripts} mint_scripts */ - set_mint(mint: Mint): void; + set_mint(mint: Mint, mint_scripts: NativeScripts): void; /** * Returns a copy of the current mint state in the builder @@ -5191,15 +5191,6 @@ declare export class TransactionBuilder { */ get_mint(): Mint | void; - /** - * Set explicit witness set to this builder - * It will replace any previously existing witnesses - * NOTE! Use carefully! If you are using `set_mint` - then you must be using - * this setter as well to be able to calculate fee automatically! - * @param {NativeScripts} mint_scripts - */ - set_mint_scripts(mint_scripts: NativeScripts): void; - /** * Returns a copy of the current mint witness scripts in the builder * @returns {NativeScripts | void} From 4a65faf4a631b552d208823203e556f650cd3f24 Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Mon, 13 Dec 2021 02:41:49 +0300 Subject: [PATCH 11/17] Replaced functions with a new type and `From` implementation --- rust/src/lib.rs | 61 +++++++++++++++++++++++------------------- rust/src/tx_builder.rs | 2 +- 2 files changed, 34 insertions(+), 29 deletions(-) diff --git a/rust/src/lib.rs b/rust/src/lib.rs index dc0c1023..ecc7c6c2 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -224,6 +224,7 @@ impl Certificates { } pub type RequiredSigners = Ed25519KeyHashes; +pub type RequiredSignersSet = BTreeSet; #[wasm_bindgen] #[derive(Clone)] @@ -2770,33 +2771,37 @@ impl NetworkId { } } -pub fn get_all_pubkeys_from_script(script: &NativeScript) -> BTreeSet { - match &script.0 { - NativeScriptEnum::ScriptPubkey(spk) => { - let mut set = BTreeSet::new(); - set.insert(spk.addr_keyhash()); - set - }, - NativeScriptEnum::ScriptAll(all) => { - get_all_pubkeys_from_scripts(&all.native_scripts) - }, - NativeScriptEnum::ScriptAny(any) => { - get_all_pubkeys_from_scripts(&any.native_scripts) - }, - NativeScriptEnum::ScriptNOfK(ofk) => { - get_all_pubkeys_from_scripts(&ofk.native_scripts) - }, - _ => BTreeSet::new(), +impl From<&NativeScript> for RequiredSignersSet { + fn from(script: &NativeScript) -> Self { + match &script.0 { + NativeScriptEnum::ScriptPubkey(spk) => { + let mut set = BTreeSet::new(); + set.insert(spk.addr_keyhash()); + set + }, + NativeScriptEnum::ScriptAll(all) => { + RequiredSignersSet::from(&all.native_scripts) + }, + NativeScriptEnum::ScriptAny(any) => { + RequiredSignersSet::from(&any.native_scripts) + }, + NativeScriptEnum::ScriptNOfK(ofk) => { + RequiredSignersSet::from(&ofk.native_scripts) + }, + _ => BTreeSet::new(), + } } } -pub fn get_all_pubkeys_from_scripts(scripts: &NativeScripts) -> BTreeSet { - scripts.0.iter().fold(BTreeSet::new(), |mut set, s| { - get_all_pubkeys_from_script(s).iter().for_each(|pk| { - set.insert(pk.clone()); - }); - set - }) +impl From<&NativeScripts> for RequiredSignersSet { + fn from(scripts: &NativeScripts) -> Self { + scripts.0.iter().fold(BTreeSet::new(), |mut set, s| { + RequiredSignersSet::from(s).iter().for_each(|pk| { + set.insert(pk.clone()); + }); + set + }) + } } #[cfg(test)] @@ -2999,18 +3004,18 @@ mod tests { let keyhash2 = keyhash(2); let keyhash3 = keyhash(3); - let pks1 = get_all_pubkeys_from_script(&pkscript(&keyhash1)); + let pks1 = RequiredSignersSet::from(&pkscript(&keyhash1)); assert_eq!(pks1.len(), 1); assert!(pks1.contains(&keyhash1)); - let pks2 = get_all_pubkeys_from_script( + let pks2 = RequiredSignersSet::from( &NativeScript::new_timelock_start( &TimelockStart::new(123), ), ); assert_eq!(pks2.len(), 0); - let pks3 = get_all_pubkeys_from_script( + let pks3 = RequiredSignersSet::from( &NativeScript::new_script_all( &ScriptAll::new(&scripts_vec(vec![ &pkscript(&keyhash1), @@ -3022,7 +3027,7 @@ mod tests { assert!(pks3.contains(&keyhash1)); assert!(pks3.contains(&keyhash2)); - let pks4 = get_all_pubkeys_from_script( + let pks4 = RequiredSignersSet::from( &NativeScript::new_script_any( &ScriptAny::new(&scripts_vec(vec![ &NativeScript::new_script_n_of_k(&ScriptNOfK::new( diff --git a/rust/src/tx_builder.rs b/rust/src/tx_builder.rs index e150d04e..fa91b2d3 100644 --- a/rust/src/tx_builder.rs +++ b/rust/src/tx_builder.rs @@ -64,7 +64,7 @@ fn count_needed_vkeys(tx_builder: &TransactionBuilder) -> usize { None => input_hashes.len(), Some(scripts) => { // Union all input keys with minting keys - input_hashes.union(&get_all_pubkeys_from_scripts(scripts)).count() + input_hashes.union(&RequiredSignersSet::from(scripts)).count() } } } From 15bd42ec3d1a181f78c56afe48a864e5782ddd42 Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Mon, 13 Dec 2021 03:15:55 +0300 Subject: [PATCH 12/17] Removed unused field --- rust/src/tx_builder.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/rust/src/tx_builder.rs b/rust/src/tx_builder.rs index fa91b2d3..aaf0c0a4 100644 --- a/rust/src/tx_builder.rs +++ b/rust/src/tx_builder.rs @@ -304,7 +304,6 @@ pub struct TransactionBuilder { input_types: MockWitnessSet, mint: Option, mint_scripts: Option, - inputs_auto_added: bool, } #[wasm_bindgen] @@ -807,7 +806,6 @@ impl TransactionBuilder { validity_start_interval: None, mint: None, mint_scripts: None, - inputs_auto_added: false, } } From 1409301e2d13c6626f6b8cbe871e52388b307f01 Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Fri, 14 Jan 2022 13:47:19 +0300 Subject: [PATCH 13/17] lock file updates --- package-lock.json | 2 +- rust/Cargo.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 56735897..11d70945 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "cardano-serialization-lib", - "version": "9.1.4", + "version": "10.0.0-beta.8", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/rust/Cargo.lock b/rust/Cargo.lock index ccdbb8d3..ab208d1f 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -50,7 +50,7 @@ checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820" [[package]] name = "cardano-serialization-lib" -version = "9.1.4" +version = "10.0.0-beta.8" dependencies = [ "bech32", "cbor_event", From 4ac439a17d26d5692112321cf608856bda65b619 Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Sat, 15 Jan 2022 10:18:45 +0300 Subject: [PATCH 14/17] Added comment to `get_witness_set()` --- rust/src/tx_builder.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rust/src/tx_builder.rs b/rust/src/tx_builder.rs index aaf0c0a4..ff0d048c 100644 --- a/rust/src/tx_builder.rs +++ b/rust/src/tx_builder.rs @@ -1142,6 +1142,10 @@ impl TransactionBuilder { } } + // 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. + // E.g. scripts or something else that has been used during the tx preparation fn get_witness_set(&self) -> TransactionWitnessSet { let mut wit = TransactionWitnessSet::new(); if let Some(scripts) = self.mint_scripts.as_ref() { From 2b93da305b0d142fc666b224be3c7437f3db0c54 Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Sat, 15 Jan 2022 10:21:19 +0300 Subject: [PATCH 15/17] Removed unnecessary clone --- rust/src/tx_builder.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/src/tx_builder.rs b/rust/src/tx_builder.rs index ff0d048c..81708ab1 100644 --- a/rust/src/tx_builder.rs +++ b/rust/src/tx_builder.rs @@ -711,7 +711,7 @@ impl TransactionBuilder { } witness_scripts }; - self.mint = Some(mint.clone()); + self.mint = Some(mint); self.mint_scripts = Some(mint_scripts.clone()); } From 97995ed6ea88b3c95a5fd286eb8cf7a26a2f24d7 Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Sat, 15 Jan 2022 10:27:09 +0300 Subject: [PATCH 16/17] Commented out a non-critical expensive assert in a critical function --- rust/src/tx_builder.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/rust/src/tx_builder.rs b/rust/src/tx_builder.rs index 81708ab1..f39dd4bd 100644 --- a/rust/src/tx_builder.rs +++ b/rust/src/tx_builder.rs @@ -167,9 +167,13 @@ fn assert_required_mint_scripts(mint: &Mint, maybe_mint_scripts: Option<&NativeS } fn min_fee(tx_builder: &TransactionBuilder) -> Result { - if let Some(mint) = tx_builder.mint.as_ref() { - assert_required_mint_scripts(mint, tx_builder.mint_scripts.as_ref())?; - } + // Commented out for performance, `min_fee` is a critical function + // This was mostly added here as a paranoid step anyways + // If someone is using `set_mint` and `add_mint*` API function, everything is expected to be intact + // TODO: figure out if assert is needed here and a better way to do it maybe only once if mint doesn't change + // if let Some(mint) = tx_builder.mint.as_ref() { + // 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) } From 3021134f6a62bf902a5989cbb626d307918b63e1 Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Mon, 17 Jan 2022 11:47:33 +0300 Subject: [PATCH 17/17] Disabled some tests for the disabled functionality --- rust/src/tx_builder.rs | 52 +++++++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/rust/src/tx_builder.rs b/rust/src/tx_builder.rs index f39dd4bd..aeabf213 100644 --- a/rust/src/tx_builder.rs +++ b/rust/src/tx_builder.rs @@ -3654,30 +3654,34 @@ mod tests { let est2 = tx_builder.min_fee(); assert!(est2.is_ok()); - // Remove one mint script - tx_builder.mint_scripts = - Some(NativeScripts::from(vec![tx_builder.mint_scripts.unwrap().get(1)])); - - // Now two different policies are minted but only one witness script is present - let est3 = tx_builder.min_fee(); - assert!(est3.is_err()); - assert!(est3.err().unwrap().to_string().contains(&format!("{:?}", hex::encode(policy_id1.to_bytes())))); - - // Remove all mint scripts - tx_builder.mint_scripts = Some(NativeScripts::new()); - - // Mint exists but no witness scripts at all present - let est4 = tx_builder.min_fee(); - assert!(est4.is_err()); - assert!(est4.err().unwrap().to_string().contains("witness scripts are not provided")); - - // Remove all mint scripts - tx_builder.mint_scripts = None; - - // Mint exists but no witness scripts at all present - let est5 = tx_builder.min_fee(); - assert!(est5.is_err()); - assert!(est5.err().unwrap().to_string().contains("witness scripts are not provided")); + // Native script assertion has been commented out in `.min_fee` + // Until implemented in a more performant manner + // TODO: these test parts might be returned back when it's done + + // // Remove one mint script + // tx_builder.mint_scripts = + // Some(NativeScripts::from(vec![tx_builder.mint_scripts.unwrap().get(1)])); + // + // // Now two different policies are minted but only one witness script is present + // let est3 = tx_builder.min_fee(); + // assert!(est3.is_err()); + // assert!(est3.err().unwrap().to_string().contains(&format!("{:?}", hex::encode(policy_id1.to_bytes())))); + // + // // Remove all mint scripts + // tx_builder.mint_scripts = Some(NativeScripts::new()); + // + // // Mint exists but no witness scripts at all present + // let est4 = tx_builder.min_fee(); + // assert!(est4.is_err()); + // assert!(est4.err().unwrap().to_string().contains("witness scripts are not provided")); + // + // // Remove all mint scripts + // tx_builder.mint_scripts = None; + // + // // Mint exists but no witness scripts at all present + // let est5 = tx_builder.min_fee(); + // assert!(est5.is_err()); + // assert!(est5.err().unwrap().to_string().contains("witness scripts are not provided")); } #[test]