feat: enforce no nonce gaps for eip-4844 (#4487)

Co-authored-by: Dan Cline <6798349+Rjected@users.noreply.github.com>
This commit is contained in:
Matthias Seitz
2023-09-05 21:26:01 +02:00
committed by GitHub
parent ca3753d53f
commit cd7e1135d8
4 changed files with 80 additions and 4 deletions

View File

@ -514,6 +514,9 @@ impl From<InvalidPoolTransactionError> for RpcPoolError {
InvalidPoolTransactionError::Underpriced => RpcPoolError::Underpriced,
InvalidPoolTransactionError::Other(err) => RpcPoolError::PoolTransactionError(err),
InvalidPoolTransactionError::Eip4844(err) => RpcPoolError::Eip4844(err),
InvalidPoolTransactionError::Overdraft => {
RpcPoolError::Invalid(RpcInvalidTransactionError::InsufficientFunds)
}
}
}
}

View File

@ -169,6 +169,9 @@ pub enum InvalidPoolTransactionError {
/// Thrown if the transaction's fee is below the minimum fee
#[error("transaction underpriced")]
Underpriced,
/// Thrown if the transaction's would require an account to be overdrawn
#[error("transaction overdraws from account")]
Overdraft,
/// Eip-4844 related errors
#[error(transparent)]
Eip4844(#[from] Eip4844PoolTransactionError),
@ -228,6 +231,7 @@ impl InvalidPoolTransactionError {
// local setting
false
}
InvalidPoolTransactionError::Overdraft => false,
InvalidPoolTransactionError::Other(err) => err.is_bad_transaction(),
InvalidPoolTransactionError::Eip4844(eip4844_err) => {
match eip4844_err {

View File

@ -1,7 +1,7 @@
//! The internal transaction pool implementation.
use crate::{
config::TXPOOL_MAX_ACCOUNT_SLOTS_PER_SENDER,
error::{InvalidPoolTransactionError, PoolError},
error::{Eip4844PoolTransactionError, InvalidPoolTransactionError, PoolError},
identifier::{SenderId, TransactionId},
metrics::TxPoolMetrics,
pool::{
@ -396,10 +396,10 @@ impl<T: TransactionOrdering> TxPool<T> {
Ok(res)
}
Err(e) => {
Err(err) => {
// Update invalid transactions metric
self.metrics.invalid_transactions.increment(1);
match e {
match err {
InsertErr::Underpriced { existing, transaction: _ } => {
Err(PoolError::ReplacementUnderpriced(existing))
}
@ -420,6 +420,16 @@ impl<T: TransactionOrdering> TxPool<T> {
*transaction.hash(),
InvalidPoolTransactionError::ExceedsGasLimit(block_gas_limit, tx_gas_limit),
)),
InsertErr::BlobTxHasNonceGap { transaction } => {
Err(PoolError::InvalidTransaction(
*transaction.hash(),
Eip4844PoolTransactionError::Eip4844NonceGap.into(),
))
}
InsertErr::Overdraft { transaction } => Err(PoolError::InvalidTransaction(
*transaction.hash(),
InvalidPoolTransactionError::Overdraft,
)),
}
}
}
@ -1065,6 +1075,39 @@ impl<T: PoolTransaction> AllTransactions<T> {
Ok(transaction)
}
/// Enforces additional constraints for blob transactions before attempting to insert:
/// - new blob transactions must not have any nonce gaps
/// - blob transactions cannot go into overdraft
fn ensure_valid_blob_transaction(
&self,
transaction: ValidPoolTransaction<T>,
on_chain_balance: U256,
ancestor: Option<TransactionId>,
) -> Result<ValidPoolTransaction<T>, InsertErr<T>> {
if let Some(ancestor) = ancestor {
let Some(tx) = self.txs.get(&ancestor) else {
// ancestor tx is missing, so we can't insert the new blob
return Err(InsertErr::BlobTxHasNonceGap { transaction: Arc::new(transaction) })
};
if tx.state.has_nonce_gap() {
// the ancestor transaction already has a nonce gap, so we can't insert the new
// blob
return Err(InsertErr::BlobTxHasNonceGap { transaction: Arc::new(transaction) })
}
// check if the new blob would go into overdraft
if tx.next_cumulative_cost() + transaction.cost() > on_chain_balance {
// the transaction would go into overdraft
return Err(InsertErr::Overdraft { transaction: Arc::new(transaction) })
}
} else if transaction.cost() > on_chain_balance {
// the transaction would go into overdraft
return Err(InsertErr::Overdraft { transaction: Arc::new(transaction) })
}
Ok(transaction)
}
/// Returns true if the replacement candidate is underpriced and can't replace the existing
/// transaction.
#[inline]
@ -1122,6 +1165,10 @@ impl<T: PoolTransaction> AllTransactions<T> {
/// These can include:
/// - closing nonce gaps of descendant transactions
/// - enough balance updates
///
/// Note: For EIP-4844 blob transactions additional constraints are enforced:
/// - new blob transactions must not have any nonce gaps
/// - blob transactions cannot go into overdraft
pub(crate) fn insert_tx(
&mut self,
transaction: ValidPoolTransaction<T>,
@ -1130,18 +1177,29 @@ impl<T: PoolTransaction> AllTransactions<T> {
) -> InsertResult<T> {
assert!(on_chain_nonce <= transaction.nonce(), "Invalid transaction");
let transaction = Arc::new(self.ensure_valid(transaction)?);
let mut transaction = self.ensure_valid(transaction)?;
let inserted_tx_id = *transaction.id();
let mut state = TxState::default();
let mut cumulative_cost = U256::ZERO;
let mut updates = Vec::new();
// identifier of the ancestor transaction, will be None if the transaction is the next tx of
// the sender
let ancestor = TransactionId::ancestor(
transaction.transaction.nonce(),
on_chain_nonce,
inserted_tx_id.sender,
);
// before attempting to insert a blob transaction, we need to ensure that additional
// constraints are met
if transaction.is_eip4844() {
transaction =
self.ensure_valid_blob_transaction(transaction, on_chain_balance, ancestor)?;
}
let transaction = Arc::new(transaction);
// If there's no ancestor tx then this is the next transaction.
if ancestor.is_none() {
state.insert(TxState::NO_NONCE_GAPS);
@ -1341,6 +1399,11 @@ pub(crate) enum InsertErr<T: PoolTransaction> {
transaction: Arc<ValidPoolTransaction<T>>,
existing: TxHash,
},
/// Attempted to insert a blob transaction with a nonce gap
BlobTxHasNonceGap { transaction: Arc<ValidPoolTransaction<T>> },
/// Attempted to insert a transaction that would overdraft the sender's balance at the time of
/// insertion.
Overdraft { transaction: Arc<ValidPoolTransaction<T>> },
/// The transactions feeCap is lower than the chain's minimum fee requirement.
///
/// See also [`MIN_PROTOCOL_BASE_FEE`]

View File

@ -285,6 +285,12 @@ impl<T: PoolTransaction> ValidPoolTransaction<T> {
self.origin.is_local()
}
/// Whether the transaction is an EIP-4844 blob transaction.
#[inline]
pub fn is_eip4844(&self) -> bool {
self.transaction.is_eip4844()
}
/// The heap allocated size of this transaction.
pub(crate) fn size(&self) -> usize {
self.transaction.size()