forked from paradigmxyz/reth
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(txpool): initial sketch (paradigmxyz#7)
- Loading branch information
Showing
18 changed files
with
2,173 additions
and
0 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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))) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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!() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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), | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
Oops, something went wrong.