Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

moved transaction_records_by_id methods into it #940

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
9b02cd0
moved transaction_records_by_id methods into it
fluidvanadium Apr 12, 2024
fcf5c46
Merge remote-tracking branch 'labs/dev' into move_methods_to_transact…
fluidvanadium Apr 12, 2024
22110be
Addressed review comments:
fluidvanadium Apr 15, 2024
8531744
clippy auto
fluidvanadium Apr 15, 2024
2ac142a
clippy p2
fluidvanadium Apr 15, 2024
a755a64
Merge remote-tracking branch 'labs/dev' into move_methods_to_transact…
fluidvanadium Apr 15, 2024
0742046
Update zingolib/src/wallet/transactions/recording.rs
fluidvanadium Apr 16, 2024
9e7ee48
Update zingolib/src/wallet/transaction_records_by_id.rs
fluidvanadium Apr 16, 2024
dbc21c0
Update zingolib/src/wallet/transaction_records_by_id.rs
fluidvanadium Apr 16, 2024
ae34042
Update zingolib/src/wallet/transaction_records_by_id.rs
fluidvanadium Apr 16, 2024
d1b18bb
Update zingolib/src/wallet/transaction_records_by_id.rs
fluidvanadium Apr 16, 2024
2187965
Update zingolib/src/wallet/transaction_records_by_id.rs
fluidvanadium Apr 16, 2024
972c3c9
Merge remote-tracking branch 'labs/dev' into move_methods_to_transact…
fluidvanadium Apr 16, 2024
e05b6d6
clarified some function names. added some doc-comments. removed an ac…
fluidvanadium Apr 16, 2024
a3af1cf
improve doc comments
fluidvanadium Apr 16, 2024
e074767
Merge remote-tracking branch 'labs/dev' into move_methods_to_transact…
fluidvanadium Apr 16, 2024
9861c48
even clearer doc-comment
fluidvanadium Apr 16, 2024
4502e50
Merge remote-tracking branch 'labs/dev' into move_methods_to_transact…
fluidvanadium Apr 16, 2024
6a0c7d3
Merge remote-tracking branch 'labs/dev' into move_methods_to_transact…
fluidvanadium Apr 17, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion zingolib/src/blaze/block_management_reorg_detection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,7 @@ impl BlockManagementData {
transaction_metadata_set
.write()
.await
.remove_txns_at_height(reorg_height);
.invalidate_all_transactions_after_or_at_height(reorg_height);
}
}

Expand Down
120 changes: 118 additions & 2 deletions zingolib/src/wallet/transaction_records_by_id.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,18 @@
use std::collections::HashMap;

use orchard::note_encryption::OrchardDomain;
use sapling_crypto::note_encryption::SaplingDomain;

use zcash_note_encryption::Domain;
use zcash_primitives::consensus::BlockHeight;

use zcash_primitives::transaction::TxId;

use crate::wallet::data::TransactionRecord;
use crate::wallet::{
data::TransactionRecord,
notes::NoteInterface,
traits::{DomainWalletExt, Recipient},
};

#[derive(Debug)]
pub struct TransactionRecordsById(pub HashMap<TxId, TransactionRecord>);
Expand All @@ -20,9 +30,115 @@ impl std::ops::DerefMut for TransactionRecordsById {
&mut self.0
}
}

