From cc7559ec02752ab76e1bb66137d4f666a812cefc Mon Sep 17 00:00:00 2001 From: lisicky Date: Mon, 8 Aug 2022 14:56:19 +0400 Subject: [PATCH] update size calculation logic --- .../batch_tools/asset_calculator.rs | 131 +++++++++++++++ .../tx_builder/batch_tools/assets_groups.rs | 151 +++++++++++------- rust/src/tx_builder/batch_tools/mod.rs | 3 +- rust/src/utils.rs | 13 +- 4 files changed, 235 insertions(+), 63 deletions(-) create mode 100644 rust/src/tx_builder/batch_tools/asset_calculator.rs diff --git a/rust/src/tx_builder/batch_tools/asset_calculator.rs b/rust/src/tx_builder/batch_tools/asset_calculator.rs new file mode 100644 index 00000000..a9c592c7 --- /dev/null +++ b/rust/src/tx_builder/batch_tools/asset_calculator.rs @@ -0,0 +1,131 @@ +use std::collections::HashMap; +use crate::tx_builder::batch_tools::assets_groups::{AssetIndex, PolicyIndex, UtxoIndex}; +use super::super::*; + +pub(super) struct UtxosStat { + total_policies: usize, + assets_in_policy: HashMap, + coins_in_assets: HashMap, + ada_coins: Coin, +} + +impl UtxosStat { + pub(super) fn new(total_ada: &Coin, policy_to_asset: &HashMap>, + amounts: &HashMap<(AssetIndex, UtxoIndex), Coin>) -> Self { + let mut utxos_stat = UtxosStat { + total_policies: 0, + assets_in_policy: HashMap::new(), + coins_in_assets: HashMap::new(), + ada_coins: Coin::zero(), + }; + for (policy_index, assets) in policy_to_asset { + utxos_stat.assets_in_policy.insert(policy_index.clone(), assets.len()); + } + + for ((asset_index, utxo_index), amount) in amounts { + if let Some(coins) = utxos_stat.coins_in_assets.get(asset_index) { + utxos_stat.coins_in_assets.insert(asset_index.clone(), coins.clone() + amount); + } else { + utxos_stat.coins_in_assets.insert(asset_index.clone(), amount.clone()); + } + } + + utxos_stat.total_policies = policy_to_asset.len(); + utxos_stat.ada_coins = total_ada.clone(); + + utxos_stat + } +} + +#[derive(PartialEq, Eq, Hash, Clone)] +pub(super) struct AssetCalculator { + assets_name_sizes: Vec, + policies_sizes: Vec, + utxo_stat: UtxosStat, + bare_output_size: usize +} + +impl AssetCalculator { + + pub(super) fn new(utxo_stat: UtxosStat, assets_name_sizes: Vec, + policies_sizes: Vec, address: &Address) -> Self { + let bare_output_size = + Self::prepare_output_size_without_assets(&utxo_stat.ada_coins, address); + Self { + assets_name_sizes, + policies_sizes, + utxo_stat, + bare_output_size + } + } + + // According to the CBOR spec, the maximum size of a inlined CBOR value is 23 bytes. + // Otherwise, the value is encoded as pair of type and value. + fn get_struct_size(items_count: u64) -> usize { + if items_count <= MAX_INLINE_ENCODING { + return 1; + } else if items_count < 0x1_00 { + return 2; + } else if items_count < 0x1_00_00 { + return 3; + } else if items_count < 0x1_00_00_00_00 { + return 5; + } else { + return 9; + } + } + + fn get_coin_size(coin: &Coin)-> usize { + Self::get_struct_size(coin.into()) + } + + pub(super) fn calc_aprox_value_size(&self, assets: &HashSet) -> usize { + let mut size = 0; + let mut policy_groups = self.get_grouped_assets(assets); + size += Self::get_struct_size(policy_groups.len().into()); + for (policy_index, assets_in_policy) in policy_groups { + size += self.policies_sizes.get(policy_index).unwrap().clone(); + size += Self::get_struct_size(assets_in_policy.len().into()); + for asset_in_policy in &assets_in_policy { + size += self.assets_name_sizes.get(asset_in_policy).unwrap().clone(); + let asset_coins = self.utxo_stat.coins_in_assets.get(asset_in_policy).unwrap(); + size += Self::get_coin_size(asset_coins); + } + } + size + } + + pub(super) fn calc_value_size(&self, assets: &HashSet, utxos: &HashSet, + assets_amounts: &HashMap<(AssetIndex, UtxoIndex), Coin>) -> usize { + let mut size = 0; + let mut policy_groups = self.get_grouped_assets(assets); + size += Self::get_struct_size(policy_groups.len().into()); + for (policy_index, assets_in_policy) in policy_groups { + size += self.policies_sizes.get(policy_index).unwrap().clone(); + size += Self::get_struct_size(assets_in_policy.len().into()); + for asset_in_policy in &assets_in_policy { + size += self.assets_name_sizes.get(asset_in_policy).unwrap().clone(); + let mut asset_coins = Coin::zero(); + for uxto in utxos { + if let Some(coin) = assets_amounts.get(&(asset_in_policy.clone(), uxto.clone())) { + asset_coins += coin.clone(); + } + } + size += Self::get_coin_size(&asset_coins); + } + } + size + } + + fn get_output_size_without_assets(&self) -> usize { + self.bare_output_size + } + + fn prepare_output_size_without_assets(max_coins: &Coin, address: &Address) -> usize { + let value = Value::new(max_coins); + let output = TransactionOutput::new(address, &value); + //output size without assets + overhead for storing coins and assents in the same time + //see cardano cddl for more info + output.to_bytes().len() + AssetSize::get_struct_size(2) + } +} \ No newline at end of file diff --git a/rust/src/tx_builder/batch_tools/assets_groups.rs b/rust/src/tx_builder/batch_tools/assets_groups.rs index d0df8313..1b99de59 100644 --- a/rust/src/tx_builder/batch_tools/assets_groups.rs +++ b/rust/src/tx_builder/batch_tools/assets_groups.rs @@ -1,7 +1,12 @@ +use std::cmp::max; use std::collections::HashMap; use js_sys::new; +use crate::tx_builder::batch_tools::abstract_map::AbstractMap; +use crate::tx_builder::batch_tools::asset_calculator::{AssetCalculator, UtxosStat}; use super::super::*; +const MAX_INLINE_ENCODING: u64 = 23; + #[derive(PartialEq, Eq, Hash, Clone)] pub struct UtxoIndex(usize); @@ -20,42 +25,43 @@ pub struct PolicyIndex(usize); #[derive(PartialEq, Eq, Clone)] pub struct PlaneAssetId(PolicyIndex, AssetName); -#[derive(PartialEq, Eq, Hash, Clone)] -struct AssetSizeCost { - full_size: usize, - without_policy_size: usize -} - -struct UtxoSizeCost { - input_size: usize, +pub struct AssetsForAppend { + utxo: UtxoIndex, + assets_for_current_output: HashMap, + assets_for_new_output: HashMap, + assets_for_rest_outputs: HashMap, } -struct UtxosStat { - assets_in_policy: HashMap, - coins_in_assets: HashMap, - ada_coins: Coin, +pub struct AssetsForAppendPrototype { + assets_for_current_output: HashSet, + assets_for_new_output: Option>, + assets_for_rest_outputs: HashSet, } pub struct AssetGroups { assets: Vec, policies: Vec, - assets_sizes: Vec, + assets_calculator: AssetCalculator, utxos: TransactionUnspentOutputs, assets_amounts: HashMap<(AssetIndex, UtxoIndex), Coin>, assets_counts: Vec<(AssetIndex, usize)>, + + //assets and utoxs that can be used free_utxo_to_assets: HashMap>, free_asset_to_utxos: HashMap>, + asset_to_policy: HashMap, policy_to_asset: HashMap>, inputs_sizes: Vec, - utxos_stat: UtxosStat + bare_output_size : usize } impl AssetGroups { - pub fn new(utxos: &TransactionUnspentOutputs) -> Self { + pub fn new(utxos: &TransactionUnspentOutputs, address: &Address) -> Self { let mut assets: Vec = Vec::new(); let mut policies: Vec = Vec::new(); - let mut assets_sizes: Vec = Vec::new(); + let mut assets_name_sizes: Vec = Vec::new(); + let mut policies_sizes: Vec = Vec::new(); let mut assets_amounts: HashMap<(AssetIndex, UtxoIndex), Coin> = HashMap::new(); //let mut assets_counts: Vec<(AssetIndex, usize)> = Vec::new(); let mut free_utxo_to_assets: HashMap> = HashMap::new(); @@ -65,29 +71,28 @@ impl AssetGroups { let mut asset_ids: HashMap = HashMap::new(); let mut policy_ids: HashMap = HashMap::new(); - let mut assets_counts: HashMap = HashMap::new(); - - let mut utxo_stat = UtxosStat { - assets_in_policy: HashMap::new(), - coins_in_assets: HashMap::new(), - ada_coins: Coin::zero() - }; + let mut assets_counts: Vec<(AssetIndex, usize)> = Vec::new(); let mut current_utxo_num = 0usize; let mut asset_count = 0usize; let mut policy_count = 0usize; + let mut total_ada = Coin::zero(); - let input_sizes = Self::get_inputs_sizes(&utxos); + let input_sizes = Self::get_inputs_sizes(&utxos); for utxo in &utxos.0 { - //TODO add input calc - let current_utxo_index= UtxoIndex(current_utxo_num.clone()); - if let Some(assests) = &utxo.output.amount.multiasset { + total_ada += utxo.output.amount.coin; + + let current_utxo_index = UtxoIndex(current_utxo_num.clone()); + if let Some(assests) = &utxo.output.amount.multiasset { for policy in &assests.0 { let mut current_policy_index = PolicyIndex(policy_count.clone()); - if Some(policy_index) = policy_ids.get(policy.0) { + if let Some(policy_index) = policy_ids.get(policy.0) { current_policy_index = policy_index.clone() } else { + let policy_id_size = AssetSize::get_struct_size(policy.0.len().into()); + policies.push(policy.0.clone()); + policies_sizes.push(policy_id_size); policy_ids.insert(policy.0.clone(), current_policy_index.clone()); policy_count += 1; } @@ -95,10 +100,15 @@ impl AssetGroups { for asset in &policy.1.0 { let mut current_asset_index = AssetIndex(asset_count.clone()); let plane_id = PlaneAssetId(current_policy_index.clone(), asset.0.clone()); - if Some(asset_index) = asset_ids.get(&plane_id) { + if let Some(asset_index) = asset_ids.get(&plane_id) { current_asset_index = asset_index.clone(); + assets_counts[current_asset_index.0].1 += 1; } else { - asset_ids.insert(plane_id, current_asset_index); + let asset_name_size = AssetSize::get_struct_size(asset.0.len().into()); + assets.push(plane_id.clone()); + assets_name_sizes.push(asset_name_size); + asset_ids.insert(plane_id, current_asset_index.clone()); + assets_counts.push((current_asset_index.clone(), 0)); asset_count += 1; } @@ -106,7 +116,7 @@ impl AssetGroups { if let Some(mut assets_set) = policy_to_asset.get(¤t_policy_index) { assets_set.insert(current_asset_index.clone()); } else { - let mut assets_set = HashSet::new(); + let mut assets_set = HashSet::new(); assets_set.insert(current_asset_index.clone()); policy_to_asset.insert(current_policy_index.clone(), assets_set); } @@ -126,25 +136,28 @@ impl AssetGroups { assets_set.insert(current_asset_index.clone()); free_utxo_to_assets.insert(current_utxo_index.clone(), assets_set); } - //TODO: add size calc } } } current_utxo_num += 1; } - AssetGroups( + let utxos_stat = UtxosStat::new(&total_ada, &policy_to_asset, &assets_amounts); + let asset_calculator = AssetCalculator::new(utxos_stat, assets_name_sizes, policies_sizes, address); + AssetGroups { assets, policies, assets_sizes, - utxos.clone(), + utxos: utxos.clone(), assets_amounts, assets_counts, free_utxo_to_assets, free_asset_to_utxos, asset_to_policy, policy_to_asset, - input_sizes) + inputs_sizes, + utxos_stat, + } } fn get_inputs_sizes(utoxs: &TransactionUnspentOutputs) -> Vec { @@ -156,12 +169,14 @@ impl AssetGroups { sizes } + + fn get_asset_intersections(&self, used_assets: &HashSet, used_assets_in_output: &HashSet) -> Vec<(AssetIndex, usize)> { let mut intersections = Vec::new(); for (index, asset_count) in &self.assets_counts { - if used_assets.contains(index) && self.free_asset_to_utxos.contains_key(index){ + if used_assets.contains(index) && self.free_asset_to_utxos.contains_key(index) { intersections.push((index.clone(), asset_count.clone())); } } @@ -170,7 +185,7 @@ impl AssetGroups { fn get_policy_intersections(&self, used_assets: &HashSet) -> Vec<(AssetIndex, usize)> { let mut intersections = Vec::new(); - let used_policies= used_assets.iter() + let used_policies = used_assets.iter() .filter_map(|x| self.asset_to_policy.get(x)); let available_assets: HashSet = used_policies .filter_map(|x| self.policy_to_asset.get(x)) @@ -178,30 +193,61 @@ impl AssetGroups { .cloned() .collect(); for (index, asset_count) in &self.assets_counts { - if available_assets.contains(index) && self.free_asset_to_utxos.contains_key(index){ + if available_assets.contains(index) && self.free_asset_to_utxos.contains_key(index) { intersections.push((index.clone(), asset_count.clone())); } } intersections } - fn if_add_to_output_possible(&self, + fn prototype_append_to_output(&self, used_assets: &HashSet, used_assets_in_output: &HashSet, utxo: &UtxoIndex, - value_free_space: &usize, tx_free_space: &usize) -> bool { - true + value_free_space: &usize, tx_free_space: &usize) -> Option { + let utxo_assets = self.free_utxo_to_assets.get(utxo); + if let Some(utxo_assets) = utxo_assets { + let output_intersection = used_assets_in_output & utxo_assets; + let rest_assets = utxo_assets - &output_intersection; + let asset_for_old_ouputs = &rest_assets & utxo_assets; + let asset_for_add = utxo_assets - &output_intersection - &asset_for_old_ouputs; + let new_ouput_state = asset_for_add + used_assets_in_output; + let new_size = self.assets_calculator.calc_aprox_value_size(&new_ouput_state); + if new_size <= *value_free_space { + return Some(AssetsForAppendPrototype { + assets_for_current_output: asset_for_add, + assets_for_new_output: None, + assets_for_rest_outputs:asset_for_old_ouputs, + }); + } + } + None } - fn if_add_to_tx_possible(&self, + fn prototype_append_to_tx(&self, used_assets: &HashSet, - utxo: &UtxoIndex, tx_free_space: &usize) -> bool { - true + utxo: &UtxoIndex, + value_free_space: &usize, tx_free_space: &usize) -> Option> { + let mut assets = Vec::new(); + + return None; + } + + fn get_grouped_assets(&self, assets: &HashSet) -> HashMap> { + let mut grouped_assets = HashMap::new(); + for asset in assets { + let policy_index = self.asset_to_policy.get(asset).unwrap(); + let assets_set = grouped_assets.entry(policy_index.clone()).or_insert(HashSet::new()); + assets_set.insert(asset.clone()); + } + grouped_assets } + + fn choose_candidate(&self, assets: &Vec<(AssetIndex, usize)>, value_free_space: &usize, tx_free_space: &usize) -> (Option, Option) { let mut output_utxo: Option = None; - let mut tx_utxo: Option = None; + let mut tx_utxo: Option = None; for (index, asset_count) in assets.iter() { let utxos_set = self.free_asset_to_utxos.get(index); if let Some(utxos) = utxos_set { @@ -213,7 +259,6 @@ impl AssetGroups { } } } - } (output_utxo, tx_utxo) @@ -248,34 +293,26 @@ impl AssetGroups { Some(res_utxo.clone()) } if tx_utxo.is_none() { - return tx_utxo_tmp.clone(); + return tx_utxo_tmp.clone(); } tx_utxo } - fn get_assets_indexes(&self, utxo_index: &UtxoIndex) -> HashSet{ + fn get_assets_indexes(&self, utxo_index: &UtxoIndex) -> HashSet { match &self.free_utxo_to_assets.get(utxo_index) { Some(&set) => set.clone(), None => HashSet::new() } } - fn get_asset_size(&self, asset_index: &AssetIndex) -> Result { + fn get_asset_size(&self, asset_index: &AssetIndex) -> Result { match &self.assets_sizes.get(asset_index.0) { Some(&size) => Ok(size.clone()), None => Err(JsError::from_str(&"Wrong index for asset sizes. Invalid AssetGroups state.")) } } - fn get_output_size_without_assets(max_coins: &Coin, address: &Address) -> usize { - //TODO: create static calculation - let value = Value::new(max_coins); - //TODO: add asset subtraction - let fake_asset = MultiAsset::new(); - let output = TransactionOutput::new(address, &value); - output.to_bytes().len() - } } diff --git a/rust/src/tx_builder/batch_tools/mod.rs b/rust/src/tx_builder/batch_tools/mod.rs index cc7c79bf..bc5e02a6 100644 --- a/rust/src/tx_builder/batch_tools/mod.rs +++ b/rust/src/tx_builder/batch_tools/mod.rs @@ -1,2 +1,3 @@ pub mod abstract_map; -pub mod assets_groups; \ No newline at end of file +pub mod assets_groups; +pub mod asset_calculator; \ No newline at end of file diff --git a/rust/src/utils.rs b/rust/src/utils.rs index e44cc9a4..7c4dc12b 100644 --- a/rust/src/utils.rs +++ b/rust/src/utils.rs @@ -1132,11 +1132,7 @@ impl MinOutputAdaCalculator { let coins_per_byte = self.data_cost.coins_per_byte(); let mut output: TransactionOutput = self.output.clone(); fn calc_required_coin(output: &TransactionOutput, coins_per_byte: &Coin) -> Result { - //according to https://hydra.iohk.io/build/15339994/download/1/babbage-changes.pdf - //See on the page 9 getValue txout - BigNum::from(output.to_bytes().len()) - .checked_add(&to_bignum(160))? - .checked_mul(&coins_per_byte) + Self::calc_size_cost(coins_per_byte,output.to_bytes().len()) } for _ in 0..3 { let required_coin = calc_required_coin(&output, &coins_per_byte)?; @@ -1155,6 +1151,13 @@ impl MinOutputAdaCalculator { let fake_value: Value = Value::new(&to_bignum(1000000)); Ok(TransactionOutput::new(&fake_base_address, &fake_value)) } + + fn calc_size_cost(coins_per_byte: &Coin, size: usize) -> Result { + //according to https://hydra.iohk.io/build/15339994/download/1/babbage-changes.pdf + //See on the page 9 getValue txout + size.into().checked_add(&to_bignum(160))? + .checked_mul(&coins_per_byte) + } } ///returns minimal amount of ada for the output for case when the amount is included to the output