From cc952c06bf29b6ede734d0397951e0029bdba8c7 Mon Sep 17 00:00:00 2001 From: tommytrg Date: Fri, 20 Oct 2023 13:24:47 +0200 Subject: [PATCH] w --- data_structures/src/chain/mod.rs | 194 +++++++++++++++++++++++++++++-- validations/src/validations.rs | 12 +- 2 files changed, 191 insertions(+), 15 deletions(-) diff --git a/data_structures/src/chain/mod.rs b/data_structures/src/chain/mod.rs index bde7ade8e..27d0bbc71 100644 --- a/data_structures/src/chain/mod.rs +++ b/data_structures/src/chain/mod.rs @@ -518,9 +518,17 @@ impl Block { } st_weight } + + pub fn ut_weight(&self) -> u32 { + let mut ut_weight = 0; + for ut_txn in self.txns.unstake_txns.iter() { + ut_weight += ut_txn.weight(); + } + ut_weight + } pub fn weight(&self) -> u32 { - self.dr_weight() + self.vt_weight() + self.st_weight() + self.dr_weight() + self.vt_weight() + self.st_weight() + self.ut_weight() } } @@ -535,6 +543,7 @@ impl BlockTransactions { + self.reveal_txns.len() + self.tally_txns.len() + self.stake_txns.len() + + self.unstake_txns.len() } /// Returns true if this block contains no transactions @@ -547,6 +556,7 @@ impl BlockTransactions { && self.reveal_txns.is_empty() && self.tally_txns.is_empty() && self.stake_txns.is_empty() + && self.unstake_txns.is_empty() } /// Get a transaction given the `TransactionPointer` @@ -583,6 +593,11 @@ impl BlockTransactions { .get(i as usize) .cloned() .map(Transaction::Stake), + TransactionPointer::Unstake(i) => self + .unstake_txns + .get(i as usize) + .cloned() + .map(Transaction::Unstake), } } @@ -630,6 +645,11 @@ impl BlockTransactions { TransactionPointer::Stake(u32::try_from(i).unwrap()); items_to_add.push((tx.hash(), pointer_to_block.clone())); } + for (i, tx) in self.unstake_txns.iter().enumerate() { + pointer_to_block.transaction_index = + TransactionPointer::Unstake(u32::try_from(i).unwrap()); + items_to_add.push((tx.hash(), pointer_to_block.clone())); + } items_to_add } @@ -1983,6 +2003,7 @@ type PrioritizedHash = (OrderedFloat, Hash); type PrioritizedVTTransaction = (OrderedFloat, VTTransaction); type PrioritizedDRTransaction = (OrderedFloat, DRTransaction); type PrioritizedStakeTransaction = (OrderedFloat, StakeTransaction); +type PrioritizedUnstakeTransaction = (OrderedFloat, UnstakeTransaction); #[derive(Debug, Clone, Default)] struct UnconfirmedTransactions { @@ -2041,6 +2062,8 @@ pub struct TransactionsPool { total_dr_weight: u64, // Total size of all stake transactions inside the pool in weight units total_st_weight: u64, + // Total size of all unstake transactions inside the pool in weight units + total_ut_weight: u64, // TransactionsPool size limit in weight units weight_limit: u64, // Ratio of value transfer transaction to data request transaction that should be in the @@ -2063,9 +2086,14 @@ pub struct TransactionsPool { unconfirmed_transactions: UnconfirmedTransactions, st_transactions: HashMap, sorted_st_index: BTreeSet, + ut_transactions: HashMap, + sorted_ut_index: BTreeSet, // Minimum fee required to include a Stake Transaction into a block. We check for this fee in the // TransactionPool so we can choose not to insert a transaction we will not mine anyway. minimum_st_fee: u64, + // Minimum fee required to include a Unstake Transaction into a block. We check for this fee in the + // TransactionPool so we can choose not to insert a transaction we will not mine anyway. + minimum_ut_fee: u64, } impl Default for TransactionsPool { @@ -2083,6 +2111,7 @@ impl Default for TransactionsPool { total_vt_weight: 0, total_dr_weight: 0, total_st_weight: 0, + total_ut_weight: 0, // Unlimited by default weight_limit: u64::MAX, // Try to keep the same amount of value transfer weight and data request weight @@ -2091,6 +2120,8 @@ impl Default for TransactionsPool { minimum_vtt_fee: 0, // Default is to include all transactions into the pool and blocks minimum_st_fee: 0, + // Default is to include all transactions into the pool and blocks + minimum_ut_fee: 0, // Collateral minimum from consensus constants collateral_minimum: 0, // Required minimum reward to collateral percentage is defined as a consensus constant @@ -2098,6 +2129,8 @@ impl Default for TransactionsPool { unconfirmed_transactions: Default::default(), st_transactions: Default::default(), sorted_st_index: Default::default(), + ut_transactions: Default::default(), + sorted_ut_index: Default::default(), } } } @@ -2170,6 +2203,7 @@ impl TransactionsPool { && self.co_transactions.is_empty() && self.re_transactions.is_empty() && self.st_transactions.is_empty() + && self.ut_transactions.is_empty() } /// Remove all the transactions but keep the allocated memory for reuse. @@ -2187,15 +2221,19 @@ impl TransactionsPool { total_vt_weight, total_dr_weight, total_st_weight, + total_ut_weight, weight_limit: _, vt_to_dr_factor: _, minimum_vtt_fee: _, minimum_st_fee: _, + minimum_ut_fee: _, collateral_minimum: _, required_reward_collateral_ratio: _, unconfirmed_transactions, st_transactions, sorted_st_index, + ut_transactions, + sorted_ut_index, } = self; vt_transactions.clear(); @@ -2210,9 +2248,12 @@ impl TransactionsPool { *total_vt_weight = 0; *total_dr_weight = 0; *total_st_weight = 0; + *total_ut_weight = 0; unconfirmed_transactions.clear(); st_transactions.clear(); sorted_st_index.clear(); + ut_transactions.clear(); + sorted_ut_index.clear(); } /// Returns the number of value transfer transactions in the pool. @@ -2278,6 +2319,27 @@ impl TransactionsPool { self.st_transactions.len() } + /// Returns the number of unstake transactions in the pool. + /// + /// # Examples: + /// + /// ``` + /// # use witnet_data_structures::chain::{TransactionsPool, Hash}; + /// # use witnet_data_structures::transaction::{Transaction, StakeTransaction}; + /// let mut pool = TransactionsPool::new(); + /// + /// let transaction = Transaction::Stake(StakeTransaction::default()); + /// + /// assert_eq!(pool.st_len(), 0); + /// + /// pool.insert(transaction, 0); + /// + /// assert_eq!(pool.st_len(), 1); + /// ``` + pub fn ut_len(&self) -> usize { + self.ut_transactions.len() + } + /// Clear commit transactions in TransactionsPool pub fn clear_commits(&mut self) { self.co_transactions.clear(); @@ -2319,8 +2381,8 @@ impl TransactionsPool { // be impossible for nodes to broadcast these kinds of transactions. Transaction::Tally(_tt) => Err(TransactionError::NotValidTransaction), Transaction::Mint(_mt) => Err(TransactionError::NotValidTransaction), - Transaction::Stake(_mt) => Ok(self.st_contains(&tx_hash)), - Transaction::Unstake(_mt) => !unimplemented!("contains Unstake tx"), + Transaction::Stake(_st) => Ok(self.st_contains(&tx_hash)), + Transaction::Unstake(_ut) => Ok(self.ut_contains(&tx_hash)), } } @@ -2437,6 +2499,30 @@ impl TransactionsPool { self.st_transactions.contains_key(key) } + /// Returns `true` if the pool contains an unstake transaction for the + /// specified hash. + /// + /// The `key` may be any borrowed form of the hash, but `Hash` and + /// `Eq` on the borrowed form must match those for the key type. + /// + /// # Examples: + /// ``` + /// # use witnet_data_structures::chain::{TransactionsPool, Hash, Hashable}; + /// # use witnet_data_structures::transaction::{Transaction, UnstakeTransaction}; + /// let mut pool = TransactionsPool::new(); + /// + /// let transaction = Transaction::Stake(UnstakeTransaction::default()); + /// let hash = transaction.hash(); + /// assert!(!pool.ut_contains(&hash)); + /// + /// pool.insert(transaction, 0); + /// + /// assert!(pool.t_contains(&hash)); + /// ``` + pub fn ut_contains(&self, key: &Hash) -> bool { + self.ut_transactions.contains_key(key) + } + /// Remove a value transfer transaction from the pool and make sure that other transactions /// that may try to spend the same UTXOs are also removed. /// This should be used to remove transactions that got included in a consolidated block. @@ -2699,6 +2785,58 @@ impl TransactionsPool { }) } + /// Remove an unstake transaction from the pool and make sure that other transactions + /// that may try to spend the same UTXOs are also removed. + /// This should be used to remove transactions that got included in a consolidated block. + /// + /// Returns an `Option` with the value transfer transaction for the specified hash or `None` if not exist. + /// + /// The `key` may be any borrowed form of the hash, but `Hash` and + /// `Eq` on the borrowed form must match those for the key type. + /// + /// # Examples: + /// ``` + /// # use witnet_data_structures::chain::{TransactionsPool, Hash, Hashable}; + /// # use witnet_data_structures::transaction::{Transaction, UnstakeTransaction}; + /// let mut pool = TransactionsPool::new(); + /// let vt_transaction = UnstakeTransaction::default(); + /// let transaction = Transaction::Unstake(ut_transaction.clone()); + /// pool.insert(transaction.clone(),0); + /// + /// assert!(pool.ut_contains(&transaction.hash())); + /// + /// let op_transaction_removed = pool.ut_remove(&ut_transaction); + /// + /// assert_eq!(Some(ut_transaction), op_transaction_removed); + /// assert!(!pool.ut_contains(&transaction.hash())); + /// ``` + pub fn ut_remove(&mut self, tx: &UnstakeTransaction) -> Option { + let key = tx.hash(); + let transaction = self.ut_remove_inner(&key, true); + + transaction + } + + /// Remove a stake transaction from the pool but do not remove other transactions that + /// may try to spend the same UTXOs. + /// This should be used to remove transactions that did not get included in a consolidated + /// block. + /// If the transaction did get included in a consolidated block, use `st_remove` instead. + fn ut_remove_inner(&mut self, key: &Hash, consolidated: bool) -> Option { + // TODO: is this taking into account the change and the stake output? + self.ut_transactions + .remove(key) + .map(|(weight, transaction)| { + self.sorted_ut_index.remove(&(weight, *key)); + self.total_ut_weight -= u64::from(transaction.weight()); + // TODO? + // if !consolidated { + // self.remove_tx_from_output_pointer_map(key, &transaction.body.); + // } + transaction + }) + } + /// Returns a tuple with a vector of reveal transactions and the value /// of all the fees obtained with those reveals pub fn get_reveals(&self, dr_pool: &DataRequestPool) -> (Vec<&RevealTransaction>, u64) { @@ -2914,6 +3052,27 @@ impl TransactionsPool { self.sorted_st_index.insert((priority, key)); } } + Transaction::Unstake(ut_tx) => { + let weight = f64::from(ut_tx.weight()); + let priority = OrderedFloat(fee as f64 / weight); + + if fee < self.minimum_ut_fee { + return vec![Transaction::Unstake(ut_tx)]; + } else { + self.total_st_weight += u64::from(ut_tx.weight()); + + // TODO + // for input in &ut_tx.body.inputs { + // self.output_pointer_map + // .entry(input.output_pointer) + // .or_insert_with(Vec::new) + // .push(ut_tx.hash()); + // } + + self.ut_transactions.insert(key, (priority, ut_tx)); + self.sorted_ut_index.insert((priority, key)); + } + } tx => { panic!( "Transaction kind not supported by TransactionsPool: {:?}", @@ -2970,6 +3129,15 @@ impl TransactionsPool { .rev() .filter_map(move |(_, h)| self.st_transactions.get(h).map(|(_, t)| t)) } + + /// An iterator visiting all the unstake transactions + /// in the pool + pub fn ut_iter(&self) -> impl Iterator { + self.sorted_ut_index + .iter() + .rev() + .filter_map(move |(_, h)| self.ut_transactions.get(h).map(|(_, t)| t)) + } /// Returns a reference to the value corresponding to the key. /// @@ -3065,11 +3233,16 @@ impl TransactionsPool { self.re_hash_index .get(hash) .map(|rt| Transaction::Reveal(rt.clone())) - .or_else(|| { - self.st_transactions - .get(hash) - .map(|(_, st)| Transaction::Stake(st.clone())) - }) + }) + .or_else(|| { + self.st_transactions + .get(hash) + .map(|(_, st)| Transaction::Stake(st.clone())) + }) + .or_else(|| { + self.ut_transactions + .get(hash) + .map(|(_, ut)| Transaction::Unstake(ut.clone())) }) } @@ -3098,6 +3271,9 @@ impl TransactionsPool { Transaction::Stake(_) => { let _x = self.st_remove_inner(&hash, false); } + Transaction::Unstake(_) => { + let _x = self.ut_remove_inner(&hash, false); + } _ => continue, } @@ -3206,6 +3382,8 @@ pub enum TransactionPointer { Mint, // Stake Stake(u32), + // Unstake + Unstake(u32), } /// This is how transactions are stored in the database: hash of the containing block, plus index diff --git a/validations/src/validations.rs b/validations/src/validations.rs index 729e7ff3d..0f929ec86 100644 --- a/validations/src/validations.rs +++ b/validations/src/validations.rs @@ -134,11 +134,9 @@ pub fn st_transaction_fee( /// their value. pub fn ut_transaction_fee( ut_tx: &UnstakeTransaction, - epoch: Epoch, - epoch_constants: EpochConstants, ) -> Result { let in_value = ut_tx.body.withdrawal.value; - let mut out_value = ut_tx.body.change.unwrap_or(Default::default()).value; + let out_value = ut_tx.body.clone().change.unwrap_or(Default::default()).value; if out_value > in_value { Err(TransactionError::NegativeFee.into()) @@ -1196,9 +1194,9 @@ pub fn validate_stake_transaction<'a>( pub fn validate_unstake_transaction<'a>( ut_tx: &'a UnstakeTransaction, st_tx: &'a StakeTransaction, - utxo_diff: &UtxoDiff<'_>, - epoch: Epoch, - epoch_constants: EpochConstants, + _utxo_diff: &UtxoDiff<'_>, + _epoch: Epoch, + _epoch_constants: EpochConstants, ) -> Result<(u64, u32, &'a Option), failure::Error> { // Check if is unstaking more than the total stake let amount_to_unstake = ut_tx.body.withdrawal.value; @@ -1228,7 +1226,7 @@ pub fn validate_unstake_transaction<'a>( validate_unstake_timelock(&ut_tx)?; // let fee = ut_tx.body.withdrawal.value; - let fee = ut_transaction_fee(ut_tx, epoch, epoch_constants)?; + let fee = ut_transaction_fee(ut_tx)?; let weight = st_tx.weight(); Ok((fee, weight, &ut_tx.body.change)) }