/// Constructors
impl TransactionRecordsById {
// Associated function to create a TransactionRecordMap from a HashMap
/// Constructs a new TransactionRecordsById with an empty map.
pub fn new() -> Self {
TransactionRecordsById(HashMap::new())
}
// Constructs a TransactionRecordMap from a HashMap
pub fn from_map(map: HashMap<TxId, TransactionRecord>) -> Self {
TransactionRecordsById(map)
}
}
/// Methods to modify the map.
impl TransactionRecordsById {
/// Invalidates all transactions from a given height including the block with block height `reorg_height`
///
/// All information above a certain height is invalidated during a reorg.
pub fn invalidate_all_transactions_after_or_at_height(&mut self, reorg_height: BlockHeight) {
fluidvanadium marked this conversation as resolved.
Show resolved Hide resolved
// First, collect txids that need to be removed
let txids_to_remove = self
.values()
.filter_map(|transaction_metadata| {
if transaction_metadata
.status
.is_confirmed_after_or_at(&reorg_height)
fluidvanadium marked this conversation as resolved.
Show resolved Hide resolved
|| transaction_metadata
.status
.is_broadcast_after_or_at(&reorg_height)
// tODo: why dont we only remove confirmed transactions. unconfirmed transactions may still be valid in the mempool and may later confirm or expire.
{
Some(transaction_metadata.txid)
} else {
None
}
})
.collect::<Vec<_>>();
self.invalidate_transactions(txids_to_remove);
}
/// Invalidiates a vec of txids by removing them and then all references to them.
///
/// A transaction can be invalidated either by a reorg or if it was never confirmed by a miner.
/// This is required in the case that a note was spent in a invalidated transaction.
/// Takes a slice of txids corresponding to the invalidated transactions, searches all notes for being spent in one of those txids, and resets them to unspent.
pub(crate) fn invalidate_transactions(&mut self, txids_to_remove: Vec<TxId>) {
for txid in &txids_to_remove {
self.remove(txid);
}

self.invalidate_transaction_specific_transparent_spends(&txids_to_remove);
// roll back any sapling spends in each invalidated tx
self.invalidate_transaction_specific_domain_spends::<SaplingDomain>(&txids_to_remove);
// roll back any orchard spends in each invalidated tx
self.invalidate_transaction_specific_domain_spends::<OrchardDomain>(&txids_to_remove);
}
/// Reverts any spent transparent notes in the given transactions to unspent.
pub(crate) fn invalidate_transaction_specific_transparent_spends(
&mut self,
invalidated_txids: &[TxId],
) {
self.values_mut().for_each(|transaction_metadata| {
// Update UTXOs to roll back any spent utxos
transaction_metadata
.transparent_notes
.iter_mut()
.for_each(|utxo| {
if utxo.is_spent() && invalidated_txids.contains(&utxo.spent().unwrap().0) {
*utxo.spent_mut() = None;
}

if utxo.unconfirmed_spent.is_some()
&& invalidated_txids.contains(&utxo.unconfirmed_spent.unwrap().0)
{
utxo.unconfirmed_spent = None;
}
})
});
}
/// Reverts any spent shielded notes in the given transactions to unspent.
pub(crate) fn invalidate_transaction_specific_domain_spends<D: DomainWalletExt>(
&mut self,
invalidated_txids: &[TxId],
) where
<D as Domain>::Recipient: Recipient,
<D as Domain>::Note: PartialEq + Clone,
{
self.values_mut().for_each(|transaction_metadata| {
// Update notes to rollback any spent notes
D::to_notes_vec_mut(transaction_metadata)
.iter_mut()
.for_each(|nd| {
// Mark note as unspent if the txid being removed spent it.
if nd.spent().is_some() && invalidated_txids.contains(&nd.spent().unwrap().0) {
*nd.spent_mut() = None;
}

// Remove unconfirmed spends too
if nd.pending_spent().is_some()
&& invalidated_txids.contains(&nd.pending_spent().unwrap().0)
{
*nd.pending_spent_mut() = None;
}
});
});
}
}

impl Default for TransactionRecordsById {
/// Default constructor
fn default() -> Self {
Self::new()
}
}
6 changes: 2 additions & 4 deletions zingolib/src/wallet/transactions.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
use std::collections::HashMap;

use crate::wallet::{data::WitnessTrees, transaction_records_by_id::TransactionRecordsById};

