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

fix: update miner mempool iterator query to consider both nonces and fee rates #5541

Open
wants to merge 60 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
b0705d0
fix: new query
rafaelcr Dec 7, 2024
a0600b6
chore: remove dev comment
rafaelcr Dec 7, 2024
3719188
style: lint fixes
rafaelcr Dec 11, 2024
2cd116f
fix: add simulated fee rates for null
rafaelcr Dec 13, 2024
685924c
fix: indexes
rafaelcr Dec 13, 2024
e1dac9d
fix: remove tx_fee column
rafaelcr Dec 13, 2024
c587c53
Merge branch 'fix/mempool-query' of github.com:rafaelcr/stacks-core i…
rafaelcr Dec 13, 2024
dd9729c
test: correct tx order
rafaelcr Dec 15, 2024
2406d25
Merge branch 'develop' into fix/mempool-query
rafaelcr Dec 15, 2024
7702f67
fix: nonce ordering
rafaelcr Dec 16, 2024
89d1078
Merge branch 'fix/mempool-query' of github.com:rafaelcr/stacks-core i…
rafaelcr Dec 16, 2024
d0d1f8d
fix: remove now unneccessary index
rafaelcr Dec 16, 2024
cf6c37b
chore: config strategy
rafaelcr Dec 17, 2024
b8b9b89
chore: strategy selection draft
rafaelcr Dec 17, 2024
ea49875
fix: correct tx confirmation order
rafaelcr Dec 17, 2024
a56baef
test: success
rafaelcr Dec 18, 2024
a854f3b
test: missing nonces from table
rafaelcr Dec 18, 2024
c24c2ad
chore: merge upstream develop
rafaelcr Dec 18, 2024
07cf97f
style: fixes
rafaelcr Dec 18, 2024
0b0b821
style: error transforms
rafaelcr Dec 18, 2024
635cfe3
fix: style
rafaelcr Dec 19, 2024
5038c4d
Merge branch 'develop' into fix/mempool-query
rafaelcr Dec 19, 2024
8b4ec39
feat: redesign nonce cache
obycode Dec 19, 2024
e7eb1cc
chore: remove debug print
obycode Dec 20, 2024
9eb3921
chore: small changes from code review
obycode Dec 20, 2024
5269ed5
chore: fix clippy warning
obycode Jan 3, 2025
3a433b4
Merge branch 'develop' into fix/mempool-query
obycode Mar 5, 2025
70bb65d
Merge branch 'feat/persist-nonce-cache-dev' into fix/mempool-query
obycode Mar 6, 2025
f251a4a
fix: remove the `candidate_cache` during mempool iteration
obycode Mar 6, 2025
6893a2a
fix: resolve DB lock issue
obycode Mar 7, 2025
e9cc50c
chore: remove candidate cache
obycode Mar 10, 2025
635cd48
test: add `large_mempool` integration test
obycode Mar 11, 2025
64d85c2
chore: cleanup
obycode Mar 7, 2025
ebadadb
test: insert transactions directly into the mempool
obycode Mar 12, 2025
967b6d1
feat: improve `NextNonceWithHighestFeeRate` algorithm
obycode Mar 12, 2025
3274f08
feat: further mempool iteration improvements and testing
obycode Mar 14, 2025
3709e96
test: use random fees for `larger_mempool` test
obycode Mar 14, 2025
db4dc8c
refactor: move testing utilities to a common location
obycode Mar 14, 2025
031a824
test: add test for mempool walk with large mempool
obycode Mar 14, 2025
6fd9d07
Merge branch 'develop' into fix/mempool-query
obycode Mar 14, 2025
97c3807
chore: remove unused import
obycode Mar 14, 2025
7f93806
test: fix `large_mempool_random_fee`
obycode Mar 15, 2025
36f2cff
chore: adjust `LIMIT` in mempool query
obycode Mar 15, 2025
fcaf8c5
test: add `tests::signer::v0::larger_mempool`
obycode Mar 17, 2025
0b25c2c
chore: fix merge conflict
obycode Mar 17, 2025
38b8464
chore: put candidate cache back for `GlobalFeeRate` strategy
obycode Mar 17, 2025
0b547a8
fix: resolve issue with `GlobalFeeRate` strategy
obycode Mar 17, 2025
c86ff5a
test: fix Bitcoin test exclusions
obycode Mar 17, 2025
66f7b4c
test: update mempool unit test for latest algorithm
obycode Mar 17, 2025
c757dbe
test: refactor and clean up tests
obycode Mar 18, 2025
ab22c32
test: only assert transaction count for new strategy
obycode Mar 18, 2025
67cc828
chore: clarify order in test
obycode Mar 18, 2025
766278f
fix: only open `nonce_conn` once in `iterate_candidates`
obycode Mar 18, 2025
b2503f2
fix: error in LruCache when evicting clean value
obycode Mar 19, 2025
d9ff63c
fix: reset nonce cache when assembled block is not accepted
obycode Mar 20, 2025
883fbac
fix: nonce cache reset logic
obycode Mar 20, 2025
e4271b3
test: fix large mempool test logic
obycode Mar 20, 2025
1c4303c
Merge branch 'develop' into fix/mempool-query
obycode Mar 20, 2025
94b55c5
feat: add `considered_txs` table
obycode Mar 21, 2025
48ca4e5
test: reduce test flakiness by ignoring phantom transactions
obycode Mar 21, 2025
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
7 changes: 7 additions & 0 deletions .github/workflows/bitcoin-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,13 @@ jobs:
- test-name: tests::epoch_24::verify_auto_unlock_behavior
# Disable this flaky test. We don't need continue testing Epoch 2 -> 3 transition
- test-name: tests::nakamoto_integrations::flash_blocks_on_epoch_3_FLAKY
# These mempool tests take a long time to run, and are meant to be run manually
- test-name: tests::nakamoto_integrations::large_mempool_original_constant_fee
- test-name: tests::nakamoto_integrations::large_mempool_original_random_fee
- test-name: tests::nakamoto_integrations::large_mempool_next_constant_fee
- test-name: tests::nakamoto_integrations::large_mempool_next_random_fee
- test-name: tests::nakamoto_integrations::larger_mempool
- test-name: tests::signer::v0::larger_mempool

