-
Notifications
You must be signed in to change notification settings - Fork 2.5k
wip: feat(txpool): initial sketch #7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
5f869fa
91efdc2
4f8b9d9
ddcf584
f679140
8a04dd6
6238619
22f1d88
00ad854
6028f47
b2ff8b7
9e1e377
3e20868
6b28560
07aab5c
56dfb76
df66604
8ba1501
153e5bf
a319290
fc0e773
957e330
9fdea85
1d710a3
5152cd1
acea23a
9ea4337
53ec974
b6936fa
eb0fa84
62a9953
9368ec0
24c10c3
3002938
0eb1539
9f23ade
82c5c4b
af1aaf3
28324d1
fe4ca48
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| 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" |
| 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>>; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am not sure if i understand why would we need get block number and mapping of hash->height. TxPool should only care about base_fee and transactions that are removed/added, and that is broadcasted to txpool on block inclusion. |
||
|
|
||
| /// 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))) | ||
| } | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. note: we probably need |
||
| } | ||
| 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!() | ||
| } | ||
| } |
| 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), | ||
| } |
| 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 { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I searched code and this seems not used |
||
| /// 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); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It seems like there is some internal mapping that uses |
||
|
|
||
| /// 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 | ||
| } | ||
| } | ||
| } | ||
| 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>, | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We have 3 layers of abstractions here. Pool-> BasicPool->PoolInner, this probably can be shrunk to only two as I am not sure if that abstraction is needed. |
||
| /// 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, | ||
| } | ||
| 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; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is
TransactionValidatorneeded here?