Skip to content

Commit

Permalink
feat(txpool): initial sketch (paradigmxyz#7)
Browse files Browse the repository at this point in the history
  • Loading branch information
mattsse authored Oct 7, 2022
1 parent 95ed994 commit 791ee28
Show file tree
Hide file tree
Showing 18 changed files with 2,173 additions and 0 deletions.
33 changes: 33 additions & 0 deletions Cargo.lock

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

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ members = [
"crates/net/rpc-types",
"crates/primitives",
"crates/stages",
"crates/transaction-pool",
"crates/db"
]

[dependencies]
Expand Down
27 changes: 27 additions & 0 deletions crates/transaction-pool/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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 changes: 24 additions & 0 deletions crates/transaction-pool/src/client.rs
Original file line number Diff line number Diff line change
@@ -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 changes: 13 additions & 0 deletions crates/transaction-pool/src/config.rs
Original file line number Diff line number Diff line change
@@ -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 changes: 23 additions & 0 deletions crates/transaction-pool/src/error.rs
Original file line number Diff line number Diff line change
@@ -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 changes: 95 additions & 0 deletions crates/transaction-pool/src/identifier.rs
Original file line number Diff line number Diff line change
@@ -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 changes: 114 additions & 0 deletions crates/transaction-pool/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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 changes: 20 additions & 0 deletions crates/transaction-pool/src/ordering.rs
Original file line number Diff line number Diff line change
@@ -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;
}
Loading

0 comments on commit 791ee28

Please sign in to comment.