steps:
## Setup test environment
Expand Down
9 changes: 8 additions & 1 deletion stacks-common/src/types/sqlite.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

use rusqlite::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};

use super::chainstate::VRFSeed;
use super::chainstate::{StacksAddress, VRFSeed};
use crate::deps_common::bitcoin::util::hash::Sha256dHash;
use crate::types::chainstate::{
BlockHeaderHash, BurnchainHeaderHash, ConsensusHash, SortitionId, StacksBlockId, TrieHash,
Expand All @@ -42,6 +42,13 @@ impl ToSql for Sha256dHash {
}
}

impl rusqlite::types::ToSql for StacksAddress {
fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput> {
let addr_str = self.to_string();
Ok(addr_str.into())
}
}

// Implement rusqlite traits for a bunch of structs that used to be defined
// in the chainstate code
impl_byte_array_rusqlite_only!(ConsensusHash);
Expand Down
307 changes: 307 additions & 0 deletions stacks-common/src/util/lru_cache.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,307 @@
// Copyright (C) 2024 Stacks Open Internet Foundation
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

use std::fmt::Display;

use hashbrown::HashMap;

/// Node in the doubly linked list
struct Node<K, V> {
key: K,
value: V,
dirty: bool,
next: usize,
prev: usize,
}

impl<K: Display, V: Display> Display for Node<K, V> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}={} ({}) [prev={}, next={}]",
self.key,
self.value,
if self.dirty { "dirty" } else { "clean" },
self.prev,
self.next
)
}
}

/// LRU cache for account nonces
pub struct LruCache<K, V> {
capacity: usize,
/// Map from address to an offset in the linked list
cache: HashMap<K, usize>,
/// Doubly linked list of values in order of most recently used
order: Vec<Node<K, V>>,
/// Index of the head of the linked list -- the most recently used element
head: usize,
/// Index of the tail of the linked list -- the least recently used element
tail: usize,
}

impl<K: Display, V: Display> Display for LruCache<K, V> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(
f,
"LruCache (capacity={}, head={}, tail={})",
self.capacity, self.head, self.tail
)?;
let mut curr = self.head;
while curr != self.capacity {
writeln!(f, " {}", self.order[curr])?;
curr = self.order[curr].next;
}
Ok(())
}
}

