-
Notifications
You must be signed in to change notification settings - Fork 31
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(starknet_l1_provider): add
SoftDeleteIndexMap
Previous implementation inside transaction manager didn't account for validate, the current implementation is more general, and can in the future perhaps be reused elsewhere after some changes are made.
- Loading branch information
Gilad Chase
committed
Jan 9, 2025
1 parent
be0cec0
commit 0c7d6df
Showing
7 changed files
with
186 additions
and
46 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
134 changes: 134 additions & 0 deletions
134
crates/starknet_l1_provider/src/soft_delete_index_map.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
use std::collections::HashSet; | ||
|
||
use indexmap::map::Entry; | ||
use indexmap::IndexMap; | ||
use starknet_api::executable_transaction::L1HandlerTransaction; | ||
use starknet_api::transaction::TransactionHash; | ||
|
||
/// An IndexMap that supports soft deletion of entries. | ||
/// Entries marked as deleted remain hidden in the map, allowing for potential recovery, | ||
/// selective permanent deletion, or rollback before being purged. | ||
// TODO: replace with a fully generic struct if there's a need for it. | ||
// TODO: replace with a BTreeIndexMap if commit performance becomes an issue, see note in commit. | ||
#[derive(Clone, Debug, Default)] | ||
pub struct SoftDeleteIndexMap { | ||
txs: IndexMap<TransactionHash, TransactionEntry>, | ||
staged_txs: HashSet<TransactionHash>, | ||
} | ||
|
||
impl SoftDeleteIndexMap { | ||
pub fn _new() -> Self { | ||
Self::default() | ||
} | ||
|
||
/// Inserts a transaction into the map, returning the previous transaction if it existed. | ||
pub fn _insert(&mut self, tx: L1HandlerTransaction) -> Option<L1HandlerTransaction> { | ||
match self.txs.entry(tx.tx_hash) { | ||
Entry::Occupied(entry) => { | ||
debug_assert_eq!(entry.get().transaction, tx); | ||
Some(entry.get().transaction.clone()) | ||
} | ||
Entry::Vacant(entry) => { | ||
entry.insert(TransactionEntry::new(tx)); | ||
None | ||
} | ||
} | ||
} | ||
|
||
/// Soft delete and return a reference to the first unstaged transaction, by insertion order. | ||
pub fn soft_pop_front(&mut self) -> Option<&L1HandlerTransaction> { | ||
let entry = self.txs.iter().find(|(_, tx)| tx.is_available()); | ||
let (&tx_hash, _) = entry?; | ||
self.soft_remove(tx_hash) | ||
} | ||
|
||
/// Stages the given transaction with the given hash if it exists and is not already staged, and | ||
/// returns a reference to it. | ||
pub fn soft_remove(&mut self, tx_hash: TransactionHash) -> Option<&L1HandlerTransaction> { | ||
let entry = self.txs.get_mut(&tx_hash)?; | ||
|
||
if !entry.is_available() { | ||
return None; | ||
} | ||
|
||
debug_assert_eq!(self.staged_txs.get(&tx_hash), None); | ||
entry.set_state(TxState::Staged); | ||
self.staged_txs.insert(tx_hash); | ||
|
||
Some(&entry.transaction) | ||
} | ||
|
||
/// Commits given transactions by removing them entirely and returning the removed transactions. | ||
/// Uncommitted staged transactions are rolled back to unstaged first. | ||
// Performance note: This operation is linear time with both the number | ||
// of known transactions and the number of committed transactions. This is assumed to be | ||
// good enough while l1-handler numbers remain low, but if this changes and we need log(n) | ||
// removals (amortized), replace indexmap with this (basically a BTreeIndexMap): | ||
// BTreeMap<u32, TransactionEntry>, Hashmap<TransactionHash, u32> and a counter: u32, such that | ||
// every new tx is inserted to the map with key counter++ and the counter is not reduced | ||
// when removing entries. Once the counter reaches u32::MAX/2 we recreate the DS in Theta(n). | ||
pub fn _commit(&mut self, tx_hashes: &[TransactionHash]) -> Vec<L1HandlerTransaction> { | ||
self._rollback_staging(); | ||
let tx_hashes: HashSet<_> = tx_hashes.iter().copied().collect(); | ||
if tx_hashes.is_empty() { | ||
return Vec::new(); | ||
} | ||
|
||
// NOTE: this takes Theta(|self.txs|), see docstring. | ||
let (committed, not_committed): (Vec<_>, Vec<_>) = | ||
self.txs.drain(..).partition(|(hash, _)| tx_hashes.contains(hash)); | ||
self.txs.extend(not_committed); | ||
|
||
committed.into_iter().map(|(_, entry)| entry.transaction).collect() | ||
} | ||
|
||
/// Rolls back all staged transactions, converting them to unstaged. | ||
pub fn _rollback_staging(&mut self) { | ||
for tx_hash in self.staged_txs.drain() { | ||
self.txs.entry(tx_hash).and_modify(|entry| entry.set_state(TxState::Unstaged)); | ||
} | ||
} | ||
|
||
pub fn is_staged(&self, tx_hash: &TransactionHash) -> bool { | ||
self.staged_txs.contains(tx_hash) | ||
} | ||
} | ||
|
||
impl From<Vec<L1HandlerTransaction>> for SoftDeleteIndexMap { | ||
fn from(txs: Vec<L1HandlerTransaction>) -> Self { | ||
let txs = txs.into_iter().map(|tx| (tx.tx_hash, TransactionEntry::new(tx))).collect(); | ||
SoftDeleteIndexMap { txs, ..Default::default() } | ||
} | ||
} | ||
|
||
/// Indicates whether a transaction is unstaged or staged. | ||
#[derive(Debug, Clone)] | ||
enum TxState { | ||
Unstaged, | ||
Staged, | ||
} | ||
|
||
/// Wraps an L1HandlerTransaction along with its current TxState, | ||
/// and provides convenience methods for stage/unstage. | ||
#[derive(Debug, Clone)] | ||
struct TransactionEntry { | ||
pub transaction: L1HandlerTransaction, | ||
pub state: TxState, | ||
} | ||
|
||
impl TransactionEntry { | ||
pub fn new(transaction: L1HandlerTransaction) -> Self { | ||
Self { transaction, state: TxState::Unstaged } | ||
} | ||
|
||
pub fn set_state(&mut self, state: TxState) { | ||
self.state = state | ||
} | ||
|
||
pub fn is_available(&self) -> bool { | ||
match self.state { | ||
TxState::Unstaged => true, | ||
TxState::Staged => false, | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters