Skip to content
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
29dd130
start definition of the pool
AurelienFT Sep 12, 2024
4049dac
Merge branch 'txpool-v2' into txpool-v2-api
AurelienFT Sep 12, 2024
dd6f7c1
Add insertion skeleton
AurelienFT Sep 12, 2024
460beef
Add all the verifications in order. Still some todo and compil errors
AurelienFT Sep 13, 2024
75fb598
Fix compil errors
AurelienFT Sep 13, 2024
054f648
Merge branch 'txpool-v2' into txpool-v2-api
AurelienFT Sep 16, 2024
a7666fc
Finish verification on all inputs
AurelienFT Sep 16, 2024
4426134
fix verifications to make no copy
AurelienFT Sep 17, 2024
86a7c86
Reorganize insertion and opti some code. Start rework of tests.
AurelienFT Sep 17, 2024
96e0cc9
Update all the tests
AurelienFT Sep 17, 2024
cb635e8
Add heavy async work for insert
AurelienFT Sep 18, 2024
e379a0c
Merge branch 'txpool-v2' into txpool-v2-api
AurelienFT Sep 18, 2024
e7dc924
Clean up of the code
AurelienFT Sep 18, 2024
c12ee38
Update changelog
AurelienFT Sep 18, 2024
4f87fbd
fix clippy
AurelienFT Sep 18, 2024
3f11941
Merge branch 'txpool-v2' into txpool-v2-api
AurelienFT Sep 19, 2024
697cf4c
Merge branch 'txpool-v2' into txpool-v2-api
AurelienFT Sep 19, 2024
fdd5f8a
Merge branch 'txpool-v2' into txpool-v2-api
AurelienFT Sep 20, 2024
72add0f
Merge branch 'txpool-v2' into txpool-v2-api
AurelienFT Sep 23, 2024
cf9e9eb
Change insertion checks to be simplified
AurelienFT Sep 23, 2024
1c59fc7
Simplified insert
AurelienFT Sep 23, 2024
e859b60
Fix collision system to allow multiple collision to the same tx
AurelienFT Sep 23, 2024
1f8aaf8
Fix some node that wasn't removed.
AurelienFT Sep 23, 2024
c7b3a29
format
AurelienFT Sep 23, 2024
0ebfc5c
fix max size algo and add tests
AurelienFT Sep 23, 2024
82c9dd9
Update crates/services/txpool_v2/src/config.rs
AurelienFT Sep 25, 2024
70a45e9
Update crates/services/txpool_v2/src/config.rs
AurelienFT Sep 25, 2024
677a130
Change collision manager and add more info in error
AurelienFT Sep 25, 2024
26871c8
Update pool check size
AurelienFT Sep 25, 2024
76b2af4
Move logic from pool to storage
AurelienFT Sep 25, 2024
13e6990
fmt and clippy
AurelienFT Sep 25, 2024
6249e5d
remove unused stream predicate verif
AurelienFT Sep 26, 2024
d6dea78
Merge branch 'txpool-v2' into txpool-v2-api
AurelienFT Sep 26, 2024
83c56ae
Change way to get less worth it transactions
AurelienFT Sep 26, 2024
de44984
Fix clippy
AurelienFT Sep 26, 2024
2edff72
Merge branch 'txpool-v2' into txpool-v2-api
AurelienFT Sep 26, 2024
029196c
Merge branch 'txpool-v2' into txpool-v2-api
AurelienFT Sep 27, 2024
4bb55ba
simplify an unused function
AurelienFT Sep 27, 2024
0570070
Merge branch 'txpool-v2' into txpool-v2-api
AurelienFT Sep 27, 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: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- [2182](https://github.com/FuelLabs/fuel-core/pull/2151): Limit number of transactions that can be fetched via TxSource::next
- [2189](https://github.com/FuelLabs/fuel-core/pull/2151): Select next DA height to never include more than u16::MAX -1 transactions from L1.
- [2162](https://github.com/FuelLabs/fuel-core/pull/2162): Pool structure with dependencies, etc.. for the next transaction pool module.
- [2193](https://github.com/FuelLabs/fuel-core/pull/2193): Insertion in PoolV2 and tests refactoring


### Changed

Expand Down
4 changes: 4 additions & 0 deletions Cargo.lock

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

11 changes: 11 additions & 0 deletions crates/services/txpool_v2/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,19 @@ derive_more = { workspace = true }
fuel-core-services = { workspace = true }
fuel-core-storage = { workspace = true, features = ["std"] }
fuel-core-types = { workspace = true, features = ["test-helpers"] }
futures = { workspace = true }
mockall = { workspace = true, optional = true }
num-rational = { workspace = true }
parking_lot = { workspace = true }
petgraph = "0.6.5"
rayon = { workspace = true }
tokio = { workspace = true, default-features = false, features = ["sync"] }
tokio-rayon = { workspace = true }
tracing = { workspace = true }

[features]
test-helpers = [
"dep:mockall",
"fuel-core-types/test-helpers",
"fuel-core-storage/test-helpers",
]
113 changes: 64 additions & 49 deletions crates/services/txpool_v2/src/collision_manager/basic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,10 @@ use crate::{
storage::StorageData,
};

use super::{
CollisionManager,
CollisionReason,
Collisions,
};
use super::CollisionManager;

pub trait BasicCollisionManagerStorage {
type StorageIndex: Copy + Debug;
type StorageIndex: Copy + Debug + PartialEq + Eq;

fn get(&self, index: &Self::StorageIndex) -> Result<&StorageData, Error>;
}
Expand Down Expand Up @@ -75,16 +71,23 @@ impl<S: BasicCollisionManagerStorage> Default for BasicCollisionManager<S> {
}

impl<S: BasicCollisionManagerStorage> BasicCollisionManager<S> {
fn gather_colliding_txs(
fn gather_colliding_tx(
&self,
tx: &PoolTransaction,
) -> Result<Collisions<S::StorageIndex>, Error> {
let mut collisions = Collisions::new();
) -> Result<Option<S::StorageIndex>, Error> {
let mut collision: Option<S::StorageIndex> = None;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to document the rule that we agreed on in the code since it is not clear why we allow only one collision=)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, I want to highlight that we agreed that this is applicable to dependent transactions. In the case of executable transactions, we can collide with more than one transaction if it is more profitable than all collided subtrees=)

But we can handle it in a separate PR if you want=) But I think it is better to return all behavior of the BasicCollisionManager and just check the number of collided transactions in another place

Copy link
Contributor Author

@AurelienFT AurelienFT Sep 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok I will add the documentation and will change the behavior however I don't like to check the number of collisions depending on being executable or not outside of the CollisionManager maybe I can add a new separate method to the collision manager. For me it's not only a detector it should be able to resolve them and be sure that the one left are less worth

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done I documented in collision manager and check pool full. I have split the detection and resolution of collisions in two different function and reworked the whole logic. Could be a bit optimised on the collisions resolution iteration but I think it will not be our pain point for now

if let PoolTransaction::Blob(checked_tx, _) = tx {
let blob_id = checked_tx.transaction().blob_id();
if let Some(state) = self.blobs_users.get(blob_id) {
collisions.reasons.insert(CollisionReason::Blob(*blob_id));
collisions.colliding_txs.push(*state);
if let Some(collision) = collision {
if state != &collision {
return Err(Error::Collided(format!(
"Transaction collides with other transactions: {:?}",
collision
)));
}
}
collision = Some(*state);
}
}
for input in tx.inputs() {
Expand All @@ -93,8 +96,15 @@ impl<S: BasicCollisionManagerStorage> BasicCollisionManager<S> {
| Input::CoinPredicate(CoinPredicate { utxo_id, .. }) => {
// Check if the utxo is already spent by another transaction in the pool
if let Some(tx_id) = self.coins_spenders.get(utxo_id) {
collisions.reasons.insert(CollisionReason::Coin(*utxo_id));
collisions.colliding_txs.push(*tx_id);
if let Some(collision) = collision {
if tx_id != &collision {
return Err(Error::Collided(format!(
"Transaction collides with other transactions: {:?}",
collision
)));
}
}
collision = Some(*tx_id);
}
}
Input::MessageCoinSigned(MessageCoinSigned { nonce, .. })
Expand All @@ -103,8 +113,15 @@ impl<S: BasicCollisionManagerStorage> BasicCollisionManager<S> {
| Input::MessageDataPredicate(MessageDataPredicate { nonce, .. }) => {
// Check if the message is already spent by another transaction in the pool
if let Some(tx_id) = self.messages_spenders.get(nonce) {
collisions.reasons.insert(CollisionReason::Message(*nonce));
collisions.colliding_txs.push(*tx_id);
if let Some(collision) = collision {
if tx_id != &collision {
return Err(Error::Collided(format!(
"Transaction collides with other transactions: {:?}",
collision
)));
}
}
collision = Some(*tx_id);
}
}
// No collision for contract inputs
Expand All @@ -116,62 +133,60 @@ impl<S: BasicCollisionManagerStorage> BasicCollisionManager<S> {
if let Output::ContractCreated { contract_id, .. } = output {
// Check if the contract is already created by another transaction in the pool
if let Some(tx_id) = self.contracts_creators.get(contract_id) {
collisions
.reasons
.insert(CollisionReason::ContractCreation(*contract_id));
collisions.colliding_txs.push(*tx_id);
if let Some(collision) = collision {
if tx_id != &collision {
return Err(Error::Collided(format!(
"Transaction collides with other transactions: {:?}",
collision
)));
}
}
collision = Some(*tx_id);
}
}
}
Ok(collisions)
Ok(collision)
}

fn is_better_than_collisions(
fn is_better_than_collision(
&self,
tx: &PoolTransaction,
collisions: &Collisions<S::StorageIndex>,
collision: S::StorageIndex,
storage: &S,
) -> bool {
let new_tx_ratio = Ratio::new(tx.tip(), tx.max_gas());
let (total_tip, total_gas) = collisions.colliding_txs.iter().fold(
(0u64, 0u64),
|(total_tip, total_gas), node_id| {
let dependent_tx = storage
.get(node_id)
.expect("Transaction always should exist in storage");
let total_tip =
total_tip.saturating_add(dependent_tx.dependents_cumulative_tip);
let total_gas =
total_gas.saturating_add(dependent_tx.dependents_cumulative_gas);
(total_tip, total_gas)
},
let colliding_tx = storage
.get(&collision)
.expect("Transaction always should exist in storage");
let colliding_tx_ratio = Ratio::new(
colliding_tx.dependents_cumulative_tip,
colliding_tx.dependents_cumulative_gas,
);

let collision_tx_ratio = Ratio::new(total_tip, total_gas);

new_tx_ratio > collision_tx_ratio
new_tx_ratio > colliding_tx_ratio
}
}

impl<S: BasicCollisionManagerStorage> CollisionManager for BasicCollisionManager<S> {
type Storage = S;
type StorageIndex = S::StorageIndex;

fn collect_colliding_transactions(
fn collect_colliding_transaction(
&self,
transaction: &PoolTransaction,
storage: &S,
) -> Result<Collisions<S::StorageIndex>, Error> {
let collisions = self.gather_colliding_txs(transaction)?;
if collisions.colliding_txs.is_empty() {
Ok(Collisions::new())
} else if self.is_better_than_collisions(transaction, &collisions, storage) {
Ok(collisions)
) -> Result<Option<S::StorageIndex>, Error> {
let collision = self.gather_colliding_tx(transaction)?;
if let Some(collision) = collision {
if self.is_better_than_collision(transaction, collision, storage) {
Ok(Some(collision))
} else {
Err(Error::Collided(format!(
"Transaction collides with other transactions: {:?}",
collision
)))
}
} else {
Err(Error::Collided(format!(
"Transaction collides with other transactions: {:?}",
collisions.colliding_txs
)))
Ok(None)
}
}

Expand Down
46 changes: 5 additions & 41 deletions crates/services/txpool_v2/src/collision_manager/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,57 +17,21 @@ use crate::error::Error;

pub mod basic;

/// The reason why a transaction collides with another.
/// It also contains additional information about the collision.
#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
pub enum CollisionReason {
Coin(UtxoId),
Blob(BlobId),
Message(Nonce),
ContractCreation(ContractId),
}

/// Contains all the information about the collisions of a transaction.
#[derive(Default, Debug)]
pub struct Collisions<Idx> {
reasons: HashSet<CollisionReason>,
colliding_txs: Vec<Idx>,
}

impl<Idx> Collisions<Idx> {
/// Create a new empty collision information.
pub fn new() -> Self {
Self {
reasons: HashSet::default(),
colliding_txs: vec![],
}
}

/// Get the reasons of the collisions.
pub fn reasons(&self) -> &HashSet<CollisionReason> {
&self.reasons
}

/// Get the transactions that collide with the transaction.
pub fn colliding_txs(&self) -> &[Idx] {
&self.colliding_txs
}
}

pub trait CollisionManager {
/// Storage type of the collision manager.
type Storage;
/// Index that identifies a transaction in the storage.
type StorageIndex;

/// Collect all the transactions that collide with the given transaction.
/// Collect the transaction that collide with the given transaction.
/// It returns an error if the transaction is less worthy than the colliding transactions.
/// It returns the information about the collisions.
fn collect_colliding_transactions(
/// It returns an error if the transaction collide with two transactions.
/// It returns the information about the collision if exists, none otherwise.
fn collect_colliding_transaction(
&self,
transaction: &PoolTransaction,
storage: &Self::Storage,
) -> Result<Collisions<Self::StorageIndex>, Error>;
) -> Result<Option<Self::StorageIndex>, Error>;

/// Inform the collision manager that a transaction was stored.
fn on_stored_transaction(
Expand Down
35 changes: 32 additions & 3 deletions crates/services/txpool_v2/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ impl BlackList {
}
}

#[derive(Clone)]
pub struct Config {
/// Enable UTXO validation (will check if UTXO exists in the database and has correct data).
pub utxo_validation: bool,
Expand All @@ -124,14 +125,34 @@ pub struct Config {
pub max_block_gas: u64,
/// Maximum transactions per dependencies chain.
pub max_txs_chain_count: usize,
/// Maximum transactions in the pool.
pub max_txs: u64,
/// Pool limits
pub pool_limits: PoolLimits,
/// Maximum transaction time to live.
pub max_txs_ttl: Duration,
/// Heavy async processing configuration.
pub heavy_work: HeavyWorkConfig,
/// Blacklist. Transactions with blacklisted inputs will not be accepted.
pub black_list: BlackList,
}

#[derive(Clone)]
pub struct PoolLimits {
/// Maximum number of transactions in the pool.
pub max_txs: usize,
/// Maximum number of gas in the pool.
pub max_gas: u64,
/// Maximum number of bytes in the pool.
pub max_bytes_size: usize,
}

#[derive(Clone)]
pub struct HeavyWorkConfig {
/// Maximum of threads for managing verifications/insertions.
pub number_threads_verif_insert_transactions: usize,
/// Maximum of tasks in the heavy async processing queue.
pub number_pending_tasks_threads_verif_insert_transactions: usize,
}

#[cfg(test)]
impl Default for Config {
fn default() -> Self {
Expand All @@ -140,9 +161,17 @@ impl Default for Config {
max_block_gas: 100000000,
max_block_size: 1000000000,
max_txs_chain_count: 1000,
max_txs: 10000,
pool_limits: PoolLimits {
max_txs: 10000,
max_gas: 100_000_000_000,
max_bytes_size: 10_000_000_000,
},
max_txs_ttl: Duration::from_secs(60 * 10),
black_list: BlackList::default(),
heavy_work: HeavyWorkConfig {
number_threads_verif_insert_transactions: 4,
number_pending_tasks_threads_verif_insert_transactions: 100,
},
}
}
}
8 changes: 8 additions & 0 deletions crates/services/txpool_v2/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ use fuel_core_types::{
fuel_vm::checked_transaction::CheckError,
};

use crate::ports::WasmValidityError;

#[derive(Debug, derive_more::Display)]
pub enum Error {
#[display(fmt = "Gas price not found for block height {_0}")]
Expand All @@ -30,6 +32,8 @@ pub enum Error {
// TODO: Make more specific errors: https://github.com/FuelLabs/fuel-core/issues/2185
#[display(fmt = "Transaction collided: {_0}")]
Collided(String),
#[display(fmt = "Transaction is not inserted. Collision is also a dependency")]
NotInsertedCollisionIsDependency,
#[display(fmt = "Utxo not found: {_0}")]
UtxoNotFound(UtxoId),
#[display(fmt = "The UTXO `{_0}` is blacklisted")]
Expand Down Expand Up @@ -82,6 +86,10 @@ pub enum Error {
NotInsertedLimitHit,
#[display(fmt = "Storage error: {_0}")]
Storage(String),
#[display(fmt = "Error with Wasm validity: {:?}", _0)]
WasmValidity(WasmValidityError),
#[display(fmt = "Transaction is not inserted. Mint transaction is not allowed")]
MintIsDisallowed,
}

impl From<CheckError> for Error {
Expand Down
Loading