/// HashMap of all transactions in a wallet, keyed by txid.
Expand All @@ -17,13 +15,13 @@ pub mod recording;
impl TxMapAndMaybeTrees {
pub(crate) fn new_with_witness_trees() -> TxMapAndMaybeTrees {
Self {
transaction_records_by_id: TransactionRecordsById(HashMap::new()),
transaction_records_by_id: TransactionRecordsById::new(),
witness_trees: Some(WitnessTrees::default()),
}
}
pub(crate) fn new_treeless() -> TxMapAndMaybeTrees {
Self {
transaction_records_by_id: TransactionRecordsById(HashMap::new()),
transaction_records_by_id: TransactionRecordsById::new(),
witness_trees: None,
}
}
Expand Down
84 changes: 7 additions & 77 deletions zingolib/src/wallet/transactions/recording.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,85 +24,13 @@ use crate::{

use super::TxMapAndMaybeTrees;
impl TxMapAndMaybeTrees {
pub fn remove_txids(&mut self, txids_to_remove: Vec<TxId>) {
for txid in &txids_to_remove {
self.transaction_records_by_id.remove(txid);
}
self.transaction_records_by_id
.values_mut()
.for_each(|transaction_metadata| {
// Update UTXOs to rollback any spent utxos
transaction_metadata
.transparent_notes
.iter_mut()
.for_each(|utxo| {
if utxo.is_spent() && txids_to_remove.contains(&utxo.spent().unwrap().0) {
*utxo.spent_mut() = None;
}

if utxo.unconfirmed_spent.is_some()
&& txids_to_remove.contains(&utxo.unconfirmed_spent.unwrap().0)
{
utxo.unconfirmed_spent = None;
}
})
});
self.remove_domain_specific_txids::<SaplingDomain>(&txids_to_remove);
self.remove_domain_specific_txids::<OrchardDomain>(&txids_to_remove);
}
/// During reorgs, we need to remove all txns at a given height, and all spends that refer to any removed txns.
pub fn invalidate_all_transactions_after_or_at_height(&mut self, reorg_height: u64) {
let reorg_height = BlockHeight::from_u32(reorg_height as u32);

fn remove_domain_specific_txids<D: DomainWalletExt>(&mut self, txids_to_remove: &[TxId])
where
<D as Domain>::Recipient: Recipient,
<D as Domain>::Note: PartialEq + Clone,
{
self.transaction_records_by_id
.values_mut()
.for_each(|transaction_metadata| {
// Update notes to rollback any spent notes
D::to_notes_vec_mut(transaction_metadata)
.iter_mut()
.for_each(|nd| {
// Mark note as unspent if the txid being removed spent it.
if nd.spent().is_some() && txids_to_remove.contains(&nd.spent().unwrap().0)
{
*nd.spent_mut() = None;
}
.invalidate_all_transactions_after_or_at_height(reorg_height);

// Remove unconfirmed spends too
if nd.pending_spent().is_some()
&& txids_to_remove.contains(&nd.pending_spent().unwrap().0)
{
*nd.pending_spent_mut() = None;
}
});
});
}

// During reorgs, we need to remove all txns at a given height, and all spends that refer to any removed txns.
pub fn remove_txns_at_height(&mut self, reorg_height: u64) {
let reorg_height = BlockHeight::from_u32(reorg_height as u32);

// First, collect txids that need to be removed
let txids_to_remove = self
.transaction_records_by_id
.values()
.filter_map(|transaction_metadata| {
if transaction_metadata
.status
.is_confirmed_after_or_at(&reorg_height)
|| transaction_metadata
.status
.is_broadcast_after_or_at(&reorg_height)
// tODo: why dont we only remove confirmed transactions. unconfirmed transactions may still be valid in the mempool and may later confirm or expire.
{
Some(transaction_metadata.txid)
} else {
None
}
})
.collect::<Vec<_>>();
self.remove_txids(txids_to_remove);
if let Some(ref mut t) = self.witness_trees {
t.witness_tree_sapling
.truncate_removing_checkpoint(&(reorg_height - 1))
Expand All @@ -114,6 +42,7 @@ impl TxMapAndMaybeTrees {
}
}

/// Invalidates all those transactions which were broadcast but never 'confirmed' accepted by a miner.
pub(crate) fn clear_expired_mempool(&mut self, latest_height: u64) {
let cutoff = BlockHeight::from_u32((latest_height.saturating_sub(MAX_REORG as u64)) as u32);

Expand All @@ -130,7 +59,8 @@ impl TxMapAndMaybeTrees {
.iter()
.for_each(|t| println!("Removing expired mempool tx {}", t));

self.remove_txids(txids_to_remove);
self.transaction_records_by_id
.invalidate_transactions(txids_to_remove);
}

// Check this transaction to see if it is an outgoing transaction, and if it is, mark all received notes with non-textual memos in this
Expand Down
Loading