Skip to content

Commit

Permalink
feat(mempool): implement priority queuedata structure
Browse files Browse the repository at this point in the history
  • Loading branch information
MohammadNassar1 committed Apr 15, 2024
1 parent 72a2b06 commit eca5cf1
Show file tree
Hide file tree
Showing 6 changed files with 172 additions and 5 deletions.
12 changes: 11 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 5 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[workspace]
resolver = "2"
members = ["crates/gateway", "crates/mempool_node"]
members = ["crates/gateway", "crates/mempool", "crates/mempool_node"]

[workspace.package]
version = "0.0.0"
Expand All @@ -21,15 +21,16 @@ as_conversions = "deny"
[workspace.dependencies]
assert_matches = "1.5.0"
axum = "0.6.12"
clap = { version = "4.3.10" }
clap = "4.3.10"
derive_more = "0.99"
hyper = "1.2.0"
papyrus_config = "0.3.0"
pretty_assertions = "1.4.0"
rstest = "0.17.0"
serde = { version = "1.0.193", features = ["derive"] }
serde_json = "1.0"
# TODO(Arni, 1/5/2024): Use a fixed version once the StarkNet API is stable.
starknet_api = { git = "https://github.com/starkware-libs/starknet-api.git", rev = "6b14aaf" }
papyrus_config = "0.3.0"
starknet_api = { git = "https://github.com/starkware-libs/starknet-api.git", rev = "016ff6c" }
thiserror = "1.0"
tokio = { version = "1", features = ["full"] }
tower = "0.4.13"
Expand Down
17 changes: 17 additions & 0 deletions crates/mempool/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[package]
name = "starknet_mempool"
version.workspace = true
edition.workspace = true
repository.workspace = true
license.workspace = true

[lints]
workspace = true

[dependencies]
derive_more.workspace = true
starknet_api.workspace = true

[dev-dependencies]
assert_matches.workspace = true
tokio.workspace = true
2 changes: 2 additions & 0 deletions crates/mempool/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// TODO: change to pub(crate) once this is used by the (not yet implemented) mempool struct.
pub mod priority_queue;
75 changes: 75 additions & 0 deletions crates/mempool/src/priority_queue.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
#[cfg(test)]
#[path = "priority_queue_test.rs"]
pub mod priority_queue_test;

use std::{cmp::Ordering, collections::BTreeSet};

use starknet_api::{
internal_transaction::InternalTransaction,
transaction::{DeclareTransaction, DeployAccountTransaction, InvokeTransaction, Tip},
};

// Assumption: for the MVP only one transaction from the same contract class can be in the mempool
// at a time. When this changes, saving the transactions themselves on the queu might no longer be
// appropriate, because we'll also need to stores transactions without indexing them. For example,
// transactions with future nonces will need to be stored, and potentially indexed on block commits.
#[derive(Clone, Debug, Default, derive_more::Deref, derive_more::DerefMut)]
pub struct PriorityQueue(BTreeSet<PQTransaction>);

impl PriorityQueue {
pub fn push(&mut self, tx: InternalTransaction) {
let mempool_tx = PQTransaction(tx);
self.insert(mempool_tx);
}

// Removes and returns the transaction with the highest tip.
pub fn pop(&mut self) -> Option<InternalTransaction> {
self.pop_last().map(|tx| tx.0)
}
}

#[derive(Clone, Debug, derive_more::Deref)]
pub struct PQTransaction(pub InternalTransaction);

impl PQTransaction {
fn tip(&self) -> Tip {
match &self.0 {
InternalTransaction::Declare(declare_tx) => match &declare_tx.tx {
DeclareTransaction::V3(tx_v3) => tx_v3.tip,
_ => unimplemented!(),
},
InternalTransaction::DeployAccount(deploy_account_tx) => match &deploy_account_tx.tx {
DeployAccountTransaction::V3(tx_v3) => tx_v3.tip,
_ => unimplemented!(),
},
InternalTransaction::Invoke(invoke_tx) => match &invoke_tx.tx {
InvokeTransaction::V3(tx_v3) => tx_v3.tip,
_ => unimplemented!(),
},
}
}
}

// Compare transactions based on their tip only, which implies `Eq`, because `tip` is uint.
impl PartialEq for PQTransaction {
fn eq(&self, other: &PQTransaction) -> bool {
self.tip() == other.tip()
}
}

/// Marks PQTransaction as capable of strict equality comparisons, signaling to the compiler it
/// adheres to equality semantics.
// Note: this depends on the implementation of `PartialEq`, see its docstring.
impl Eq for PQTransaction {}

impl Ord for PQTransaction {
fn cmp(&self, other: &Self) -> Ordering {
self.tip().cmp(&other.tip())
}
}

impl PartialOrd for PQTransaction {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
62 changes: 62 additions & 0 deletions crates/mempool/src/priority_queue_test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
use starknet_api::hash::StarkFelt;
use starknet_api::{
data_availability::DataAvailabilityMode,
internal_transaction::{InternalInvokeTransaction, InternalTransaction},
transaction::{
InvokeTransaction, InvokeTransactionV3, ResourceBounds, ResourceBoundsMapping, Tip,
TransactionHash,
},
};

use crate::priority_queue::PriorityQueue;

pub fn create_tx_for_testing(tip: Tip, tx_hash: TransactionHash) -> InternalTransaction {
let tx = InvokeTransactionV3 {
resource_bounds: ResourceBoundsMapping::try_from(vec![
(
starknet_api::transaction::Resource::L1Gas,
ResourceBounds::default(),
),
(
starknet_api::transaction::Resource::L2Gas,
ResourceBounds::default(),
),
])
.expect("Resource bounds mapping has unexpected structure."),
signature: Default::default(),
nonce: Default::default(),
sender_address: Default::default(),
calldata: Default::default(),
nonce_data_availability_mode: DataAvailabilityMode::L1,
fee_data_availability_mode: DataAvailabilityMode::L1,
paymaster_data: Default::default(),
account_deployment_data: Default::default(),
tip,
};

InternalTransaction::Invoke(InternalInvokeTransaction {
tx: InvokeTransaction::V3(tx),
tx_hash,
only_query: false,
})
}

#[tokio::test]
async fn test_priority_queue() {
let tx_hash_50 = TransactionHash(StarkFelt::ONE);
let tx_hash_100 = TransactionHash(StarkFelt::TWO);
let tx_hash_10 = TransactionHash(StarkFelt::THREE);

let tx_tip_50 = create_tx_for_testing(Tip(50), tx_hash_50);
let tx_tip_100 = create_tx_for_testing(Tip(100), tx_hash_100);
let tx_tip_10 = create_tx_for_testing(Tip(10), tx_hash_10);

let mut pq = PriorityQueue::default();
pq.push(tx_tip_50.clone());
pq.push(tx_tip_100.clone());
pq.push(tx_tip_10.clone());

assert_eq!(pq.pop().unwrap(), tx_tip_100);
assert_eq!(pq.pop().unwrap(), tx_tip_50);
assert_eq!(pq.pop().unwrap(), tx_tip_10);
}

0 comments on commit eca5cf1

Please sign in to comment.