impl<K: Eq + std::hash::Hash + Clone, V: Copy> LruCache<K, V> {
/// Create a new LRU cache with the given capacity
pub fn new(capacity: usize) -> Self {
LruCache {
capacity,
cache: HashMap::new(),
order: Vec::with_capacity(capacity),
head: capacity,
tail: capacity,
}
}

/// Get the value for the given key
pub fn get(&mut self, key: &K) -> Option<V> {
if let Some(node) = self.cache.get(key) {
// Move the node to the head of the LRU list
let node = *node;

if node != self.head {
let prev = self.order[node].prev;
let next = self.order[node].next;

if node == self.tail {
// If this is the tail, update the tail
self.tail = prev;
} else {
// Else, update the next node's prev pointer
self.order[next].prev = prev;
}

self.order[prev].next = next;
self.order[node].prev = self.capacity;
self.order[node].next = self.head;
self.order[self.head].prev = node;
self.head = node;
}

Some(self.order[node].value)
} else {
None
}
}

/// Insert a key-value pair into the cache, marking it as dirty.
/// Returns `Some((K, V))` if a dirty value was evicted.
pub fn insert(&mut self, key: K, value: V) -> Option<(K, V)> {
self.insert_with_dirty(key, value, true)
}

/// Insert a key-value pair into the cache, marking it as clean.
/// Returns `Some((K, V))` if a dirty value was evicted.
pub fn insert_clean(&mut self, key: K, value: V) -> Option<(K, V)> {
self.insert_with_dirty(key, value, false)
}

/// Insert a key-value pair into the cache
/// Returns `Some((K, V))` if a dirty value was evicted.
pub fn insert_with_dirty(&mut self, key: K, value: V, dirty: bool) -> Option<(K, V)> {
let mut evicted = None;
if let Some(node) = self.cache.get(&key) {
// Update the value for the key
let node = *node;
self.order[node].value = value;
self.order[node].dirty = dirty;

// Just call get to handle updating the LRU list
self.get(&key);
} else {
let index = if self.cache.len() == self.capacity {
// Take the place of the least recently used element.
// First, remove it from the tail of the LRU list
let index = self.tail;
let prev = self.order[index].prev;
self.order[prev].next = self.capacity;
self.tail = prev;

// Remove it from the cache
self.cache.remove(&self.order[index].key);

// Replace the key with the new key, saving the old key
let replaced_key = std::mem::replace(&mut self.order[index].key, key.clone());

// If it is dirty, save the key-value pair to return
if self.order[index].dirty {
evicted = Some((replaced_key, self.order[index].value));
}

// Insert this new value into the cache
self.cache.insert(key, index);

// Update the node with the new key-value pair, inserting it at
// the head of the LRU list
self.order[index].value = value;
self.order[index].dirty = dirty;
self.order[index].next = self.head;
self.order[index].prev = self.capacity;

index
} else {
// Insert a new key-value pair
let node = Node {
key: key.clone(),
value,
dirty,
next: self.head,
prev: self.capacity,
};

let index = self.order.len();
self.order.push(node);
self.cache.insert(key, index);

index
};

// Put it at the head of the LRU list
if self.head != self.capacity {
self.order[self.head].prev = index;
} else {
self.tail = index;
}

self.head = index;
}
evicted
}

pub fn flush<E>(&mut self, mut f: impl FnMut(&K, V) -> Result<(), E>) -> Result<(), E> {
let mut index = self.head;
while index != self.capacity {
let next = self.order[index].next;
if self.order[index].dirty {
let value = self.order[index].value;
f(&self.order[index].key, value)?;
self.order[index].dirty = false;
}
index = next;
}
Ok(())
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_lru_cache() {
let mut cache = LruCache::new(2);

cache.insert(1, 1);
cache.insert(2, 2);
assert_eq!(cache.get(&1), Some(1));
cache.insert(3, 3);
assert_eq!(cache.get(&2), None);
cache.insert(4, 4);
assert_eq!(cache.get(&1), None);
assert_eq!(cache.get(&3), Some(3));
assert_eq!(cache.get(&4), Some(4));
}

#[test]
fn test_lru_cache_update() {
let mut cache = LruCache::new(2);

cache.insert(1, 1);
cache.insert(2, 2);
cache.insert(1, 10);
assert_eq!(cache.get(&1), Some(10));
cache.insert(3, 3);
assert_eq!(cache.get(&2), None);
cache.insert(2, 4);
assert_eq!(cache.get(&2), Some(4));
assert_eq!(cache.get(&3), Some(3));
}

#[test]
fn test_lru_cache_evicted() {
let mut cache = LruCache::new(2);

assert!(cache.insert(1, 1).is_none());
assert!(cache.insert(2, 2).is_none());
let evicted = cache.insert(3, 3).expect("expected an eviction");
assert_eq!(evicted, (1, 1));
}

#[test]
fn test_lru_cache_flush() {
let mut cache = LruCache::new(2);

cache.insert(1, 1);

let mut flushed = Vec::new();
cache
.flush(|k, v| {
flushed.push((*k, v));
Ok::<(), ()>(())
})
.unwrap();

assert_eq!(flushed, vec![(1, 1)]);

cache.insert(1, 3);
cache.insert(2, 2);

let mut flushed = Vec::new();
cache
.flush(|k, v| {
flushed.push((*k, v));
Ok::<(), ()>(())
})
.unwrap();

assert_eq!(flushed, vec![(2, 2), (1, 3)]);
}

#[test]
fn test_lru_cache_evict_clean() {
let mut cache = LruCache::new(2);

assert!(cache.insert_with_dirty(0, 0, false).is_none());
assert!(cache.insert_with_dirty(1, 1, false).is_none());
assert!(cache.insert_with_dirty(2, 2, true).is_none());
assert!(cache.insert_with_dirty(3, 3, true).is_none());

let mut flushed = Vec::new();
cache
.flush(|k, v| {
flushed.push((*k, v));
Ok::<(), ()>(())
})
.unwrap();

assert_eq!(flushed, [(3, 3), (2, 2)]);
}
}
1 change: 1 addition & 0 deletions stacks-common/src/util/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ pub mod chunked_encoding;
#[cfg(feature = "rusqlite")]
pub mod db;
pub mod hash;
pub mod lru_cache;
pub mod pair;
pub mod pipe;
pub mod retry;
Expand Down
Loading
Loading