We'll extend reth's transaction pool to support a segregated private mempool with access controls, while ensuring private transactions don't leak to the public network.
File: crates/transaction-pool/src/pool/mod.rs
use std::collections::{HashMap, HashSet};
use reth_primitives::{Address, TxHash, B256};
use tokio::sync::RwLock;
#[derive(Debug, Clone)]
pub struct PrivateTransaction {
pub transaction: PooledTransaction,
pub submitter: Address,
pub submission_time: u64,
pub priority_fee: u128,
}
#[derive(Debug)]
pub struct PrivateMempool {
/// Private transactions by hash
private_txs: HashMap<TxHash, PrivateTransaction>,
/// Authorized addresses that can submit private transactions
authorized_submitters: HashSet<Address>,
/// Max private transactions to store
max_private_txs: usize,
/// Access control for authentication
access_control: Arc<AccessControl>,
}
impl PrivateMempool {
pub fn new(max_private_txs: usize) -> Self {
Self {
private_txs: HashMap::new(),
authorized_submitters: HashSet::new(),
max_private_txs,
access_control: Arc::new(AccessControl::new()),
}
}
pub fn add_private_transaction(
&mut self,
tx: PooledTransaction,
submitter: Address,
) -> Result<TxHash, PrivateMempoolError> {
// Validate authorization
if !self.is_authorized(&submitter) {
return Err(PrivateMempoolError::Unauthorized);
}
// Check capacity
if self.private_txs.len() >= self.max_private_txs {
self.evict_oldest();
}
let tx_hash = tx.hash();
let private_tx = PrivateTransaction {
transaction: tx,
submitter,
submission_time: SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(),
priority_fee: tx.max_priority_fee_per_gas().unwrap_or_default(),
};
self.private_txs.insert(tx_hash, private_tx);
Ok(tx_hash)
}
pub fn get_private_transactions_for_block(&self, limit: usize) -> Vec<PooledTransaction> {
let mut txs: Vec<_> = self.private_txs.values().collect();
// Sort by priority fee (descending) then by submission time (ascending)
txs.sort_by(|a, b| {
b.priority_fee.cmp(&a.priority_fee)
.then_with(|| a.submission_time.cmp(&b.submission_time))
});
txs.into_iter()
.take(limit)
.map(|private_tx| private_tx.transaction.clone())
.collect()
}
}
Extend existing TxPool struct:
// In the main TxPool struct, add:
pub struct Pool<V: PoolTransaction, T: TransactionOrdering, S: PooledTransactions<Transaction = V>> {
// ... existing fields
/// Private mempool for authorized transactions
private_mempool: Arc<RwLock<PrivateMempool>>,
}
Create: crates/transaction-pool/src/access_control.rs
use jsonwebtoken::{decode, DecodingKey, Validation, Algorithm};
use serde::{Deserialize, Serialize};
use std::collections::HashSet;
#[derive(Debug, Serialize, Deserialize)]
struct Claims {
sub: String, // submitter address
exp: usize, // expiration time
iat: usize, // issued at
authorized_addresses: Vec<String>,
}
pub struct AccessControl {
jwt_secret: String,
api_keys: HashSet<String>,
authorized_addresses: HashSet<Address>,
}
impl AccessControl {
pub fn new() -> Self {
Self {
jwt_secret: std::env::var("PRIVATE_MEMPOOL_JWT_SECRET")
.unwrap_or_else(|_| "default-secret".to_string()),
api_keys: HashSet::new(),
authorized_addresses: HashSet::new(),
}
}
pub fn validate_jwt(&self, token: &str) -> Result<Address, AccessError> {
let decoding_key = DecodingKey::from_secret(self.jwt_secret.as_bytes());
let validation = Validation::new(Algorithm::HS256);
match decode::<Claims>(token, &decoding_key, &validation) {
Ok(token_data) => {
let address = token_data.claims.sub.parse::<Address>()
.map_err(|_| AccessError::InvalidAddress)?;
if self.authorized_addresses.contains(&address) {
Ok(address)
} else {
Err(AccessError::Unauthorized)
}
}
Err(_) => Err(AccessError::InvalidToken),
}
}
pub fn validate_api_key(&self, api_key: &str, address: Address) -> Result<(), AccessError> {
if self.api_keys.contains(api_key) && self.authorized_addresses.contains(&address) {
Ok(())
} else {
Err(AccessError::Unauthorized)
}
}
}
File: crates/rpc/rpc/src/eth/api/transactions.rs
// Add new RPC methods for private mempool
impl<Provider, Pool, Network, EvmConfig> EthApi<Provider, Pool, Network, EvmConfig>
where
Provider: BlockReaderIdExt + ChainSpecProvider + StateProviderFactory + EvmEnvProvider + 'static,
Pool: TransactionPool + 'static,
Network: NetworkInfo + Peers + 'static,
EvmConfig: ConfigureEvm + 'static,
{
/// Submit a private transaction
pub async fn send_private_transaction(
&self,
request: TransactionRequest,
auth_token: String,
) -> Result<B256, EthApiError> {
// Validate authentication
let submitter = self.pool().private_mempool()
.read()
.await
.access_control()
.validate_jwt(&auth_token)?;
// Convert request to transaction
let transaction = self.build_transaction(request, submitter).await?;
// Add to private mempool
let tx_hash = self.pool()
.private_mempool()
.write()
.await
.add_private_transaction(transaction, submitter)?;
Ok(tx_hash)
}
/// Get private mempool status for authorized users
pub async fn get_private_mempool_status(
&self,
auth_token: String,
) -> Result<PrivateMempoolStatus, EthApiError> {
let submitter = self.pool().private_mempool()
.read()
.await
.access_control()
.validate_jwt(&auth_token)?;
let private_mempool = self.pool().private_mempool().read().await;
Ok(PrivateMempoolStatus {
pending_count: private_mempool.pending_count(),
your_transactions: private_mempool.get_transactions_by_submitter(submitter),
})
}
}
File: crates/payload/builder/src/lib.rs
// Modify the payload builder to prioritize private transactions
impl<Pool, Client> PayloadBuilder<Pool, Client>
where
Pool: TransactionPool,
Client: StateProviderFactory,
{
pub fn build_payload(&self, args: BuildArguments) -> Result<BuiltPayload, PayloadBuilderError> {
let mut transactions = Vec::new();
// First, add private transactions with higher priority
let private_mempool = self.pool.private_mempool().read().await;
let private_txs = private_mempool.get_private_transactions_for_block(100); // Max 100 private txs per block
for private_tx in private_txs {
if self.can_fit_transaction(&private_tx, &args) {
transactions.push(private_tx);
}
}
// Then fill remaining space with public transactions
let public_txs = self.pool.best_transactions_with_attributes(TransactionListingAttributes {
// ... existing attributes
exclude_private: true, // New field to exclude private transactions
});
for public_tx in public_txs {
if transactions.len() >= args.max_transactions {
break;
}
if self.can_fit_transaction(&public_tx, &args) {
transactions.push(public_tx);
}
}
self.build_block_with_transactions(transactions, args)
}
}
File: crates/net/network/src/transactions.rs
// Modify transaction propagation to exclude private transactions
impl<Pool> TransactionsManager<Pool>
where
Pool: TransactionPool,
{
/// Modified to prevent private transaction gossip
fn on_new_transactions(&mut self, txs: Vec<NewPooledTransaction>) {
let mut public_txs = Vec::new();
for tx in txs {
// Only propagate if not in private mempool
if !self.pool.is_private_transaction(&tx.hash()) {
public_txs.push(tx);
}
}
if !public_txs.is_empty() {
// Existing propagation logic for public transactions only
self.propagate_transactions(public_txs);
}
}
/// Ensure private transactions are never included in transaction announcements
fn get_transactions_to_propagate(&self, peer_id: PeerId) -> Vec<TxHash> {
self.pool
.pooled_transactions_max(MAX_TRANSACTIONS_TO_PROPAGATE)
.filter(|tx| !self.pool.is_private_transaction(&tx.hash()))
.map(|tx| tx.hash())
.collect()
}
}
File: bin/reth/src/args/private_mempool.rs
use clap::Args;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Args, PartialEq, Eq, Serialize, Deserialize)]
pub struct PrivateMempoolArgs {
/// Enable private mempool functionality
#[arg(long, default_value = "false")]
pub enable_private_mempool: bool,
/// Maximum number of private transactions to store
#[arg(long, default_value = "10000")]
pub max_private_transactions: usize,
/// JWT secret for private transaction authentication
#[arg(long, env = "PRIVATE_MEMPOOL_JWT_SECRET")]
pub jwt_secret: Option<String>,
/// File containing authorized addresses
#[arg(long)]
pub authorized_addresses_file: Option<PathBuf>,
/// Private transaction TTL in seconds
#[arg(long, default_value = "300")]
pub private_transaction_ttl: u64,
}
Create: crates/transaction-pool/src/test/private_mempool_tests.rs
#[cfg(test)]
mod tests {
use super::*;
use reth_primitives::TransactionSigned;
#[tokio::test]
async fn test_private_transaction_submission() {
let mut private_mempool = PrivateMempool::new(1000);
// Add authorized submitter
let authorized_addr = Address::random();
private_mempool.add_authorized_address(authorized_addr);
// Create test transaction
let tx = create_test_transaction();
// Should succeed for authorized address
let result = private_mempool.add_private_transaction(tx.clone(), authorized_addr);
assert!(result.is_ok());
// Should fail for unauthorized address
let unauthorized_addr = Address::random();
let result = private_mempool.add_private_transaction(tx, unauthorized_addr);
assert!(result.is_err());
}
#[tokio::test]
async fn test_private_transaction_ordering() {
let mut private_mempool = PrivateMempool::new(1000);
// Add transactions with different priority fees
let high_fee_tx = create_transaction_with_priority_fee(1000);
let low_fee_tx = create_transaction_with_priority_fee(100);
private_mempool.add_private_transaction(low_fee_tx, Address::random()).unwrap();
private_mempool.add_private_transaction(high_fee_tx, Address::random()).unwrap();
let ordered_txs = private_mempool.get_private_transactions_for_block(10);
// High fee transaction should be first
assert!(ordered_txs[0].max_priority_fee_per_gas() > ordered_txs[1].max_priority_fee_per_gas());
}
}
Create: docs/private_mempool.md
# Private Mempool Configuration
## Setup
1. Generate JWT secret:
```bash
openssl rand -base64 32
- Create authorized addresses file:
[
"0x742d35cc6C2C2f0B5e2CD6EDZ...",
"0x8ba1f109551bD432803012645Hd..."
]
- Start reth with private mempool:
reth node \
--enable-private-mempool \
--jwt-secret="your-secret-here" \
--authorized-addresses-file=./authorized.json \
--max-private-transactions=5000
Submit private transaction:
curl -X POST http://localhost:8545 \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "eth_sendPrivateTransaction",
"params": [{
"from": "0x...",
"to": "0x...",
"value": "0x1000",
"gas": "0x5208"
}, "your-jwt-token"],
"id": 1
}'
## Implementation Checklist
**Week 1:**
- [ ] Private mempool data structures
- [ ] Access control with JWT/API keys
- [ ] Basic RPC endpoints
- [ ] Unit tests for core functionality
**Week 2:**
- [ ] Block building integration
- [ ] Network layer modifications (prevent gossip)
- [ ] CLI configuration
- [ ] Integration tests
- [ ] Documentation
**Optional Enhancements (Week 3+):**
- [ ] Metrics and monitoring
- [ ] Rate limiting per submitter
- [ ] Transaction replacement policies
- [ ] MEV protection mechanisms
- URL: https://linear.app/infrared-dev/issue/SC-133/private-mempool-reth-patch
- Identifier: SC-133
- Status: Backlog
- Priority: Medium
- Assignee: Nofront Bear
- Labels: Smart Contracts
- Created: 2025-08-27T10:18:18.945Z
- Updated: 2025-08-27T10:18:18.945Z