mirror of
https://github.com/hl-archive-node/nanoreth.git
synced 2025-12-06 10:59:55 +00:00
feat(txpool): initial sketch (#7)
This commit is contained in:
33
Cargo.lock
generated
33
Cargo.lock
generated
@ -503,6 +503,7 @@ checksum = "7f21eda599937fba36daeb58a22e8f5cee2d14c4a17b5b7739c7c8e5e3b8230c"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
|
"futures-executor",
|
||||||
"futures-io",
|
"futures-io",
|
||||||
"futures-sink",
|
"futures-sink",
|
||||||
"futures-task",
|
"futures-task",
|
||||||
@ -525,6 +526,17 @@ version = "0.3.24"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4e5aa3de05362c3fb88de6531e6296e85cde7739cccad4b9dfeeb7f6ebce56bf"
|
checksum = "4e5aa3de05362c3fb88de6531e6296e85cde7739cccad4b9dfeeb7f6ebce56bf"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-executor"
|
||||||
|
version = "0.3.24"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9ff63c23854bee61b6e9cd331d523909f238fc7636290b96826e9cfa5faa00ab"
|
||||||
|
dependencies = [
|
||||||
|
"futures-core",
|
||||||
|
"futures-task",
|
||||||
|
"futures-util",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-io"
|
name = "futures-io"
|
||||||
version = "0.3.24"
|
version = "0.3.24"
|
||||||
@ -975,6 +987,12 @@ dependencies = [
|
|||||||
"thiserror",
|
"thiserror",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "linked-hash-map"
|
||||||
|
version = "0.5.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lock_api"
|
name = "lock_api"
|
||||||
version = "0.4.9"
|
version = "0.4.9"
|
||||||
@ -1494,6 +1512,21 @@ dependencies = [
|
|||||||
"tracing-futures",
|
"tracing-futures",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "reth-transaction-pool"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"async-trait",
|
||||||
|
"fnv",
|
||||||
|
"futures",
|
||||||
|
"linked-hash-map",
|
||||||
|
"parking_lot",
|
||||||
|
"reth-primitives",
|
||||||
|
"serde",
|
||||||
|
"thiserror",
|
||||||
|
"tracing",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "revm"
|
name = "revm"
|
||||||
version = "2.1.0"
|
version = "2.1.0"
|
||||||
|
|||||||
@ -15,6 +15,8 @@ members = [
|
|||||||
"crates/net/rpc-types",
|
"crates/net/rpc-types",
|
||||||
"crates/primitives",
|
"crates/primitives",
|
||||||
"crates/stages",
|
"crates/stages",
|
||||||
|
"crates/transaction-pool",
|
||||||
|
"crates/db"
|
||||||
]
|
]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|||||||
27
crates/transaction-pool/Cargo.toml
Normal file
27
crates/transaction-pool/Cargo.toml
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
[package]
|
||||||
|
name = "reth-transaction-pool"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
license = "MIT OR Apache-2.0"
|
||||||
|
repository = "https://github.com/foundry-rs/reth"
|
||||||
|
readme = "README.md"
|
||||||
|
description = """
|
||||||
|
Transaction pool implementation
|
||||||
|
"""
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
|
||||||
|
# eth
|
||||||
|
reth-primitives = { path = "../primitives" }
|
||||||
|
|
||||||
|
# async/futures
|
||||||
|
async-trait = "0.1"
|
||||||
|
futures = "0.3"
|
||||||
|
parking_lot = "0.12"
|
||||||
|
|
||||||
|
# misc
|
||||||
|
thiserror = "1.0"
|
||||||
|
tracing = "0.1"
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
linked-hash-map = "0.5"
|
||||||
|
fnv = "1.0.7"
|
||||||
24
crates/transaction-pool/src/client.rs
Normal file
24
crates/transaction-pool/src/client.rs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
//! Provides access to the chain's storage
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
error::{PoolError, PoolResult},
|
||||||
|
validate::TransactionValidator,
|
||||||
|
};
|
||||||
|
use reth_primitives::{BlockID, U64};
|
||||||
|
|
||||||
|
/// The interface used to interact with the blockchain and access storage.
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
pub trait PoolClient: Send + Sync + TransactionValidator {
|
||||||
|
/// Error type that can be converted to the crate's internal Error.
|
||||||
|
type Error: Into<PoolError>;
|
||||||
|
|
||||||
|
/// Returns the block number for the given block identifier.
|
||||||
|
fn convert_block_id(&self, block_id: &BlockID) -> PoolResult<Option<U64>>;
|
||||||
|
|
||||||
|
/// Same as [`PoolClient::convert_block_id()`] but returns an error if no matching block number
|
||||||
|
/// was found
|
||||||
|
fn ensure_block_number(&self, block_id: &BlockID) -> PoolResult<U64> {
|
||||||
|
self.convert_block_id(block_id)
|
||||||
|
.and_then(|number| number.ok_or(PoolError::BlockNumberNotFound(*block_id)))
|
||||||
|
}
|
||||||
|
}
|
||||||
13
crates/transaction-pool/src/config.rs
Normal file
13
crates/transaction-pool/src/config.rs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
///! Configuration options for the Transaction pool.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct PoolConfig {
|
||||||
|
// TODO add limits for subpools
|
||||||
|
// TODO limits for per peer
|
||||||
|
// TODO config whether to check if transactions are banned
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for PoolConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
23
crates/transaction-pool/src/error.rs
Normal file
23
crates/transaction-pool/src/error.rs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
//! Transaction pool errors
|
||||||
|
|
||||||
|
use reth_primitives::BlockID;
|
||||||
|
|
||||||
|
/// Transaction pool result type.
|
||||||
|
pub type PoolResult<T> = Result<T, PoolError>;
|
||||||
|
|
||||||
|
/// All errors the Transaction pool can throw.
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
pub enum PoolError {
|
||||||
|
/// Thrown if a replacement transaction's gas price is below the already imported transaction
|
||||||
|
#[error("Tx: insufficient gas price to replace existing transaction")]
|
||||||
|
ReplacementUnderpriced,
|
||||||
|
/// Encountered a transaction that was already added into the poll
|
||||||
|
#[error("[{0:?}] Already added")]
|
||||||
|
AlreadyAdded(Box<dyn std::any::Any + Send + Sync>),
|
||||||
|
/// Encountered a cycle in the graph pool
|
||||||
|
#[error("Transaction with cyclic dependent transactions")]
|
||||||
|
CyclicTransaction,
|
||||||
|
/// Thrown if no number was found for the given block id
|
||||||
|
#[error("Invalid block id: {0:?}")]
|
||||||
|
BlockNumberNotFound(BlockID),
|
||||||
|
}
|
||||||
95
crates/transaction-pool/src/identifier.rs
Normal file
95
crates/transaction-pool/src/identifier.rs
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
use crate::U256;
|
||||||
|
use fnv::FnvHashMap;
|
||||||
|
use reth_primitives::Address;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
/// An internal mapping of addresses.
|
||||||
|
///
|
||||||
|
/// This assigns a _unique_ `SenderId` for a new `Address`.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct SenderIdentifiers {
|
||||||
|
/// The identifier to use next.
|
||||||
|
id: u64,
|
||||||
|
/// Assigned `SenderId` for an `Address`.
|
||||||
|
address_to_id: HashMap<Address, SenderId>,
|
||||||
|
/// Reverse mapping of `SenderId` to `Address`.
|
||||||
|
sender_to_address: FnvHashMap<SenderId, Address>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SenderIdentifiers {
|
||||||
|
/// Returns the address for the given identifier.
|
||||||
|
pub fn address(&self, id: &SenderId) -> Option<&Address> {
|
||||||
|
self.sender_to_address.get(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the `SenderId` that belongs to the given address, if it exists
|
||||||
|
pub fn sender_id(&self, addr: &Address) -> Option<SenderId> {
|
||||||
|
self.address_to_id.get(addr).copied()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the existing `SendId` or assigns a new one if it's missing
|
||||||
|
pub fn sender_id_or_create(&mut self, addr: Address) -> SenderId {
|
||||||
|
if let Some(id) = self.sender_id(&addr) {
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
let id = self.next_id();
|
||||||
|
self.address_to_id.insert(addr, id);
|
||||||
|
self.sender_to_address.insert(id, addr);
|
||||||
|
id
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a new address
|
||||||
|
fn next_id(&mut self) -> SenderId {
|
||||||
|
let id = self.id;
|
||||||
|
self.id = self.id.wrapping_add(1);
|
||||||
|
SenderId(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A _unique_ identifier for a sender of an address.
|
||||||
|
///
|
||||||
|
/// This is the identifier of an internal `address` mapping that is valid in the context of this
|
||||||
|
/// program.
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||||
|
pub struct SenderId(u64);
|
||||||
|
|
||||||
|
/// A unique identifier of a transaction of a Sender.
|
||||||
|
///
|
||||||
|
/// This serves as an identifier for dependencies of a transaction:
|
||||||
|
/// A transaction with a nonce higher than the current state nonce depends on `tx.nonce - 1`.
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||||
|
pub struct TransactionId {
|
||||||
|
/// Sender of this transaction
|
||||||
|
pub sender: SenderId,
|
||||||
|
/// Nonce of this transaction
|
||||||
|
pub nonce: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
// === impl TransactionId ===
|
||||||
|
|
||||||
|
impl TransactionId {
|
||||||
|
/// Create a new identifier pair
|
||||||
|
pub fn new(sender: SenderId, nonce: u64) -> Self {
|
||||||
|
Self { sender, nonce }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the id a transactions depends on
|
||||||
|
///
|
||||||
|
/// This returns `transaction_nonce - 1` if `transaction_nonce` is higher than the
|
||||||
|
/// `on_chain_none`
|
||||||
|
pub fn dependency(
|
||||||
|
transaction_nonce: u64,
|
||||||
|
on_chain_nonce: u64,
|
||||||
|
sender: SenderId,
|
||||||
|
) -> Option<TransactionId> {
|
||||||
|
if transaction_nonce == on_chain_nonce {
|
||||||
|
return None
|
||||||
|
}
|
||||||
|
let prev_nonce = transaction_nonce.saturating_sub(1);
|
||||||
|
if on_chain_nonce <= prev_nonce {
|
||||||
|
Some(Self::new(sender, prev_nonce))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
114
crates/transaction-pool/src/lib.rs
Normal file
114
crates/transaction-pool/src/lib.rs
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
#![warn(missing_docs)] // unreachable_pub, missing_debug_implementations
|
||||||
|
#![allow(unused)] // TODO(mattsse) remove after progress was made
|
||||||
|
#![deny(unused_must_use, rust_2018_idioms)]
|
||||||
|
#![doc(test(
|
||||||
|
no_crate_inject,
|
||||||
|
attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_variables))
|
||||||
|
))]
|
||||||
|
|
||||||
|
//! Reth's transaction pool implementation
|
||||||
|
|
||||||
|
use futures::channel::mpsc::Receiver;
|
||||||
|
use parking_lot::Mutex;
|
||||||
|
use reth_primitives::{BlockID, TxHash, U256, U64};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
mod client;
|
||||||
|
mod config;
|
||||||
|
pub mod error;
|
||||||
|
mod identifier;
|
||||||
|
mod ordering;
|
||||||
|
pub mod pool;
|
||||||
|
mod traits;
|
||||||
|
mod validate;
|
||||||
|
|
||||||
|
pub use crate::{
|
||||||
|
client::PoolClient,
|
||||||
|
config::PoolConfig,
|
||||||
|
ordering::TransactionOrdering,
|
||||||
|
pool::BasicPool,
|
||||||
|
traits::{BestTransactions, NewBlockEvent, PoolTransaction, TransactionPool},
|
||||||
|
validate::{TransactionValidationOutcome, TransactionValidator},
|
||||||
|
};
|
||||||
|
use crate::{error::PoolResult, validate::ValidPoolTransaction};
|
||||||
|
|
||||||
|
/// A generic, customizable `TransactionPool` implementation.
|
||||||
|
pub struct Pool<P: PoolClient, T: TransactionOrdering> {
|
||||||
|
/// The actual transaction pool where transactions and subscriptions are handled.
|
||||||
|
pool: BasicPool<P, T>,
|
||||||
|
/// Tracks status updates linked to chain events.
|
||||||
|
update_status: Arc<Mutex<UpdateStatus>>,
|
||||||
|
/// Chain/Storage access.
|
||||||
|
client: Arc<P>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// === impl Pool ===
|
||||||
|
|
||||||
|
impl<P, T> Pool<P, T>
|
||||||
|
where
|
||||||
|
P: PoolClient,
|
||||||
|
T: TransactionOrdering<Transaction = <P as TransactionValidator>::Transaction>,
|
||||||
|
{
|
||||||
|
/// Creates a new `Pool` with the given config and client and ordering.
|
||||||
|
pub fn new(client: Arc<P>, ordering: Arc<T>, config: PoolConfig) -> Self {
|
||||||
|
let pool = BasicPool::new(Arc::clone(&client), ordering, config);
|
||||||
|
Self { pool, update_status: Arc::new(Default::default()), client }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// implements the `TransactionPool` interface for various transaction pool API consumers.
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl<P, T> TransactionPool for Pool<P, T>
|
||||||
|
where
|
||||||
|
P: PoolClient,
|
||||||
|
T: TransactionOrdering<Transaction = <P as TransactionValidator>::Transaction>,
|
||||||
|
{
|
||||||
|
type Transaction = T::Transaction;
|
||||||
|
|
||||||
|
async fn on_new_block(&self, _event: NewBlockEvent) {
|
||||||
|
// TODO perform maintenance: update pool accordingly
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn add_transaction(
|
||||||
|
&self,
|
||||||
|
block_id: BlockID,
|
||||||
|
transaction: Self::Transaction,
|
||||||
|
) -> PoolResult<TxHash> {
|
||||||
|
self.pool.clone().add_transaction(&block_id, transaction).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn add_transactions(
|
||||||
|
&self,
|
||||||
|
block_id: BlockID,
|
||||||
|
transactions: Vec<Self::Transaction>,
|
||||||
|
) -> PoolResult<Vec<PoolResult<TxHash>>> {
|
||||||
|
self.pool.clone().add_transactions(&block_id, transactions).await
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ready_transactions_listener(&self) -> Receiver<TxHash> {
|
||||||
|
self.pool.ready_transactions_listener()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn best_transactions(
|
||||||
|
&self,
|
||||||
|
) -> Box<dyn BestTransactions<Item = Arc<ValidPoolTransaction<Self::Transaction>>>> {
|
||||||
|
Box::new(self.pool.inner().ready_transactions())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_invalid(
|
||||||
|
&self,
|
||||||
|
_tx_hashes: &[TxHash],
|
||||||
|
) -> Vec<Arc<ValidPoolTransaction<Self::Transaction>>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tracks the current update status of the pool.
|
||||||
|
#[derive(Debug, Clone, Default)]
|
||||||
|
struct UpdateStatus {
|
||||||
|
/// Block number when the pool was last updated.
|
||||||
|
updated_at: U64,
|
||||||
|
/// Current base fee that needs to be enforced
|
||||||
|
base_fee: U256,
|
||||||
|
}
|
||||||
20
crates/transaction-pool/src/ordering.rs
Normal file
20
crates/transaction-pool/src/ordering.rs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
use crate::traits::PoolTransaction;
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
/// Transaction ordering.
|
||||||
|
///
|
||||||
|
/// Decides how transactions should be ordered within the pool.
|
||||||
|
///
|
||||||
|
/// The returned priority must reflect natural `Ordering`.
|
||||||
|
// TODO: for custom, more advanced scoring it would be ideal to determine the priority in the
|
||||||
|
// context of the entire pool instead of standalone by alone looking at a single transaction
|
||||||
|
pub trait TransactionOrdering: Send + Sync + 'static {
|
||||||
|
/// Priority of a transaction.
|
||||||
|
type Priority: Ord + Clone + Default + fmt::Debug + Send + Sync;
|
||||||
|
|
||||||
|
/// The transaction type to score.
|
||||||
|
type Transaction: PoolTransaction + Send + Sync + 'static;
|
||||||
|
|
||||||
|
/// Returns the priority score for the given transaction.
|
||||||
|
fn priority(&self, transaction: &Self::Transaction) -> Self::Priority;
|
||||||
|
}
|
||||||
24
crates/transaction-pool/src/pool/events.rs
Normal file
24
crates/transaction-pool/src/pool/events.rs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
use reth_primitives::H256;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
/// Various events that describe status changes of a transaction.
|
||||||
|
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub enum TransactionEvent<Hash> {
|
||||||
|
/// Transaction has been added to the ready queue.
|
||||||
|
Ready,
|
||||||
|
/// Transaction has been added to the pending pool.
|
||||||
|
Pending,
|
||||||
|
/// Transaction has been added to the queued pool.
|
||||||
|
Queued,
|
||||||
|
/// Transaction has been included in the block belonging to this hash.
|
||||||
|
Included(H256),
|
||||||
|
/// Transaction has been replaced by the transaction belonging to the hash.
|
||||||
|
///
|
||||||
|
/// E.g. same (sender + nonce) pair
|
||||||
|
Replaced(Hash),
|
||||||
|
/// Transaction was dropped due to configured limits.
|
||||||
|
Dropped,
|
||||||
|
/// Transaction became invalid indefinitely.
|
||||||
|
Invalid,
|
||||||
|
// TODO Timedout?, broadcasted(peers)
|
||||||
|
}
|
||||||
89
crates/transaction-pool/src/pool/listener.rs
Normal file
89
crates/transaction-pool/src/pool/listener.rs
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
//! Listeners for the transaction-pool
|
||||||
|
|
||||||
|
use crate::pool::events::TransactionEvent;
|
||||||
|
use futures::channel::mpsc::UnboundedSender;
|
||||||
|
use std::{collections::HashMap, hash};
|
||||||
|
|
||||||
|
type EventSink<Hash> = UnboundedSender<TransactionEvent<Hash>>;
|
||||||
|
|
||||||
|
/// Transaction pool event listeners.
|
||||||
|
pub(crate) struct PoolEventListener<Hash: hash::Hash + Eq> {
|
||||||
|
/// All listeners for certain transaction events.
|
||||||
|
listeners: HashMap<Hash, PoolEventNotifier<Hash>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Hash: hash::Hash + Eq + Clone> PoolEventListener<Hash> {
|
||||||
|
/// Calls the notification callback with the `PoolEventListenerSender` that belongs to the hash.
|
||||||
|
fn notify_with<F>(&mut self, hash: &Hash, callback: F)
|
||||||
|
where
|
||||||
|
F: FnOnce(&mut PoolEventNotifier<Hash>),
|
||||||
|
{
|
||||||
|
let is_done = if let Some(sink) = self.listeners.get_mut(hash) {
|
||||||
|
callback(sink);
|
||||||
|
sink.is_done()
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
|
if is_done {
|
||||||
|
self.listeners.remove(hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Notify listeners about a transaction that was added to the ready queue.
|
||||||
|
pub(crate) fn ready(&mut self, tx: &Hash, replaced: Option<&Hash>) {
|
||||||
|
self.notify_with(tx, |notifier| notifier.ready());
|
||||||
|
|
||||||
|
if let Some(replaced) = replaced {
|
||||||
|
// notify listeners that this transaction was replaced
|
||||||
|
self.notify_with(replaced, |notifier| notifier.replaced(tx.clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Notify listeners about a transaction that was added to the queued pool.
|
||||||
|
pub(crate) fn queued(&mut self, tx: &Hash) {
|
||||||
|
self.notify_with(tx, |notifier| notifier.queued());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Hash: hash::Hash + Eq> Default for PoolEventListener<Hash> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self { listeners: Default::default() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sender half(s) of the event channels for a specific transaction
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct PoolEventNotifier<Hash> {
|
||||||
|
/// Tracks whether the transaction this notifier can stop because the transaction was
|
||||||
|
/// completed, or removed.
|
||||||
|
is_done: bool,
|
||||||
|
/// Corresponding sender half(s) for event listener channel
|
||||||
|
senders: Vec<EventSink<Hash>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Hash: Clone> PoolEventNotifier<Hash> {
|
||||||
|
fn notify(&mut self, event: TransactionEvent<Hash>) {
|
||||||
|
self.senders.retain(|sender| sender.unbounded_send(event.clone()).is_ok())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_done(&self) -> bool {
|
||||||
|
self.senders.is_empty() || self.is_done
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Transaction became ready.
|
||||||
|
fn ready(&mut self) {
|
||||||
|
self.notify(TransactionEvent::Pending)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Transaction was moved to the queued pool
|
||||||
|
fn queued(&mut self) {
|
||||||
|
self.notify(TransactionEvent::Queued)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Transaction was replaced with the given transaction
|
||||||
|
fn replaced(&mut self, hash: Hash) {
|
||||||
|
self.notify(TransactionEvent::Replaced(hash));
|
||||||
|
self.is_done = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
655
crates/transaction-pool/src/pool/mod.rs
Normal file
655
crates/transaction-pool/src/pool/mod.rs
Normal file
@ -0,0 +1,655 @@
|
|||||||
|
//! Transaction Pool internals.
|
||||||
|
//!
|
||||||
|
//! Incoming transactions are validated first. The validation outcome can have 3 states:
|
||||||
|
//!
|
||||||
|
//! 1. Transaction can _never_ be valid
|
||||||
|
//! 2. Transaction is _currently_ valid
|
||||||
|
//! 3. Transaction is _currently_ invalid, but could potentially become valid in the future
|
||||||
|
//!
|
||||||
|
//! However, (2.) and (3.) of a transaction can only be determined on the basis of the current
|
||||||
|
//! state, whereas (1.) holds indefinitely. This means once the state changes (2.) and (3.) need to
|
||||||
|
//! be reevaluated again.
|
||||||
|
//!
|
||||||
|
//! The transaction pool is responsible for storing new, valid transactions and providing the next
|
||||||
|
//! best transactions sorted by their priority. Where priority is determined by the transaction's
|
||||||
|
//! score.
|
||||||
|
//!
|
||||||
|
//! However, the score is also only valid for the current state.
|
||||||
|
//!
|
||||||
|
//! Furthermore, the following characteristics fall under (3.):
|
||||||
|
//!
|
||||||
|
//! a) Nonce of a transaction is higher than the expected nonce for the next transaction of its
|
||||||
|
//! sender. A distinction is made here whether multiple transactions from the same sender have
|
||||||
|
//! gapless nonce increments. a)(1) If _no_ transaction is missing in a chain of multiple
|
||||||
|
//! transactions from the same sender (all nonce in row), all of them can in principle be executed
|
||||||
|
//! on the current state one after the other. a)(2) If there's a nonce gap, then all
|
||||||
|
//! transactions after the missing transaction are blocked until the missing transaction arrives.
|
||||||
|
//! b) Transaction does not meet the dynamic fee cap requirement introduced by EIP-1559: The fee
|
||||||
|
//! cap of the transaction needs to be no less than the base fee of block.
|
||||||
|
//!
|
||||||
|
//!
|
||||||
|
//! In essence the transaction pool is made of two separate sub-pools:
|
||||||
|
//!
|
||||||
|
//! _Pending Pool_: Contains all transactions that are valid on the current state and satisfy
|
||||||
|
//! (3. a)(1): _No_ nonce gaps _Queued Pool_: Contains all transactions that are currently
|
||||||
|
//! blocked by missing transactions: (3. a)(2): _With_ nonce gaps
|
||||||
|
//!
|
||||||
|
//! To account for the dynamic base fee requirement (3. b) which could render an EIP-1559 and all
|
||||||
|
//! subsequent transactions of the sender currently invalid, the pending pool itself consists of two
|
||||||
|
//! queues:
|
||||||
|
//!
|
||||||
|
//! _Ready Queue_: Contains all transactions that can be executed on the current state
|
||||||
|
//! _Parked Queue_: Contains all transactions that either do not currently meet the dynamic
|
||||||
|
//! base fee requirement or are blocked by a previous transaction that violates it.
|
||||||
|
//!
|
||||||
|
//! The classification of transaction in which queue it belongs depends on the current base fee and
|
||||||
|
//! must be updated after changes:
|
||||||
|
//!
|
||||||
|
//! - Base Fee increases: recheck the _Ready Queue_ and evict transactions that don't satisfy
|
||||||
|
//! the new base fee, or depend on a transaction that no longer satisfies it, and move them
|
||||||
|
//! to the _Parked Queue_.
|
||||||
|
//! - Base Fee decreases: recheck the _Parked Queue_ and move all transactions that now satisfy
|
||||||
|
//! the new base fee to the _Ready Queue_.
|
||||||
|
//!
|
||||||
|
//!
|
||||||
|
//! Depending on the use case, consumers of the [`TransactionPool`](crate::traits::TransactionPool)
|
||||||
|
//! are interested in (2.) and/or (3.).
|
||||||
|
|
||||||
|
//! A generic [`TransactionPool`](crate::traits::TransactionPool) that only handles transactions.
|
||||||
|
//!
|
||||||
|
//! This Pool maintains two separate sub-pools for (2.) and (3.)
|
||||||
|
//!
|
||||||
|
//! ## Terminology
|
||||||
|
//!
|
||||||
|
//! - _Pending_: pending transactions are transactions that fall under (2.). Those transactions
|
||||||
|
//! are _currently_ ready to be executed and are stored in the `pending` sub-pool
|
||||||
|
//! - _Queued_: queued transactions are transactions that fall under category (3.). Those
|
||||||
|
//! transactions are _currently_ waiting for state changes that eventually move them into
|
||||||
|
//! category (2.) and become pending.
|
||||||
|
use crate::{
|
||||||
|
error::{PoolError, PoolResult},
|
||||||
|
pool::{
|
||||||
|
listener::PoolEventListener,
|
||||||
|
pending::PendingTransactions,
|
||||||
|
queued::{QueuedPoolTransaction, QueuedTransactions},
|
||||||
|
},
|
||||||
|
traits::PoolTransaction,
|
||||||
|
validate::ValidPoolTransaction,
|
||||||
|
BlockID, PoolClient, PoolConfig, TransactionOrdering, TransactionValidator, U256,
|
||||||
|
};
|
||||||
|
use fnv::FnvHashMap;
|
||||||
|
use futures::channel::mpsc::{channel, Receiver, Sender};
|
||||||
|
use parking_lot::{Mutex, RwLock};
|
||||||
|
use reth_primitives::{TxHash, U64};
|
||||||
|
use std::{
|
||||||
|
collections::{HashMap, VecDeque},
|
||||||
|
fmt,
|
||||||
|
sync::Arc,
|
||||||
|
};
|
||||||
|
use tracing::{debug, trace, warn};
|
||||||
|
|
||||||
|
mod events;
|
||||||
|
mod listener;
|
||||||
|
mod pending;
|
||||||
|
mod queued;
|
||||||
|
mod transaction;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
identifier::{SenderId, TransactionId},
|
||||||
|
validate::TransactionValidationOutcome,
|
||||||
|
};
|
||||||
|
pub use events::TransactionEvent;
|
||||||
|
pub use pending::TransactionsIterator;
|
||||||
|
|
||||||
|
/// Shareable Transaction pool.
|
||||||
|
pub struct BasicPool<P: PoolClient, T: TransactionOrdering> {
|
||||||
|
/// Arc'ed instance of the pool internals
|
||||||
|
pool: Arc<PoolInner<P, T>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// === impl Pool ===
|
||||||
|
|
||||||
|
impl<P: PoolClient, T: TransactionOrdering> BasicPool<P, T>
|
||||||
|
where
|
||||||
|
P: PoolClient,
|
||||||
|
T: TransactionOrdering<Transaction = <P as TransactionValidator>::Transaction>,
|
||||||
|
{
|
||||||
|
/// Create a new transaction pool instance.
|
||||||
|
pub fn new(client: Arc<P>, ordering: Arc<T>, config: PoolConfig) -> Self {
|
||||||
|
Self { pool: Arc::new(PoolInner::new(client, ordering, config)) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the wrapped pool
|
||||||
|
pub(crate) fn inner(&self) -> &PoolInner<P, T> {
|
||||||
|
&self.pool
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the actual block number for the block id
|
||||||
|
fn resolve_block_number(&self, block_id: &BlockID) -> PoolResult<U64> {
|
||||||
|
self.pool.client().ensure_block_number(block_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a single _unverified_ transaction into the pool.
|
||||||
|
pub async fn add_transaction(
|
||||||
|
&self,
|
||||||
|
block_id: &BlockID,
|
||||||
|
transaction: P::Transaction,
|
||||||
|
) -> PoolResult<TxHash> {
|
||||||
|
self.add_transactions(block_id, Some(transaction))
|
||||||
|
.await?
|
||||||
|
.pop()
|
||||||
|
.expect("transaction exists; qed")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds all given transactions into the pool
|
||||||
|
pub async fn add_transactions(
|
||||||
|
&self,
|
||||||
|
block_id: &BlockID,
|
||||||
|
transactions: impl IntoIterator<Item = P::Transaction>,
|
||||||
|
) -> PoolResult<Vec<PoolResult<TxHash>>> {
|
||||||
|
let validated = self.validate_all(block_id, transactions).await?;
|
||||||
|
let transactions = self.pool.add_transactions(validated.into_values());
|
||||||
|
Ok(transactions)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns future that validates all transaction in the given iterator at the block the
|
||||||
|
/// `block_id` points to.
|
||||||
|
async fn validate_all(
|
||||||
|
&self,
|
||||||
|
block_id: &BlockID,
|
||||||
|
transactions: impl IntoIterator<Item = P::Transaction>,
|
||||||
|
) -> PoolResult<HashMap<TxHash, TransactionValidationOutcome<P::Transaction>>> {
|
||||||
|
// get the actual block number which is required to validate the transactions
|
||||||
|
let block_number = self.resolve_block_number(block_id)?;
|
||||||
|
|
||||||
|
let outcome = futures::future::join_all(
|
||||||
|
transactions.into_iter().map(|tx| self.validate(block_id, block_number, tx)),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.into_iter()
|
||||||
|
.collect::<HashMap<_, _>>();
|
||||||
|
|
||||||
|
Ok(outcome)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Validates the given transaction at the given block
|
||||||
|
async fn validate(
|
||||||
|
&self,
|
||||||
|
block_id: &BlockID,
|
||||||
|
_block_number: U64,
|
||||||
|
transaction: P::Transaction,
|
||||||
|
) -> (TxHash, TransactionValidationOutcome<P::Transaction>) {
|
||||||
|
let _hash = *transaction.hash();
|
||||||
|
// TODO this is where additional validate checks would go, like banned senders etc...
|
||||||
|
let _res = self.pool.client().validate_transaction(block_id, transaction).await;
|
||||||
|
|
||||||
|
// TODO blockstamp the transaction
|
||||||
|
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Registers a new transaction listener and returns the receiver stream.
|
||||||
|
pub fn ready_transactions_listener(&self) -> Receiver<TxHash> {
|
||||||
|
self.pool.add_ready_listener()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<P: PoolClient, O: TransactionOrdering> Clone for BasicPool<P, O> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self { pool: Arc::clone(&self.pool) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Transaction pool internals.
|
||||||
|
pub struct PoolInner<P: PoolClient, T: TransactionOrdering> {
|
||||||
|
/// Chain/Storage access.
|
||||||
|
client: Arc<P>,
|
||||||
|
/// The internal pool that manages
|
||||||
|
pool: RwLock<GraphPool<T>>,
|
||||||
|
/// Pool settings.
|
||||||
|
config: PoolConfig,
|
||||||
|
/// Manages listeners for transaction state change events.
|
||||||
|
event_listener: RwLock<PoolEventListener<TxHash>>,
|
||||||
|
/// Listeners for new ready transactions.
|
||||||
|
ready_transaction_listener: Mutex<Vec<Sender<TxHash>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// === impl PoolInner ===
|
||||||
|
|
||||||
|
impl<P: PoolClient, T: TransactionOrdering> PoolInner<P, T>
|
||||||
|
where
|
||||||
|
P: PoolClient,
|
||||||
|
T: TransactionOrdering<Transaction = <P as TransactionValidator>::Transaction>,
|
||||||
|
{
|
||||||
|
/// Create a new transaction pool instance.
|
||||||
|
pub fn new(client: Arc<P>, ordering: Arc<T>, config: PoolConfig) -> Self {
|
||||||
|
Self {
|
||||||
|
client,
|
||||||
|
config,
|
||||||
|
event_listener: Default::default(),
|
||||||
|
pool: RwLock::new(GraphPool::new(ordering)),
|
||||||
|
ready_transaction_listener: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Updates the pool
|
||||||
|
pub(crate) fn update_base_fee(&self, base_fee: U256) {
|
||||||
|
self.pool.write().update_base_fee(base_fee);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get client reference.
|
||||||
|
pub fn client(&self) -> &P {
|
||||||
|
&self.client
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds a new transaction listener to the pool that gets notified about every new ready
|
||||||
|
/// transaction
|
||||||
|
pub fn add_ready_listener(&self) -> Receiver<TxHash> {
|
||||||
|
const TX_LISTENER_BUFFER_SIZE: usize = 2048;
|
||||||
|
let (tx, rx) = channel(TX_LISTENER_BUFFER_SIZE);
|
||||||
|
self.ready_transaction_listener.lock().push(tx);
|
||||||
|
rx
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resubmits transactions back into the pool.
|
||||||
|
pub fn resubmit(&self, _transactions: HashMap<TxHash, ValidPoolTransaction<T::Transaction>>) {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a single validated transaction into the pool.
|
||||||
|
fn add_transaction(
|
||||||
|
&self,
|
||||||
|
tx: TransactionValidationOutcome<T::Transaction>,
|
||||||
|
) -> PoolResult<TxHash> {
|
||||||
|
match tx {
|
||||||
|
TransactionValidationOutcome::Valid { balance, state_nonce, transaction } => {
|
||||||
|
// TODO create `ValidPoolTransaction`
|
||||||
|
|
||||||
|
// let added = self.pool.write().add_transaction(tx)?;
|
||||||
|
//
|
||||||
|
// if let Some(ready) = added.as_ready() {
|
||||||
|
// self.on_new_ready_transaction(ready);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// self.notify_event_listeners(&added);
|
||||||
|
//
|
||||||
|
// Ok(*added.hash())
|
||||||
|
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
TransactionValidationOutcome::Invalid(_tx, err) => {
|
||||||
|
// TODO notify listeners about invalid
|
||||||
|
Err(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds all transactions in the iterator to the pool, returning a list of results.
|
||||||
|
pub fn add_transactions(
|
||||||
|
&self,
|
||||||
|
transactions: impl IntoIterator<Item = TransactionValidationOutcome<T::Transaction>>,
|
||||||
|
) -> Vec<PoolResult<TxHash>> {
|
||||||
|
// TODO check pool limits
|
||||||
|
|
||||||
|
transactions.into_iter().map(|tx| self.add_transaction(tx)).collect::<Vec<_>>()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Notify all listeners about the new transaction.
|
||||||
|
fn on_new_ready_transaction(&self, ready: &TxHash) {
|
||||||
|
let mut transaction_listeners = self.ready_transaction_listener.lock();
|
||||||
|
transaction_listeners.retain_mut(|listener| match listener.try_send(*ready) {
|
||||||
|
Ok(()) => true,
|
||||||
|
Err(e) => {
|
||||||
|
if e.is_full() {
|
||||||
|
warn!(
|
||||||
|
target: "txpool",
|
||||||
|
"[{:?}] dropping full ready transaction listener",
|
||||||
|
ready,
|
||||||
|
);
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fire events for the newly added transaction.
|
||||||
|
fn notify_event_listeners(&self, tx: &AddedTransaction<T::Transaction>) {
|
||||||
|
let mut listener = self.event_listener.write();
|
||||||
|
|
||||||
|
match tx {
|
||||||
|
AddedTransaction::Pending(tx) => {
|
||||||
|
listener.ready(&tx.hash, None);
|
||||||
|
// TODO more listeners for discarded, removed etc...
|
||||||
|
}
|
||||||
|
AddedTransaction::Queued { hash } => {
|
||||||
|
listener.queued(hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns an iterator that yields transactions that are ready to be included in the block.
|
||||||
|
pub(crate) fn ready_transactions(&self) -> TransactionsIterator<T> {
|
||||||
|
self.pool.read().ready_transactions()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A pool that only manages transactions.
|
||||||
|
///
|
||||||
|
/// This pool maintains a dependency graph of transactions and provides the currently ready
|
||||||
|
/// transactions.
|
||||||
|
pub struct GraphPool<T: TransactionOrdering> {
|
||||||
|
/// How to order transactions.
|
||||||
|
ordering: Arc<T>,
|
||||||
|
/// Contains the currently known info
|
||||||
|
sender_info: FnvHashMap<SenderId, SenderInfo>,
|
||||||
|
/// Sub-Pool of transactions that are ready and waiting to be executed
|
||||||
|
pending: PendingTransactions<T>,
|
||||||
|
/// Sub-Pool of transactions that are waiting for state changes that eventually turn them
|
||||||
|
/// valid, so they can be moved in the `pending` pool.
|
||||||
|
queued: QueuedTransactions<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// === impl PoolInner ===
|
||||||
|
|
||||||
|
impl<T: TransactionOrdering> GraphPool<T> {
|
||||||
|
/// Create a new graph pool instance.
|
||||||
|
pub fn new(ordering: Arc<T>) -> Self {
|
||||||
|
let pending = PendingTransactions::new(Arc::clone(&ordering));
|
||||||
|
Self { ordering, sender_info: Default::default(), pending, queued: Default::default() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Updates the pool based on the changed base fee.
|
||||||
|
///
|
||||||
|
/// This enforces the dynamic fee requirement.
|
||||||
|
/// If the `new_base_fee` is _higher_ than previous base fee, all EIP-1559 transactions in the
|
||||||
|
/// ready queue that now violate the dynamic fee requirement need to parked.
|
||||||
|
/// If the `new_base_fee` is _lower_ than the previous base fee, all parked transactions that
|
||||||
|
/// now satisfy the dynamic fee requirement need to moved to the ready queue.
|
||||||
|
pub(crate) fn update_base_fee(&mut self, new_base_fee: U256) {
|
||||||
|
let _old_base_fee = self.pending.set_next_base_fee(new_base_fee);
|
||||||
|
// TODO update according to the changed base_fee
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns if the transaction for the given hash is already included in this pool
|
||||||
|
pub(crate) fn contains(&self, tx_hash: &TxHash) -> bool {
|
||||||
|
self.queued.contains(tx_hash) || self.pending.contains(tx_hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns an iterator that yields transactions that are ready to be included in the block.
|
||||||
|
pub(crate) fn ready_transactions(&self) -> TransactionsIterator<T> {
|
||||||
|
self.pending.get_transactions()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds the transaction into the pool
|
||||||
|
///
|
||||||
|
/// This pool consists of two sub-pools: `Queued` and `Pending`.
|
||||||
|
///
|
||||||
|
/// The `Queued` pool contains transaction with gaps in its dependency tree: It requires
|
||||||
|
/// additional transaction that are note yet present in the pool.
|
||||||
|
///
|
||||||
|
/// The `Pending` pool contains all transactions that have all their dependencies satisfied (no
|
||||||
|
/// nonce gaps). It consists of two parts: `Parked` and `Ready`.
|
||||||
|
///
|
||||||
|
/// The `Ready` queue contains transactions that are ready to be included in the pending block.
|
||||||
|
/// With EIP-1559, transactions can become executable or not without any changes to the
|
||||||
|
/// sender's balance or nonce and instead their feeCap determines whether the transaction is
|
||||||
|
/// _currently_ (on the current state) ready or needs to be parked until the feeCap satisfies
|
||||||
|
/// the block's baseFee.
|
||||||
|
fn add_transaction(
|
||||||
|
&mut self,
|
||||||
|
tx: ValidPoolTransaction<T::Transaction>,
|
||||||
|
) -> PoolResult<AddedTransaction<T::Transaction>> {
|
||||||
|
if self.contains(tx.hash()) {
|
||||||
|
warn!(target: "txpool", "[{:?}] Already added", tx.hash());
|
||||||
|
return Err(PoolError::AlreadyAdded(Box::new(*tx.hash())))
|
||||||
|
}
|
||||||
|
|
||||||
|
let tx = QueuedPoolTransaction::new(tx, self.pending.provided_dependencies());
|
||||||
|
trace!(target: "txpool", "[{:?}] {:?}", tx.transaction.hash(), tx);
|
||||||
|
|
||||||
|
// If all ids are not satisfied import to queued
|
||||||
|
if !tx.is_satisfied() {
|
||||||
|
let hash = *tx.transaction.hash();
|
||||||
|
self.queued.add_transaction(tx)?;
|
||||||
|
return Ok(AddedTransaction::Queued { hash })
|
||||||
|
}
|
||||||
|
self.add_pending_transaction(tx)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds the transaction to the pending pool.
|
||||||
|
///
|
||||||
|
/// This will also move all transaction that get unlocked by the dependency id this transaction
|
||||||
|
/// provides from the queued pool into the pending pool.
|
||||||
|
///
|
||||||
|
/// CAUTION: this expects that transaction's dependencies are fully satisfied
|
||||||
|
fn add_pending_transaction(
|
||||||
|
&mut self,
|
||||||
|
tx: QueuedPoolTransaction<T>,
|
||||||
|
) -> PoolResult<AddedTransaction<T::Transaction>> {
|
||||||
|
let hash = *tx.transaction.hash();
|
||||||
|
trace!(target: "txpool", "adding pending transaction [{:?}]", hash);
|
||||||
|
let mut pending = AddedPendingTransaction::new(hash);
|
||||||
|
|
||||||
|
// tracks all transaction that can be moved to the pending pool, starting the given
|
||||||
|
// transaction
|
||||||
|
let mut pending_transactions = VecDeque::from([tx]);
|
||||||
|
// tracks whether we're processing the given `tx`
|
||||||
|
let mut is_new_tx = true;
|
||||||
|
|
||||||
|
// take first transaction from the list
|
||||||
|
while let Some(current_tx) = pending_transactions.pop_front() {
|
||||||
|
// also add the transaction that the current transaction unlocks
|
||||||
|
pending_transactions
|
||||||
|
.extend(self.queued.satisfy_and_unlock(¤t_tx.transaction.transaction_id));
|
||||||
|
|
||||||
|
let current_hash = *current_tx.transaction.hash();
|
||||||
|
|
||||||
|
// try to add the transaction to the ready pool
|
||||||
|
match self.pending.add_transaction(current_tx) {
|
||||||
|
Ok(replaced_transactions) => {
|
||||||
|
if !is_new_tx {
|
||||||
|
pending.promoted.push(current_hash);
|
||||||
|
}
|
||||||
|
// tx removed from ready pool
|
||||||
|
pending.removed.extend(replaced_transactions);
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
// failed to add transaction
|
||||||
|
if is_new_tx {
|
||||||
|
debug!(target: "txpool", "[{:?}] Failed to add tx: {:?}", current_hash,
|
||||||
|
err);
|
||||||
|
return Err(err)
|
||||||
|
} else {
|
||||||
|
pending.discarded.push(current_hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is_new_tx = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for a cycle where importing a transaction resulted in pending transactions to be
|
||||||
|
// added while removing current transaction. in which case we move this transaction back to
|
||||||
|
// the pending queue
|
||||||
|
if pending.removed.iter().any(|tx| *tx.hash() == hash) {
|
||||||
|
self.pending.clear_transactions(&pending.promoted);
|
||||||
|
return Err(PoolError::CyclicTransaction)
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(AddedTransaction::Pending(pending))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prunes the transactions that provide the given dependencies.
|
||||||
|
///
|
||||||
|
/// This will effectively remove those transactions that satisfy the dependencies.
|
||||||
|
/// And queued transactions might get promoted if the pruned dependencies unlock them.
|
||||||
|
pub fn prune_transactions(
|
||||||
|
&mut self,
|
||||||
|
dependencies: impl IntoIterator<Item = TransactionId>,
|
||||||
|
) -> PruneResult<T::Transaction> {
|
||||||
|
let mut imports = vec![];
|
||||||
|
let mut pruned = vec![];
|
||||||
|
|
||||||
|
for dependency in dependencies {
|
||||||
|
// mark as satisfied and store the transactions that got unlocked
|
||||||
|
imports.extend(self.queued.satisfy_and_unlock(&dependency));
|
||||||
|
// prune transactions
|
||||||
|
pruned.extend(self.pending.remove_mined(dependency));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut promoted = vec![];
|
||||||
|
let mut failed = vec![];
|
||||||
|
for tx in imports {
|
||||||
|
let hash = *tx.transaction.hash();
|
||||||
|
match self.add_pending_transaction(tx) {
|
||||||
|
Ok(res) => promoted.push(res),
|
||||||
|
Err(e) => {
|
||||||
|
warn!(target: "txpool", "Failed to promote tx [{:?}] : {:?}", hash, e);
|
||||||
|
failed.push(hash)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PruneResult { pruned, failed, promoted }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove the given transactions from the pool.
|
||||||
|
pub fn remove_invalid(
|
||||||
|
&mut self,
|
||||||
|
tx_hashes: Vec<TxHash>,
|
||||||
|
) -> Vec<Arc<ValidPoolTransaction<T::Transaction>>> {
|
||||||
|
// early exit in case there is no invalid transactions.
|
||||||
|
if tx_hashes.is_empty() {
|
||||||
|
return vec![]
|
||||||
|
}
|
||||||
|
trace!(target: "txpool", "Removing invalid transactions: {:?}", tx_hashes);
|
||||||
|
|
||||||
|
let mut removed = self.pending.remove_with_dependencies(tx_hashes.clone(), None);
|
||||||
|
removed.extend(self.queued.remove(tx_hashes));
|
||||||
|
|
||||||
|
trace!(target: "txpool", "Removed invalid transactions: {:?}", removed);
|
||||||
|
|
||||||
|
removed
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the current size of the entire pool
|
||||||
|
pub fn size_of(&self) -> usize {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ensures that the transactions in the sub-pools are within the given bounds.
|
||||||
|
///
|
||||||
|
/// If the current size exceeds the given bounds, the worst transactions are evicted from the
|
||||||
|
/// pool and returned.
|
||||||
|
pub fn enforce_size_limits(&mut self) {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents the outcome of a prune
|
||||||
|
pub struct PruneResult<T: PoolTransaction> {
|
||||||
|
/// a list of added transactions that a pruned marker satisfied
|
||||||
|
pub promoted: Vec<AddedTransaction<T>>,
|
||||||
|
/// all transactions that failed to be promoted and now are discarded
|
||||||
|
pub failed: Vec<TxHash>,
|
||||||
|
/// all transactions that were pruned from the ready pool
|
||||||
|
pub pruned: Vec<Arc<ValidPoolTransaction<T>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: PoolTransaction> fmt::Debug for PruneResult<T> {
|
||||||
|
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(fmt, "PruneResult {{ ")?;
|
||||||
|
write!(
|
||||||
|
fmt,
|
||||||
|
"promoted: {:?}, ",
|
||||||
|
self.promoted.iter().map(|tx| *tx.hash()).collect::<Vec<_>>()
|
||||||
|
)?;
|
||||||
|
write!(fmt, "failed: {:?}, ", self.failed)?;
|
||||||
|
write!(
|
||||||
|
fmt,
|
||||||
|
"pruned: {:?}, ",
|
||||||
|
self.pruned.iter().map(|tx| *tx.transaction.hash()).collect::<Vec<_>>()
|
||||||
|
)?;
|
||||||
|
write!(fmt, "}}")?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tracks an added transaction and all graph changes caused by adding it.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct AddedPendingTransaction<T: PoolTransaction> {
|
||||||
|
/// the hash of the submitted transaction
|
||||||
|
hash: TxHash,
|
||||||
|
/// transactions promoted to the ready queue
|
||||||
|
promoted: Vec<TxHash>,
|
||||||
|
/// transaction that failed and became discarded
|
||||||
|
discarded: Vec<TxHash>,
|
||||||
|
/// Transactions removed from the Ready pool
|
||||||
|
removed: Vec<Arc<ValidPoolTransaction<T>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: PoolTransaction> AddedPendingTransaction<T> {
|
||||||
|
/// Create a new, empty transaction.
|
||||||
|
fn new(hash: TxHash) -> Self {
|
||||||
|
Self {
|
||||||
|
hash,
|
||||||
|
promoted: Default::default(),
|
||||||
|
discarded: Default::default(),
|
||||||
|
removed: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stores relevant context about a sender.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct SenderInfo {
|
||||||
|
/// current nonce of the sender
|
||||||
|
state_nonce: u64,
|
||||||
|
/// Balance of the sender at the current point.
|
||||||
|
balance: U256,
|
||||||
|
/// How many transactions of this sender are currently in the pool.
|
||||||
|
num_transactions: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
// === impl SenderInfo ===
|
||||||
|
|
||||||
|
impl SenderInfo {
|
||||||
|
/// Creates a new entry for an incoming, not yet tracked sender.
|
||||||
|
fn new_incoming(state_nonce: u64, balance: U256) -> Self {
|
||||||
|
Self { state_nonce, balance, num_transactions: 1 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents a transaction that was added into the pool and its state
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum AddedTransaction<T: PoolTransaction> {
|
||||||
|
/// Transaction was successfully added and moved to the pending pool.
|
||||||
|
Pending(AddedPendingTransaction<T>),
|
||||||
|
/// Transaction was successfully added but not yet queued for processing and moved to the
|
||||||
|
/// queued pool instead.
|
||||||
|
Queued {
|
||||||
|
/// the hash of the submitted transaction
|
||||||
|
hash: TxHash,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: PoolTransaction> AddedTransaction<T> {
|
||||||
|
/// Returns the hash of the transaction if it's ready
|
||||||
|
pub fn as_ready(&self) -> Option<&TxHash> {
|
||||||
|
if let AddedTransaction::Pending(tx) = self {
|
||||||
|
Some(&tx.hash)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the hash of the transaction
|
||||||
|
pub fn hash(&self) -> &TxHash {
|
||||||
|
match self {
|
||||||
|
AddedTransaction::Pending(tx) => &tx.hash,
|
||||||
|
AddedTransaction::Queued { hash } => hash,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
606
crates/transaction-pool/src/pool/pending.rs
Normal file
606
crates/transaction-pool/src/pool/pending.rs
Normal file
@ -0,0 +1,606 @@
|
|||||||
|
use crate::{
|
||||||
|
error::PoolResult, identifier::TransactionId, pool::queued::QueuedPoolTransaction,
|
||||||
|
traits::BestTransactions, validate::ValidPoolTransaction, TransactionOrdering,
|
||||||
|
};
|
||||||
|
use parking_lot::RwLock;
|
||||||
|
use reth_primitives::{TxHash, U256};
|
||||||
|
use std::{
|
||||||
|
cmp::Ordering,
|
||||||
|
collections::{BTreeSet, HashMap, HashSet},
|
||||||
|
sync::Arc,
|
||||||
|
};
|
||||||
|
use tracing::debug;
|
||||||
|
|
||||||
|
/// Type alias for replaced transactions
|
||||||
|
pub(crate) type ReplacedTransactions<T> =
|
||||||
|
(Vec<Arc<ValidPoolTransaction<<T as TransactionOrdering>::Transaction>>>, Vec<TxHash>);
|
||||||
|
|
||||||
|
/// A pool of validated transactions that are ready on the current state and are waiting to be
|
||||||
|
/// included in a block.
|
||||||
|
///
|
||||||
|
/// Each transaction in this pool is valid on its own, i.e. they are not dependent on transaction
|
||||||
|
/// that must be executed first. Each of these transaction can be executed independently on the
|
||||||
|
/// current state
|
||||||
|
pub(crate) struct PendingTransactions<T: TransactionOrdering> {
|
||||||
|
/// Keeps track of transactions inserted in the pool.
|
||||||
|
///
|
||||||
|
/// This way we can determine when transactions where submitted to the pool.
|
||||||
|
id: u64,
|
||||||
|
/// How to order transactions.
|
||||||
|
ordering: Arc<T>,
|
||||||
|
/// Base fee of the next block.
|
||||||
|
pending_base_fee: U256,
|
||||||
|
/// Dependencies that are provided by `PendingTransaction`s
|
||||||
|
provided_dependencies: HashMap<TransactionId, TxHash>,
|
||||||
|
/// Pending transactions that are currently on hold until the `baseFee` of the pending block
|
||||||
|
/// changes in favor of the parked transactions: the `pendingBlock.baseFee` must decrease
|
||||||
|
/// before they can be moved to the ready pool and are ready to be executed.
|
||||||
|
parked: ParkedTransactions<T>,
|
||||||
|
/// All Transactions that are currently ready.
|
||||||
|
///
|
||||||
|
/// Meaning, there are no nonce gaps in these transactions and all of them satisfy the
|
||||||
|
/// `baseFee` condition: transaction `maxFeePerGas >= pendingBlock.baseFee`
|
||||||
|
ready_transactions: Arc<RwLock<HashMap<TxHash, PendingTransaction<T>>>>,
|
||||||
|
/// Independent transactions that can be included directly and don't require other
|
||||||
|
/// transactions.
|
||||||
|
///
|
||||||
|
/// Sorted by their scoring value.
|
||||||
|
independent_transactions: BTreeSet<PoolTransactionRef<T>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// === impl PendingTransactions ===
|
||||||
|
|
||||||
|
impl<T: TransactionOrdering> PendingTransactions<T> {
|
||||||
|
/// Create a new pool instance
|
||||||
|
pub(crate) fn new(ordering: Arc<T>) -> Self {
|
||||||
|
Self {
|
||||||
|
id: 0,
|
||||||
|
provided_dependencies: Default::default(),
|
||||||
|
parked: Default::default(),
|
||||||
|
ready_transactions: Arc::new(Default::default()),
|
||||||
|
ordering,
|
||||||
|
independent_transactions: Default::default(),
|
||||||
|
pending_base_fee: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Returns an iterator over all transactions that are _currently_ ready.
|
||||||
|
///
|
||||||
|
/// 1. The iterator _always_ returns transaction in order: It never returns a transaction with
|
||||||
|
/// an unsatisfied dependency and only returns them if dependency transaction were yielded
|
||||||
|
/// previously. In other words: The nonces of transactions with the same sender will _always_
|
||||||
|
/// increase by exactly 1.
|
||||||
|
///
|
||||||
|
/// The order of transactions which satisfy (1.) is determent by their computed priority: A
|
||||||
|
/// transaction with a higher priority is returned before a transaction with a lower priority.
|
||||||
|
///
|
||||||
|
/// If two transactions have the same priority score, then the transactions which spent more
|
||||||
|
/// time in pool (were added earlier) are returned first.
|
||||||
|
///
|
||||||
|
/// NOTE: while this iterator returns transaction that pool considers valid at this point, they
|
||||||
|
/// could potentially be become invalid at point of execution. Therefore, this iterator
|
||||||
|
/// provides a way to mark transactions that the consumer of this iterator considers invalid. In
|
||||||
|
/// which case the transaction's subgraph is also automatically marked invalid, See (1.).
|
||||||
|
/// Invalid transactions are skipped.
|
||||||
|
pub(crate) fn get_transactions(&self) -> TransactionsIterator<T> {
|
||||||
|
TransactionsIterator {
|
||||||
|
all: self.ready_transactions.read().clone(),
|
||||||
|
independent: self.independent_transactions.clone(),
|
||||||
|
awaiting: Default::default(),
|
||||||
|
invalid: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the given base fee and returns the old one.
|
||||||
|
pub(crate) fn set_next_base_fee(&mut self, base_fee: U256) -> U256 {
|
||||||
|
std::mem::replace(&mut self.pending_base_fee, base_fee)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if the transaction is part of the queue.
|
||||||
|
pub(crate) fn contains(&self, hash: &TxHash) -> bool {
|
||||||
|
self.ready_transactions.read().contains_key(hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the transaction for the hash if it's in the ready pool but not yet mined
|
||||||
|
pub(crate) fn get(&self, hash: &TxHash) -> Option<PendingTransaction<T>> {
|
||||||
|
self.ready_transactions.read().get(hash).cloned()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn provided_dependencies(&self) -> &HashMap<TransactionId, TxHash> {
|
||||||
|
&self.provided_dependencies
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next_id(&mut self) -> u64 {
|
||||||
|
let id = self.id;
|
||||||
|
self.id = self.id.wrapping_add(1);
|
||||||
|
id
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds a new transactions to the pending queue.
|
||||||
|
///
|
||||||
|
/// Depending on the transaction's feeCap, this will either move it into the ready queue or park
|
||||||
|
/// it until a future baseFee unlocks it.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// if the pending transaction is not ready
|
||||||
|
/// or the transaction is already included
|
||||||
|
pub(crate) fn add_transaction(
|
||||||
|
&mut self,
|
||||||
|
tx: QueuedPoolTransaction<T>,
|
||||||
|
) -> PoolResult<Vec<Arc<ValidPoolTransaction<T::Transaction>>>> {
|
||||||
|
assert!(tx.is_satisfied(), "transaction must be ready",);
|
||||||
|
assert!(
|
||||||
|
!self.ready_transactions.read().contains_key(tx.transaction.hash()),
|
||||||
|
"transaction already included"
|
||||||
|
);
|
||||||
|
|
||||||
|
let (replaced_tx, unlocks) = self.replaced_transactions(&tx.transaction)?;
|
||||||
|
|
||||||
|
let submission_id = self.next_id();
|
||||||
|
let hash = *tx.transaction.hash();
|
||||||
|
|
||||||
|
let mut independent = true;
|
||||||
|
let mut requires_offset = 0;
|
||||||
|
let mut ready = self.ready_transactions.write();
|
||||||
|
|
||||||
|
// Add links to transactions that unlock the current one
|
||||||
|
for dependency in &tx.transaction.depends_on {
|
||||||
|
// Check if the transaction that satisfies the mark is still in the queue.
|
||||||
|
if let Some(other) = self.provided_dependencies.get(dependency) {
|
||||||
|
let tx = ready.get_mut(other).expect("hash included;");
|
||||||
|
tx.unlocks.push(hash);
|
||||||
|
// tx still depends on other tx
|
||||||
|
independent = false;
|
||||||
|
} else {
|
||||||
|
requires_offset += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// update dependencies
|
||||||
|
self.provided_dependencies.insert(tx.transaction.transaction_id, hash);
|
||||||
|
|
||||||
|
let priority = self.ordering.priority(&tx.transaction.transaction);
|
||||||
|
|
||||||
|
let transaction =
|
||||||
|
PoolTransactionRef { submission_id, transaction: tx.transaction, priority };
|
||||||
|
|
||||||
|
// TODO check basefee requirement
|
||||||
|
|
||||||
|
// add to the independent set
|
||||||
|
if independent {
|
||||||
|
self.independent_transactions.insert(transaction.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
// insert to ready queue
|
||||||
|
ready.insert(hash, PendingTransaction { transaction, unlocks, requires_offset });
|
||||||
|
|
||||||
|
Ok(replaced_tx)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes and returns those transactions that got replaced by the `tx`
|
||||||
|
fn replaced_transactions(
|
||||||
|
&mut self,
|
||||||
|
tx: &ValidPoolTransaction<T::Transaction>,
|
||||||
|
) -> PoolResult<ReplacedTransactions<T>> {
|
||||||
|
// check if we are replacing transactions
|
||||||
|
let remove_hashes: HashSet<_> =
|
||||||
|
if let Some(hash) = self.provided_dependencies.get(&tx.transaction_id) {
|
||||||
|
HashSet::from([hash])
|
||||||
|
} else {
|
||||||
|
return Ok((Vec::new(), Vec::new()))
|
||||||
|
};
|
||||||
|
|
||||||
|
// early exit if we are not replacing anything.
|
||||||
|
if remove_hashes.is_empty() {
|
||||||
|
return Ok((Vec::new(), Vec::new()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if we're replacing the same transaction and if it can be replaced
|
||||||
|
|
||||||
|
let mut unlocked_tx = Vec::new();
|
||||||
|
{
|
||||||
|
// construct a list of unlocked transactions
|
||||||
|
// also check for transactions that shouldn't be replaced because underpriced
|
||||||
|
let ready = self.ready_transactions.read();
|
||||||
|
for to_remove in remove_hashes.iter().filter_map(|hash| ready.get(hash)) {
|
||||||
|
// if we're attempting to replace a transaction that provides the exact same
|
||||||
|
// dependencies (addr + nonce) then we check for gas price
|
||||||
|
if to_remove.id().eq(&tx.transaction_id) {
|
||||||
|
// check if underpriced
|
||||||
|
// TODO check if underpriced
|
||||||
|
// if tx.pending_transaction.transaction.gas_price() <= to_remove.gas_price() {
|
||||||
|
// warn!(target: "txpool", "ready replacement transaction underpriced
|
||||||
|
// [{:?}]", tx.hash()); return
|
||||||
|
// Err(PoolError::ReplacementUnderpriced(Box::new(tx.clone())))
|
||||||
|
// } else {
|
||||||
|
// trace!(target: "txpool", "replacing ready transaction [{:?}] with higher
|
||||||
|
// gas price [{:?}]", to_remove.transaction.transaction.hash(), tx.hash());
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
unlocked_tx.extend(to_remove.unlocks.iter().cloned())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let remove_hashes = remove_hashes.into_iter().copied().collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let new_provides = HashSet::from([tx.transaction_id]);
|
||||||
|
let removed_tx = self.remove_with_dependencies(remove_hashes, Some(new_provides));
|
||||||
|
|
||||||
|
Ok((removed_tx, unlocked_tx))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes the transactions from the ready queue and returns the removed transactions.
|
||||||
|
/// This will also remove all transactions that depend on those.
|
||||||
|
pub(crate) fn clear_transactions(
|
||||||
|
&mut self,
|
||||||
|
tx_hashes: &[TxHash],
|
||||||
|
) -> Vec<Arc<ValidPoolTransaction<T::Transaction>>> {
|
||||||
|
self.remove_with_dependencies(tx_hashes.to_vec(), None)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes the transactions that was mined.
|
||||||
|
///
|
||||||
|
/// This will also remove all transactions that lead to the transaction that provides the
|
||||||
|
/// id.
|
||||||
|
pub(crate) fn remove_mined(
|
||||||
|
&mut self,
|
||||||
|
id: TransactionId,
|
||||||
|
) -> Vec<Arc<ValidPoolTransaction<T::Transaction>>> {
|
||||||
|
let mut removed_tx = vec![];
|
||||||
|
|
||||||
|
// the dependencies to remove
|
||||||
|
let mut remove = vec![id];
|
||||||
|
|
||||||
|
while let Some(dependency) = remove.pop() {
|
||||||
|
let res = self
|
||||||
|
.provided_dependencies
|
||||||
|
.remove(&dependency)
|
||||||
|
.and_then(|hash| self.ready_transactions.write().remove(&hash));
|
||||||
|
|
||||||
|
if let Some(tx) = res {
|
||||||
|
let unlocks = tx.unlocks;
|
||||||
|
self.independent_transactions.remove(&tx.transaction);
|
||||||
|
let tx = tx.transaction.transaction;
|
||||||
|
|
||||||
|
// also remove previous transactions
|
||||||
|
{
|
||||||
|
let hash = tx.hash();
|
||||||
|
let mut ready = self.ready_transactions.write();
|
||||||
|
|
||||||
|
let mut previous_dependency = |dependency| -> Option<Vec<TransactionId>> {
|
||||||
|
let prev_hash = self.provided_dependencies.get(dependency)?;
|
||||||
|
let tx2 = ready.get_mut(prev_hash)?;
|
||||||
|
// remove hash
|
||||||
|
if let Some(idx) = tx2.unlocks.iter().position(|i| i == hash) {
|
||||||
|
tx2.unlocks.swap_remove(idx);
|
||||||
|
}
|
||||||
|
if tx2.unlocks.is_empty() {
|
||||||
|
Some(vec![tx2.transaction.transaction.transaction_id])
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// find previous transactions
|
||||||
|
for dep in &tx.depends_on {
|
||||||
|
if let Some(mut dependency_to_remove) = previous_dependency(dep) {
|
||||||
|
remove.append(&mut dependency_to_remove);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// add the transactions that just got unlocked to independent set
|
||||||
|
for hash in unlocks {
|
||||||
|
if let Some(tx) = self.ready_transactions.write().get_mut(&hash) {
|
||||||
|
tx.requires_offset += 1;
|
||||||
|
if tx.requires_offset == tx.transaction.transaction.depends_on.len() {
|
||||||
|
self.independent_transactions.insert(tx.transaction.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// finally, remove the dependencies that this transaction provides
|
||||||
|
let current_dependency = &dependency;
|
||||||
|
let removed = self.provided_dependencies.remove(&tx.transaction_id);
|
||||||
|
assert_eq!(
|
||||||
|
removed.as_ref(),
|
||||||
|
if current_dependency.eq(&tx.transaction_id) { None } else { Some(tx.hash()) },
|
||||||
|
"The pool contains exactly one transaction providing given tag; the removed transaction
|
||||||
|
claims to provide that tag, so it has to be mapped to it's hash; qed"
|
||||||
|
);
|
||||||
|
removed_tx.push(tx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
removed_tx
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes transactions and those that depend on them and satisfy at least one dependency in
|
||||||
|
/// the given filter set.
|
||||||
|
pub(crate) fn remove_with_dependencies(
|
||||||
|
&mut self,
|
||||||
|
mut tx_hashes: Vec<TxHash>,
|
||||||
|
dependency_filter: Option<HashSet<TransactionId>>,
|
||||||
|
) -> Vec<Arc<ValidPoolTransaction<T::Transaction>>> {
|
||||||
|
let mut removed = Vec::new();
|
||||||
|
let mut ready = self.ready_transactions.write();
|
||||||
|
|
||||||
|
while let Some(hash) = tx_hashes.pop() {
|
||||||
|
if let Some(mut tx) = ready.remove(&hash) {
|
||||||
|
let id = &tx.transaction.transaction.transaction_id;
|
||||||
|
|
||||||
|
// remove the transactions
|
||||||
|
let removed_transaction = if dependency_filter
|
||||||
|
.as_ref()
|
||||||
|
.map(|filter| !filter.contains(id))
|
||||||
|
.unwrap_or(true)
|
||||||
|
{
|
||||||
|
self.provided_dependencies.remove(id);
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
|
// remove from unlocks
|
||||||
|
for dependency in &tx.transaction.transaction.depends_on {
|
||||||
|
if let Some(hash) = self.provided_dependencies.get(dependency) {
|
||||||
|
if let Some(tx) = ready.get_mut(hash) {
|
||||||
|
if let Some(idx) = tx.unlocks.iter().position(|i| i == hash) {
|
||||||
|
tx.unlocks.swap_remove(idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove from the independent set
|
||||||
|
self.independent_transactions.remove(&tx.transaction);
|
||||||
|
|
||||||
|
if removed_transaction {
|
||||||
|
// remove all transactions that the current one unlocks
|
||||||
|
tx_hashes.append(&mut tx.unlocks);
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove transaction
|
||||||
|
removed.push(tx.transaction.transaction);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
removed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A transaction that is ready to be included in a block.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct PendingTransaction<T: TransactionOrdering> {
|
||||||
|
/// Reference to the actual transaction.
|
||||||
|
transaction: PoolTransactionRef<T>,
|
||||||
|
/// Tracks the transactions that get unlocked by this transaction.
|
||||||
|
unlocks: Vec<TxHash>,
|
||||||
|
/// Amount of required dependencies that are inherently provided.
|
||||||
|
requires_offset: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
// == impl PendingTransaction ===
|
||||||
|
|
||||||
|
impl<T: TransactionOrdering> PendingTransaction<T> {
|
||||||
|
/// Returns all ids this transaction satisfies.
|
||||||
|
pub(crate) fn id(&self) -> &TransactionId {
|
||||||
|
&self.transaction.transaction.transaction_id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: TransactionOrdering> Clone for PendingTransaction<T> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
transaction: self.transaction.clone(),
|
||||||
|
unlocks: self.unlocks.clone(),
|
||||||
|
requires_offset: self.requires_offset,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A reference to a transaction in the _pending_ pool
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct PoolTransactionRef<T: TransactionOrdering> {
|
||||||
|
/// Actual transaction.
|
||||||
|
pub(crate) transaction: Arc<ValidPoolTransaction<T::Transaction>>,
|
||||||
|
/// Identifier that tags when transaction was submitted in the pool.
|
||||||
|
pub(crate) submission_id: u64,
|
||||||
|
/// The priority value assigned by the used `Ordering` function.
|
||||||
|
pub(crate) priority: T::Priority,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: TransactionOrdering> Clone for PoolTransactionRef<T> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
transaction: Arc::clone(&self.transaction),
|
||||||
|
submission_id: self.submission_id,
|
||||||
|
priority: self.priority.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: TransactionOrdering> Eq for PoolTransactionRef<T> {}
|
||||||
|
|
||||||
|
impl<T: TransactionOrdering> PartialEq<Self> for PoolTransactionRef<T> {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.cmp(other) == Ordering::Equal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: TransactionOrdering> PartialOrd<Self> for PoolTransactionRef<T> {
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||||
|
Some(self.cmp(other))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: TransactionOrdering> Ord for PoolTransactionRef<T> {
|
||||||
|
fn cmp(&self, other: &Self) -> Ordering {
|
||||||
|
// This compares by `priority` and only if two tx have the exact same priority this compares
|
||||||
|
// the unique `submission_id`. This ensures that transactions with same priority are not
|
||||||
|
// equal, so they're not replaced in the set
|
||||||
|
self.priority
|
||||||
|
.cmp(&other.priority)
|
||||||
|
.then_with(|| other.submission_id.cmp(&self.submission_id))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pending Transactions that are currently parked until their set baseFee becomes valid
|
||||||
|
struct ParkedTransactions<T: TransactionOrdering> {
|
||||||
|
/// Keeps track of transactions inserted in the pool.
|
||||||
|
///
|
||||||
|
/// This way we can determine when transactions where submitted to the pool.
|
||||||
|
id: u64,
|
||||||
|
/// All transactions that are currently parked due to their fee.
|
||||||
|
parked_transactions: HashMap<TxHash, ParkedTransaction<T>>,
|
||||||
|
/// Same transactions but sorted by their fee and priority
|
||||||
|
sorted_transactions: BTreeSet<ParkedTransactionRef<T>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: TransactionOrdering> Default for ParkedTransactions<T> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
id: 0,
|
||||||
|
parked_transactions: Default::default(),
|
||||||
|
sorted_transactions: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A transaction that is ready to be included in a block.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub(crate) struct ParkedTransaction<T: TransactionOrdering> {
|
||||||
|
/// Reference to the actual transaction.
|
||||||
|
transaction: PoolTransactionRef<T>,
|
||||||
|
/// Tracks the transactions that get unlocked by this transaction.
|
||||||
|
unlocks: Vec<TxHash>,
|
||||||
|
/// Amount of required dependencies that are inherently provided
|
||||||
|
requires_offset: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A reference to a currently _parked_ transaction.
|
||||||
|
struct ParkedTransactionRef<T: TransactionOrdering> {
|
||||||
|
/// Actual transaction.
|
||||||
|
transaction: Arc<ValidPoolTransaction<T::Transaction>>,
|
||||||
|
/// Identifier that tags when transaction was submitted in the pool.
|
||||||
|
submission_id: u64,
|
||||||
|
/// The priority value assigned by the used `Ordering` function.
|
||||||
|
priority: T::Priority,
|
||||||
|
/// EIP-1559 Max base fee the caller is willing to pay.
|
||||||
|
max_fee_per_gas: U256,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: TransactionOrdering> Eq for ParkedTransactionRef<T> {}
|
||||||
|
|
||||||
|
impl<T: TransactionOrdering> PartialEq<Self> for ParkedTransactionRef<T> {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.cmp(other) == Ordering::Equal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: TransactionOrdering> PartialOrd<Self> for ParkedTransactionRef<T> {
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||||
|
Some(self.cmp(other))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: TransactionOrdering> Ord for ParkedTransactionRef<T> {
|
||||||
|
fn cmp(&self, other: &Self) -> Ordering {
|
||||||
|
// This compares the `max_fee_per_gas` value of the transaction
|
||||||
|
self.max_fee_per_gas
|
||||||
|
.cmp(&other.max_fee_per_gas)
|
||||||
|
.then_with(|| self.priority.cmp(&other.priority))
|
||||||
|
.then_with(|| other.submission_id.cmp(&self.submission_id))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An iterator that returns transactions that can be executed on the current state.
|
||||||
|
pub struct TransactionsIterator<T: TransactionOrdering> {
|
||||||
|
all: HashMap<TxHash, PendingTransaction<T>>,
|
||||||
|
awaiting: HashMap<TxHash, (usize, PoolTransactionRef<T>)>,
|
||||||
|
independent: BTreeSet<PoolTransactionRef<T>>,
|
||||||
|
invalid: HashSet<TxHash>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// == impl TransactionsIterator ==
|
||||||
|
|
||||||
|
impl<T: TransactionOrdering> TransactionsIterator<T> {
|
||||||
|
/// Mark the transaction as invalid.
|
||||||
|
///
|
||||||
|
/// As a consequence, all values that depend on the invalid one will be skipped.
|
||||||
|
/// When given transaction is not in the pool it has no effect.
|
||||||
|
pub(crate) fn mark_invalid(&mut self, tx: &Arc<ValidPoolTransaction<T::Transaction>>) {
|
||||||
|
if let Some(invalid_transaction) = self.all.get(tx.hash()) {
|
||||||
|
debug!(
|
||||||
|
target: "txpool",
|
||||||
|
"[{:?}] Marked as invalid",
|
||||||
|
invalid_transaction.transaction.transaction.hash()
|
||||||
|
);
|
||||||
|
for hash in &invalid_transaction.unlocks {
|
||||||
|
self.invalid.insert(*hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Depending on number of satisfied requirements insert given ref
|
||||||
|
/// either to awaiting set or to best set.
|
||||||
|
fn independent_or_awaiting(&mut self, satisfied: usize, tx_ref: PoolTransactionRef<T>) {
|
||||||
|
if satisfied >= tx_ref.transaction.depends_on.len() {
|
||||||
|
// If we have satisfied all deps insert to the best set
|
||||||
|
self.independent.insert(tx_ref);
|
||||||
|
} else {
|
||||||
|
// otherwise we're still waiting for some deps
|
||||||
|
self.awaiting.insert(*tx_ref.transaction.hash(), (satisfied, tx_ref));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: TransactionOrdering> BestTransactions for TransactionsIterator<T> {
|
||||||
|
fn mark_invalid(&mut self, tx: &Self::Item) {
|
||||||
|
TransactionsIterator::mark_invalid(self, tx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: TransactionOrdering> Iterator for TransactionsIterator<T> {
|
||||||
|
type Item = Arc<ValidPoolTransaction<T::Transaction>>;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
loop {
|
||||||
|
let best = self.independent.iter().next_back()?.clone();
|
||||||
|
let best = self.independent.take(&best)?;
|
||||||
|
let hash = best.transaction.hash();
|
||||||
|
|
||||||
|
// skip transactions that were marked as invalid
|
||||||
|
if self.invalid.contains(hash) {
|
||||||
|
debug!(
|
||||||
|
target: "txpool",
|
||||||
|
"[{:?}] skipping invalid transaction",
|
||||||
|
hash
|
||||||
|
);
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
let ready =
|
||||||
|
if let Some(ready) = self.all.get(hash).cloned() { ready } else { continue };
|
||||||
|
|
||||||
|
// Insert transactions that just got unlocked.
|
||||||
|
for hash in &ready.unlocks {
|
||||||
|
// first check local awaiting transactions
|
||||||
|
let res = if let Some((mut satisfied, tx_ref)) = self.awaiting.remove(hash) {
|
||||||
|
satisfied += 1;
|
||||||
|
Some((satisfied, tx_ref))
|
||||||
|
// then get from the pool
|
||||||
|
} else {
|
||||||
|
self.all
|
||||||
|
.get(hash)
|
||||||
|
.map(|next| (next.requires_offset + 1, next.transaction.clone()))
|
||||||
|
};
|
||||||
|
if let Some((satisfied, tx_ref)) = res {
|
||||||
|
self.independent_or_awaiting(satisfied, tx_ref)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Some(best.transaction)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
232
crates/transaction-pool/src/pool/queued.rs
Normal file
232
crates/transaction-pool/src/pool/queued.rs
Normal file
@ -0,0 +1,232 @@
|
|||||||
|
use crate::{
|
||||||
|
error::PoolResult, identifier::TransactionId, traits::PoolTransaction,
|
||||||
|
validate::ValidPoolTransaction, TransactionOrdering,
|
||||||
|
};
|
||||||
|
use reth_primitives::TxHash;
|
||||||
|
use std::{
|
||||||
|
collections::{HashMap, HashSet},
|
||||||
|
fmt,
|
||||||
|
sync::Arc,
|
||||||
|
time::Instant,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// A pool of transactions that are not ready on the current state and are waiting for state changes
|
||||||
|
/// that turn them valid.
|
||||||
|
///
|
||||||
|
/// This could include transactions with nonce gaps: Transactions that are waiting until for a
|
||||||
|
/// transaction to arrive that closes the nonce gap.
|
||||||
|
///
|
||||||
|
/// Keeps a set of transactions that are waiting until their dependencies are unlocked.
|
||||||
|
pub(crate) struct QueuedTransactions<T: TransactionOrdering> {
|
||||||
|
/// Dependencies that aren't yet provided by any transaction.
|
||||||
|
required_dependencies: HashMap<TransactionId, HashSet<TxHash>>,
|
||||||
|
/// Mapping of the dependencies of a transaction to the hash of the transaction,
|
||||||
|
waiting_dependencies: HashMap<TransactionId, TxHash>,
|
||||||
|
/// Transactions that are not ready yet are waiting for another tx to finish,
|
||||||
|
waiting_queue: HashMap<TxHash, QueuedPoolTransaction<T>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// == impl QueuedTransactions ==
|
||||||
|
|
||||||
|
impl<T: TransactionOrdering> QueuedTransactions<T> {
|
||||||
|
/// Returns the number of transactions that are currently waiting in this pool for new
|
||||||
|
/// transactions to satisfy their dependencies.
|
||||||
|
pub(crate) fn len(&self) -> usize {
|
||||||
|
self.waiting_queue.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether this pool is empty.
|
||||||
|
pub(crate) fn is_empty(&self) -> bool {
|
||||||
|
self.waiting_queue.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns an iterator over all transactions waiting in this pool.
|
||||||
|
pub(crate) fn transactions(
|
||||||
|
&self,
|
||||||
|
) -> impl Iterator<Item = Arc<ValidPoolTransaction<T::Transaction>>> + '_ {
|
||||||
|
self.waiting_queue.values().map(|tx| Arc::clone(&tx.transaction))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds a transaction to the queue of transactions
|
||||||
|
pub(crate) fn add_transaction(&mut self, tx: QueuedPoolTransaction<T>) -> PoolResult<()> {
|
||||||
|
assert!(!tx.is_satisfied(), "transaction must not be ready");
|
||||||
|
assert!(
|
||||||
|
!self.waiting_queue.contains_key(tx.transaction.hash()),
|
||||||
|
"transaction is already added"
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Some(_replace) = self
|
||||||
|
.waiting_dependencies
|
||||||
|
.get(&tx.transaction.transaction_id)
|
||||||
|
.and_then(|hash| self.waiting_queue.get(hash))
|
||||||
|
{
|
||||||
|
// TODO handle transaction underpriced
|
||||||
|
// // check if underpriced
|
||||||
|
// if tx.transaction.gas_price() < replace.transaction.gas_price() {
|
||||||
|
// warn!(target: "txpool", "pending replacement transaction underpriced [{:?}]",
|
||||||
|
// tx.transaction.hash()); return Err(Error::ReplacementUnderpriced)
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
// add all missing dependencies
|
||||||
|
for dependency in &tx.missing_dependencies {
|
||||||
|
self.required_dependencies
|
||||||
|
.entry(*dependency)
|
||||||
|
.or_default()
|
||||||
|
.insert(*tx.transaction.hash());
|
||||||
|
}
|
||||||
|
|
||||||
|
// also track identifying dependencies
|
||||||
|
self.waiting_dependencies.insert(tx.transaction.transaction_id, *tx.transaction.hash());
|
||||||
|
|
||||||
|
// add tx to the queue
|
||||||
|
self.waiting_queue.insert(*tx.transaction.hash(), tx);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if given transaction is part of the queue
|
||||||
|
pub(crate) fn contains(&self, hash: &TxHash) -> bool {
|
||||||
|
self.waiting_queue.contains_key(hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the transaction for the hash if it's waiting
|
||||||
|
pub(crate) fn get(&self, tx_hash: &TxHash) -> Option<&QueuedPoolTransaction<T>> {
|
||||||
|
self.waiting_queue.get(tx_hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the transactions for the given hashes, `None` if no transaction exists
|
||||||
|
pub(crate) fn get_all(
|
||||||
|
&self,
|
||||||
|
tx_hashes: &[TxHash],
|
||||||
|
) -> Vec<Option<Arc<ValidPoolTransaction<T::Transaction>>>> {
|
||||||
|
tx_hashes
|
||||||
|
.iter()
|
||||||
|
.map(|hash| self.waiting_queue.get(hash).map(|tx| Arc::clone(&tx.transaction)))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This will check off the dependencies of queued transactions.
|
||||||
|
///
|
||||||
|
/// Returns the those transactions that become unlocked (all dependencies checked) and can be
|
||||||
|
/// moved to the ready queue.
|
||||||
|
pub(crate) fn satisfy_and_unlock(
|
||||||
|
&mut self,
|
||||||
|
id: &TransactionId,
|
||||||
|
) -> Vec<QueuedPoolTransaction<T>> {
|
||||||
|
let mut unlocked_ready = Vec::new();
|
||||||
|
if let Some(tx_hashes) = self.required_dependencies.remove(id) {
|
||||||
|
for hash in tx_hashes {
|
||||||
|
let tx = self.waiting_queue.get_mut(&hash).expect("tx is included;");
|
||||||
|
tx.satisfy(id);
|
||||||
|
if tx.is_satisfied() {
|
||||||
|
let tx = self.waiting_queue.remove(&hash).expect("tx is included;");
|
||||||
|
self.waiting_dependencies.remove(&tx.transaction.transaction_id);
|
||||||
|
unlocked_ready.push(tx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unlocked_ready
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes the transactions associated with the given hashes
|
||||||
|
///
|
||||||
|
/// Returns all removed transactions.
|
||||||
|
pub(crate) fn remove(
|
||||||
|
&mut self,
|
||||||
|
hashes: Vec<TxHash>,
|
||||||
|
) -> Vec<Arc<ValidPoolTransaction<T::Transaction>>> {
|
||||||
|
let mut removed = vec![];
|
||||||
|
for hash in hashes {
|
||||||
|
if let Some(waiting_tx) = self.waiting_queue.remove(&hash) {
|
||||||
|
self.waiting_dependencies.remove(&waiting_tx.transaction.transaction_id);
|
||||||
|
for dependency in waiting_tx.missing_dependencies {
|
||||||
|
let remove =
|
||||||
|
if let Some(required) = self.required_dependencies.get_mut(&dependency) {
|
||||||
|
required.remove(&hash);
|
||||||
|
required.is_empty()
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
if remove {
|
||||||
|
self.required_dependencies.remove(&dependency);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
removed.push(waiting_tx.transaction)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
removed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A transaction submitted to the pool.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub(crate) struct QueuedPoolTransaction<T: TransactionOrdering> {
|
||||||
|
/// The actual validated transaction.
|
||||||
|
pub(crate) transaction: Arc<ValidPoolTransaction<T::Transaction>>,
|
||||||
|
/// Transactions required for and have not been satisfied yet by other transactions in the
|
||||||
|
/// pool.
|
||||||
|
///
|
||||||
|
/// This will be an empty list if there are no nonce gaps across multiple transactions of the
|
||||||
|
/// same sender in the pool. If there are gaps, this will include the missing transactions.
|
||||||
|
pub(crate) missing_dependencies: HashSet<TransactionId>,
|
||||||
|
/// Timestamp when the tx was added.
|
||||||
|
pub(crate) added_at: Instant,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: TransactionOrdering> Default for QueuedTransactions<T> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
required_dependencies: Default::default(),
|
||||||
|
waiting_dependencies: Default::default(),
|
||||||
|
waiting_queue: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// === impl QuQueuedPoolTransaction ===
|
||||||
|
|
||||||
|
impl<T: TransactionOrdering> QueuedPoolTransaction<T> {
|
||||||
|
/// Creates a new `QueuedPoolTransaction`.
|
||||||
|
///
|
||||||
|
/// Determines the dependent transaction that are still missing before this transaction can be
|
||||||
|
/// moved to the queue.
|
||||||
|
pub(crate) fn new(
|
||||||
|
transaction: ValidPoolTransaction<T::Transaction>,
|
||||||
|
provided: &HashMap<TransactionId, TxHash>,
|
||||||
|
) -> Self {
|
||||||
|
let missing_dependencies = transaction
|
||||||
|
.depends_on
|
||||||
|
.iter()
|
||||||
|
.filter(|id| {
|
||||||
|
// is true if the dependency id is already satisfied either via transaction in the
|
||||||
|
// pool
|
||||||
|
!provided.contains_key(&**id)
|
||||||
|
})
|
||||||
|
.cloned()
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Self { transaction: Arc::new(transaction), missing_dependencies, added_at: Instant::now() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes the required dependency.
|
||||||
|
pub(crate) fn satisfy(&mut self, id: &TransactionId) {
|
||||||
|
self.missing_dependencies.remove(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if transaction has all dependencies are satisfied.
|
||||||
|
pub(crate) fn is_satisfied(&self) -> bool {
|
||||||
|
self.missing_dependencies.is_empty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: TransactionOrdering> fmt::Debug for QueuedPoolTransaction<T> {
|
||||||
|
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(fmt, "QueuedPoolTransaction {{ ")?;
|
||||||
|
write!(fmt, "added_at: {:?}, ", self.added_at)?;
|
||||||
|
write!(fmt, "tx: {:?}, ", self.transaction)?;
|
||||||
|
write!(fmt, "missing_dependencies: [{:?}]", &self.missing_dependencies)?;
|
||||||
|
write!(fmt, "}}")
|
||||||
|
}
|
||||||
|
}
|
||||||
1
crates/transaction-pool/src/pool/transaction.rs
Normal file
1
crates/transaction-pool/src/pool/transaction.rs
Normal file
@ -0,0 +1 @@
|
|||||||
|
|
||||||
123
crates/transaction-pool/src/traits.rs
Normal file
123
crates/transaction-pool/src/traits.rs
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
use crate::{error::PoolResult, validate::ValidPoolTransaction, BlockID};
|
||||||
|
use futures::channel::mpsc::Receiver;
|
||||||
|
use reth_primitives::{Address, TxHash, H256, U256};
|
||||||
|
use std::{fmt, hash::Hash, sync::Arc};
|
||||||
|
|
||||||
|
/// General purpose abstraction fo a transaction-pool.
|
||||||
|
///
|
||||||
|
/// This is intended to be used by API-consumers such as RPC that need inject new incoming,
|
||||||
|
/// unverified transactions. And by block production that needs to get transactions to execute in a
|
||||||
|
/// new block.
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
pub trait TransactionPool: Send + Sync {
|
||||||
|
/// The transaction type of the pool
|
||||||
|
type Transaction: PoolTransaction + Send + Sync + 'static;
|
||||||
|
|
||||||
|
/// Event listener for when a new block was mined.
|
||||||
|
///
|
||||||
|
/// Implementers need to update the pool accordingly.
|
||||||
|
/// For example the base fee of the pending block is determined after a block is mined which
|
||||||
|
/// affects the dynamic fee requirement of pending transactions in the pool.
|
||||||
|
async fn on_new_block(&self, event: NewBlockEvent);
|
||||||
|
|
||||||
|
/// Adds an _unvalidated_ transaction into the pool.
|
||||||
|
///
|
||||||
|
/// Consumer: RPC
|
||||||
|
async fn add_transaction(
|
||||||
|
&self,
|
||||||
|
block_id: BlockID,
|
||||||
|
transaction: Self::Transaction,
|
||||||
|
) -> PoolResult<TxHash>;
|
||||||
|
|
||||||
|
/// Adds the given _unvalidated_ transaction into the pool.
|
||||||
|
///
|
||||||
|
/// Returns a list of results.
|
||||||
|
///
|
||||||
|
/// Consumer: RPC
|
||||||
|
async fn add_transactions(
|
||||||
|
&self,
|
||||||
|
block_id: BlockID,
|
||||||
|
transactions: Vec<Self::Transaction>,
|
||||||
|
) -> PoolResult<Vec<PoolResult<TxHash>>>;
|
||||||
|
|
||||||
|
/// Returns a new Stream that yields transactions hashes for new ready transactions.
|
||||||
|
///
|
||||||
|
/// Consumer: RPC
|
||||||
|
fn ready_transactions_listener(&self) -> Receiver<TxHash>;
|
||||||
|
|
||||||
|
/// Returns an iterator that yields transactions that are ready for block production.
|
||||||
|
///
|
||||||
|
/// Consumer: Block production
|
||||||
|
fn best_transactions(
|
||||||
|
&self,
|
||||||
|
) -> Box<dyn BestTransactions<Item = Arc<ValidPoolTransaction<Self::Transaction>>>>;
|
||||||
|
|
||||||
|
/// Removes all transactions corresponding to the given hashes.
|
||||||
|
///
|
||||||
|
/// Also removes all dependent transactions.
|
||||||
|
///
|
||||||
|
/// Consumer: Block production
|
||||||
|
fn remove_invalid(
|
||||||
|
&self,
|
||||||
|
tx_hashes: &[TxHash],
|
||||||
|
) -> Vec<Arc<ValidPoolTransaction<Self::Transaction>>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Event fired when a new block was mined
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct NewBlockEvent {
|
||||||
|
/// Hash of the added block.
|
||||||
|
pub hash: H256,
|
||||||
|
/// EIP-1559 Base fee of the _next_ (pending) block
|
||||||
|
///
|
||||||
|
/// The base fee of a block depends on the utilization of the last block and its base fee.
|
||||||
|
pub pending_block_base_fee: U256,
|
||||||
|
/// Provides a set of state changes that affected the accounts.
|
||||||
|
// TODO based on the account changes, we can recheck balance
|
||||||
|
pub state_changes: (),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An `Iterator` that only returns transactions that are ready to be executed.
|
||||||
|
///
|
||||||
|
/// This makes no assumptions about the order of the transactions, but expects that _all_
|
||||||
|
/// transactions are valid (no nonce gaps.).
|
||||||
|
pub trait BestTransactions: Iterator + Send {
|
||||||
|
/// Mark the transaction as invalid.
|
||||||
|
///
|
||||||
|
/// Implementers must ensure all subsequent transaction _don't_ depend on this transaction.
|
||||||
|
/// In other words, this must remove the given transaction _and_ drain all transaction that
|
||||||
|
/// depend on it.
|
||||||
|
fn mark_invalid(&mut self, transaction: &Self::Item);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A no-op implementation that yields no transactions.
|
||||||
|
impl<T> BestTransactions for std::iter::Empty<T> {
|
||||||
|
fn mark_invalid(&mut self, _tx: &T) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Trait for transaction types used inside the pool
|
||||||
|
pub trait PoolTransaction: fmt::Debug + Send + Send + 'static {
|
||||||
|
/// Hash of the transaction
|
||||||
|
fn hash(&self) -> &TxHash;
|
||||||
|
|
||||||
|
/// The Sender of the transaction.
|
||||||
|
fn sender(&self) -> &Address;
|
||||||
|
|
||||||
|
/// Returns the nonce for this transaction.
|
||||||
|
fn nonce(&self) -> u64;
|
||||||
|
|
||||||
|
/// Calculates the cost that this transaction is allowed to consume:
|
||||||
|
///
|
||||||
|
/// For EIP-1559 transactions that is `feeCap x gasLimit + transferred_value`
|
||||||
|
fn cost(&self) -> U256;
|
||||||
|
|
||||||
|
/// Returns the EIP-1559 Max base fee the caller is willing to pay.
|
||||||
|
///
|
||||||
|
/// This will return `None` for non-EIP1559 transactions
|
||||||
|
fn max_fee_per_gas(&self) -> Option<&U256>;
|
||||||
|
|
||||||
|
/// Returns the EIP-1559 Priority fee the caller is paying to the block author.
|
||||||
|
///
|
||||||
|
/// This will return `None` for non-EIP1559 transactions
|
||||||
|
fn max_priority_fee_per_gas(&self) -> Option<&U256>;
|
||||||
|
}
|
||||||
89
crates/transaction-pool/src/validate.rs
Normal file
89
crates/transaction-pool/src/validate.rs
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
//! Transaction validation abstractions.
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
error::PoolError,
|
||||||
|
identifier::{SenderId, TransactionId},
|
||||||
|
traits::PoolTransaction,
|
||||||
|
};
|
||||||
|
use reth_primitives::{BlockID, TxHash, U256};
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
/// A Result type returned after checking a transaction's validity.
|
||||||
|
pub enum TransactionValidationOutcome<T: PoolTransaction> {
|
||||||
|
/// Transaction successfully validated
|
||||||
|
Valid {
|
||||||
|
/// Balance of the sender at the current point.
|
||||||
|
balance: U256,
|
||||||
|
/// current nonce of the sender
|
||||||
|
state_nonce: u64,
|
||||||
|
/// Validated transaction.
|
||||||
|
transaction: T,
|
||||||
|
},
|
||||||
|
/// The transaction is considered invalid.
|
||||||
|
///
|
||||||
|
/// Note: This does not indicate whether the transaction will not be valid in the future
|
||||||
|
Invalid(T, PoolError),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Provides support for validating transaction at any given state of the chain
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
pub trait TransactionValidator: Send + Sync {
|
||||||
|
/// The transaction type to validate.
|
||||||
|
type Transaction: PoolTransaction + Send + Sync + 'static;
|
||||||
|
|
||||||
|
/// Validates the transaction and returns a validated outcome
|
||||||
|
///
|
||||||
|
/// This is used by the transaction-pool check the transaction's validity against the state of
|
||||||
|
/// the given block hash.
|
||||||
|
///
|
||||||
|
/// This is supposed to extend the `transaction` with its id in the graph of
|
||||||
|
/// transactions for the sender.
|
||||||
|
async fn validate_transaction(
|
||||||
|
&self,
|
||||||
|
_block_id: &BlockID,
|
||||||
|
_transaction: Self::Transaction,
|
||||||
|
) -> TransactionValidationOutcome<Self::Transaction> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A valida transaction in the pool.
|
||||||
|
pub struct ValidPoolTransaction<T: PoolTransaction> {
|
||||||
|
/// The transaction
|
||||||
|
pub transaction: T,
|
||||||
|
/// Ids required by the transaction.
|
||||||
|
///
|
||||||
|
/// This lists all unique transactions that need to be mined before this transaction can be
|
||||||
|
/// considered `pending` and itself be included.
|
||||||
|
pub depends_on: Vec<TransactionId>,
|
||||||
|
/// The identifier for this transaction.
|
||||||
|
pub transaction_id: TransactionId,
|
||||||
|
/// Whether to propagate the transaction.
|
||||||
|
pub propagate: bool,
|
||||||
|
/// Internal `Sender` identifier
|
||||||
|
pub sender_id: SenderId,
|
||||||
|
/// Total cost of the transaction: `feeCap x gasLimit + transferred_value`
|
||||||
|
pub cost: U256,
|
||||||
|
// TODO add a block timestamp that marks validity
|
||||||
|
}
|
||||||
|
|
||||||
|
// === impl ValidPoolTransaction ===
|
||||||
|
|
||||||
|
impl<T: PoolTransaction> ValidPoolTransaction<T> {
|
||||||
|
/// Returns the hash of the transaction
|
||||||
|
pub fn hash(&self) -> &TxHash {
|
||||||
|
self.transaction.hash()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: PoolTransaction> fmt::Debug for ValidPoolTransaction<T> {
|
||||||
|
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(fmt, "Transaction {{ ")?;
|
||||||
|
write!(fmt, "hash: {:?}, ", &self.transaction.hash())?;
|
||||||
|
write!(fmt, "provides: {:?}, ", &self.transaction_id)?;
|
||||||
|
write!(fmt, "depends_on: {:?}, ", &self.depends_on)?;
|
||||||
|
write!(fmt, "raw tx: {:?}", &self.transaction)?;
|
||||||
|
write!(fmt, "}}")?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
3
crates/transaction-pool/tests/it/main.rs
Normal file
3
crates/transaction-pool/tests/it/main.rs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
//! transaction-pool integration tests
|
||||||
|
|
||||||
|
fn main() {}
|
||||||
Reference in New Issue
Block a user