<T>, scope: &'static str) -> Self {
        Self { receiver, metrics: MeteredReceiverMetrics::new(scope) }
    }

    /// Receives the next value for this receiver.
    pub async fn recv(&mut self) -> Option<T> {
        let msg = self.receiver.recv().await;
        if msg.is_some() {
            self.metrics.messages_received_total.increment(1);
        }
        msg
    }

    /// Tries to receive the next value for this receiver.
    pub fn try_recv(&mut self) -> Result<T, TryRecvError> {
        let msg = self.receiver.try_recv()?;
        self.metrics.messages_received_total.increment(1);
        Ok(msg)
    }

    /// Closes the receiving half of a channel without dropping it.
    pub fn close(&mut self) {
        self.receiver.close();
    }

    /// Polls to receive the next message on this channel.
    pub fn poll_recv(&mut self, cx: &mut Context<'_>) -> Poll<Option<T>> {
        let msg = ready!(self.receiver.poll_recv(cx));
        if msg.is_some() {
            self.metrics.messages_received_total.increment(1);
        }
        Poll::Ready(msg)
    }
}

impl<T> Stream for MeteredReceiver<T> {
    type Item = T;

    fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
        self.poll_recv(cx)
    }
}

/// Throughput metrics for [`MeteredSender`]
#[derive(Clone, Metrics)]
#[metrics(dynamic = true)]
pub struct MeteredSenderMetrics {
    /// Number of messages sent
    messages_sent_total: Counter,
    /// Number of failed message deliveries
    send_errors_total: Counter,
}

/// Throughput metrics for [`MeteredReceiver`]
#[derive(Clone, Metrics)]
#[metrics(dynamic = true)]
struct MeteredReceiverMetrics {
    /// Number of messages received
    messages_received_total: Counter,
}

/// A wrapper type around [`PollSender`] that updates metrics on send.
#[derive(Debug)]
pub struct MeteredPollSender<T> {
    /// The [`PollSender`] that this wraps around.
    sender: PollSender<T>,
    /// Holds metrics for this type.
    metrics: MeteredPollSenderMetrics,
}

impl<T: Send + 'static> MeteredPollSender<T> {
    /// Creates a new [`MeteredPollSender`] wrapping around the provided [`PollSender`].
    pub fn new(sender: PollSender<T>, scope: &'static str) -> Self {
        Self { sender, metrics: MeteredPollSenderMetrics::new(scope) }
    }

    /// Returns the underlying [`PollSender`].
    pub const fn inner(&self) -> &PollSender<T> {
        &self.sender
    }

    /// Calls the underlying [`PollSender`]'s `poll_reserve`, incrementing the appropriate
    /// metrics depending on the result.
    pub fn poll_reserve(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), PollSendError<T>>> {
        match self.sender.poll_reserve(cx) {
            Poll::Ready(Ok(permit)) => Poll::Ready(Ok(permit)),
            Poll::Ready(Err(error)) => Poll::Ready(Err(error)),
            Poll::Pending => {
                self.metrics.back_pressure_total.increment(1);
                Poll::Pending
            }
        }
    }

    /// Calls the underlying [`PollSender`]'s `send_item`, incrementing the appropriate
    /// metrics depending on the result.
    pub fn send_item(&mut self, item: T) -> Result<(), PollSendError<T>> {
        match self.sender.send_item(item) {
            Ok(()) => {
                self.metrics.messages_sent_total.increment(1);
                Ok(())
            }
            Err(error) => Err(error),
        }
    }
}

impl<T> Clone for MeteredPollSender<T> {
    fn clone(&self) -> Self {
        Self { sender: self.sender.clone(), metrics: self.metrics.clone() }
    }
}

/// Throughput metrics for [`MeteredPollSender`]
#[derive(Clone, Metrics)]
#[metrics(dynamic = true)]
struct MeteredPollSenderMetrics {
    /// Number of messages sent
    messages_sent_total: Counter,
    /// Number of delayed message deliveries caused by a full channel
    back_pressure_total: Counter,
}

/// Shared state for tracking memory budget across sender and receiver.
///
/// `used` is a pure accounting counter — it does not gate access to any other
/// shared memory, so all operations on it use [`Ordering::Relaxed`]. Cross-thread
/// publication of message contents is handled by the underlying mpsc channel.
#[derive(Debug)]
struct MemoryBudget {
    /// Current number of bytes used by buffered messages.
    used: AtomicUsize,
    /// Maximum allowed bytes.
    max_bytes: usize,
}

/// Guard that releases memory budget when dropped.
///
/// Holds the size of the message and a reference to the shared budget counter.
/// When dropped, it atomically decreases the used counter.
#[derive(Debug)]
struct BudgetGuard {
    size: usize,
    budget: Arc<MemoryBudget>,
}

impl Drop for BudgetGuard {
    fn drop(&mut self) {
        self.budget.used.fetch_sub(self.size, Ordering::Relaxed);
    }
}

/// Message envelope that holds the memory budget while the message sits in the channel.
///
/// The guard is dropped (releasing the budget) as soon as the receiver dequeues
/// the message via [`MemoryBoundedReceiver::recv`] / [`MemoryBoundedReceiver::poll_recv`],
/// so the budget tracks bytes *currently in the channel queue*, not bytes in flight
/// downstream of the receiver.
#[derive(Debug)]
struct Budgeted<T> {
    msg: T,
    _guard: BudgetGuard,
}

/// A sender that enforces a byte budget before enqueueing messages.
///
/// Uses a shared atomic counter to track memory usage. Each message's size is added
/// to the counter on send and subtracted when the message is dequeued by the receiver.
///
/// The current call sites (specifically [`crate::common::mpsc::MemoryBoundedSender`] used
/// for the `NetworkManager → TransactionsManager` channel) have a single producer driven
/// from a single `poll`, so the `fetch_add → check → fetch_sub-on-overflow` reservation
/// pattern can never race with itself. The atomic is still used so the receiver can
/// release budget from a different task.
#[derive(Debug, Clone)]
pub struct MemoryBoundedSender<T: InMemorySize> {
    /// The underlying unbounded metered sender
    inner: UnboundedMeteredSender<Budgeted<T>>,
    /// Shared memory budget tracker
    budget: Arc<MemoryBudget>,
}

impl<T: InMemorySize> MemoryBoundedSender<T> {
    /// Tries to send a message if there is sufficient budget.
    ///
    /// Returns `TrySendError::Full` if insufficient budget is available.
    pub fn try_send(&self, msg: T) -> Result<(), TrySendError<T>> {
        let size = msg.size();

        // Reserve budget: add first, check after
        let prev = self.budget.used.fetch_add(size, Ordering::Relaxed);
        if prev.saturating_add(size) > self.budget.max_bytes {
            // Over budget, undo
            self.budget.used.fetch_sub(size, Ordering::Relaxed);
            return Err(TrySendError::Full(msg));
        }

        let guard = BudgetGuard { size, budget: Arc::clone(&self.budget) };
        let budgeted = Budgeted { msg, _guard: guard };

        self.inner.send(budgeted).map_err(|e| {
            // Guard will be dropped here, releasing the budget
            TrySendError::Closed(e.0.msg)
        })
    }
}

/// A receiver for memory-bounded messages.
///
/// On receive, the budget reserved for the message is released immediately and the
/// inner `T` is yielded — callers do not need to opt into any wrapper type.
#[derive(Debug)]
pub struct MemoryBoundedReceiver<T> {
    /// The underlying unbounded metered receiver
    inner: UnboundedMeteredReceiver<Budgeted<T>>,
}

impl<T> MemoryBoundedReceiver<T> {
    /// Receives the next message, returning `None` if the channel is closed.
    ///
    /// Releases the message's reserved budget before returning.
    pub async fn recv(&mut self) -> Option<T> {
        self.inner.recv().await.map(unwrap_budgeted)
    }

    /// Polls to receive the next message on this channel.
    ///
    /// Releases the message's reserved budget before returning.
    pub fn poll_recv(&mut self, cx: &mut Context<'_>) -> Poll<Option<T>>

{
        self.inner.poll_recv(cx).map(|opt| opt.map(unwrap_budgeted))
    }


bug)]
struct MemoryBudget {
    /// Current number of bytes used by buffered messages.
    used: AtomicUsize,
    /// Maximum allowed bytes.
    max_bytes: usize,
}

/// Guard that releases memory budget when dropped.
///
/// Holds the size of the message and a reference to the shared budget counter.
/// When dropped, it atomically decreases the used counter.
#[derive(Debug)]
struct BudgetGuard {
    size: usize,
    budget: Arc<MemoryBudget>,
}

impl Drop for BudgetGuard {
    fn drop(&mut self) {
        self.budget.used.fetch_sub(self.size, Ordering::Relaxed);
    }
}

/// Message envelope that holds the memory budget while the message sits in the channel.
///
/// The guard is dropped (releasing the budget) as soon as the receiver dequeues
/// the message via [`MemoryBoundedReceiver::recv`] / [`MemoryBoundedReceiver::poll_recv`],
/// so the budget tracks bytes *currently in the channel queue*, not bytes in flight
/// downstream of the receiver.
#[derive(Debug)]
struct Budgeted<T> {
    msg: T,
    _guard: BudgetGuard,
}

/// A sender that enforces a byte budget before enqueueing messages.
///
/// Uses a shared atomic counter to track memory usage. Each message's size is added
/// to the counter on send and subtracted when the message is dequeued by the receiver.
///
/// The current call sites (specifically [`crate::common::mpsc::MemoryBoundedSender`] used
/// for the `NetworkManager → TransactionsManager` channel) have a single producer driven
/// from a single `poll`, so the `fetch_add → check → fetch_sub-on-overflow` reservation
/// pattern can never race with itself. The atomic is still used so the receiver can
/// release budget from a different task.
#[derive(Debug, Clone)]
pub struct MemoryBoundedSender<T: InMemorySize> {
    /// The underlying unbounded metered sender
    inner: UnboundedMeteredSender<Budgeted<T>>,
    /// Shared memory budget tracker
    budget: Arc<MemoryBudget>,
}

impl<T: InMemorySize> MemoryBoundedSender<T> {
    /// Tries to send a message if there is sufficient budget.
    ///
    /// Returns `TrySendError::Full` if insufficient budget is available.
    pub fn try_send(&self, msg: T) -> Result<(), TrySendError<T>> {
        let size = msg.size();

        // Reserve budget: add first, check after
        let prev = self.budget.used.fetch_add(size, Ordering::Relaxed);
        if prev.saturating_add(size) > self.budget.max_bytes {
            // Over budget, undo
            self.budget.used.fetch_sub(size, Ordering::Relaxed);
            return Err(TrySendError::Full(msg));
        }

        let guard = BudgetGuard { size, budget: Arc::clone(&self.budget) };
        let budgeted = Budgeted { msg, _guard: guard };

        self.inner.send(budgeted).map_err(|e| {
            // Guard will be dropped here, releasing the budget
            TrySendError::Closed(e.0.msg)
        })
    }
}

/// A receiver for memory-bounded messages.
///
/// On receive, the budget reserved for the message is released immediately and the
/// inner `T` is yielded — callers do not need to opt into any wrapper type.
#[derive(Debug)]
pub struct MemoryBoundedReceiver<T> {
    /// The underlying unbounded metered receiver
    inner: UnboundedMeteredReceiver<Budgeted<T>>,
}

impl<T> MemoryBoundedReceiver<T> {
    /// Receives the next message, returning `None` if the channel is closed.
    ///
    /// Releases the message's reserved budget before returning.
    pub async fn recv(&mut self) -> Option<T> {
        self.inner.recv().await.map(unwrap_budgeted)
    }

    /// Polls to receive the next message on this channel.
    ///
    /// Releases the message's reserved budget before returning.
    pub fn poll_recv(&mut self, cx: &mut Context<'_>) -> Poll<Option<T>> {
        self.inner.poll_recv(cx).map(|opt| opt.map(unwrap_budgeted))
    }
}

/// Releases the budget guard and returns the inner message.

fn unwrap_budgeted<T>(b: Budgeted<T>) -> T {
    // Destructuring binds `_guard` so it is dropped when this function returns,
    // which runs `BudgetGuard::drop` and releases the reserved bytes.
    let Budgeted { msg, _guard } = b;
    msg
}

impl<T> Stream for MemoryBoundedReceiver<T> {
    type Item = T;

    fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
        self.poll_recv(cx)
    }
}

/// Creates a new memory-bounded channel with the given byte budget.
///
/// The budget tracks bytes currently buffered in the channel; it is reserved on
/// [`MemoryBoundedSender::try_send`] and released as soon as the receiver dequeues
/// the message.
pub fn memory_bounded_channel<T: InMemorySize>(
    max_bytes: usize,
    scope: &'static str,
) -> (MemoryBoundedSender<T>, MemoryBoundedReceiver<T>) {
    let (tx, rx) = metered_unbounded_channel(scope);
    let budget = Arc::new(MemoryBudget { used: AtomicUsize::new(0), max_bytes });

    let sender = MemoryBoundedSender { inner: tx, budget };
    let receiver = MemoryBoundedReceiver { inner: rx };

    (sender, receiver)
}


cv().await;
        if msg.is_some() {
            self.metrics.messages_received_total.increment(1);
        }
        msg
    }

    /// Tries to receive the next value for this receiver.
    pub fn try_recv(&mut self) -> Result<T, TryRecvError> {
        let msg = self.receiver.try_recv()?;
        self.metrics.messages_received_total.increment(1);
        Ok(msg)
    }

    /// Closes the receiving half of a channel without dropping it.
    pub fn close(&mut self) {
        self.receiver.close();
    }

    /// Polls to receive the next message on this channel.
    pub fn poll_recv(&mut self, cx: &mut Context<'_>) -> Poll<Option<T>> {
        let msg = ready!(self.receiver.poll_recv(cx));
        if msg.is_some() {
            self.metrics.messages_received_total.increment(1);
        }
        Poll::Ready(msg)
    }
}

impl<T> Stream for MeteredReceiver<T> {
    type Item = T;

    fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
        self.poll_recv(cx)
    }
}

/// Throughput metrics for [`MeteredSender`]
#[derive(Clone, Metrics)]
#[metrics(dynamic = true)]
pub struct MeteredSenderMetrics {
    /// Number of messages sent
    messages_sent_total: Counter,
    /// Number of failed message deliveries
    send_errors_total: Counter,
}

/// Throughput metrics for [`MeteredReceiver`]
#[derive(Clone, Metrics)]
#[metrics(dynamic = true)]
struct MeteredReceiverMetrics {
    /// Number of messages received
    messages_received_total: Counter,
}

/// A wrapper type around [`PollSender`] that updates metrics on send.
#[derive(Debug)]
pub struct MeteredPollSender<T> {
    /// The [`PollSender`] that this wraps around.
    sender: PollSender<T>,
    /// Holds metrics for this type.
    metrics: MeteredPollSenderMetrics,
}

impl<T: Send + 'static> MeteredPollSender<T> {
    /// Creates a new [`MeteredPollSender`] wrapping around the provided [`PollSender`].
    pub fn new(sender: PollSender<T>, scope: &'static str) -> Self {
        Self { sender, metrics: MeteredPollSenderMetrics::new(scope) }
    }

    /// Returns the underlying [`PollSender`].
    pub const fn inner(&self) -> &PollSender<T> {
        &self.sender
    }

    /// Calls the underlying [`PollSender`]'s `poll_reserve`, incrementing the appropriate
    /// metrics depending on the result.
    pub fn poll_reserve(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), PollSendError<T>>> {
        match self.sender.poll_reserve(cx) {
            Poll::Ready(Ok(permit)) => Poll::Ready(Ok(permit)),
            Poll::Ready(Err(error)) => Poll::Ready(Err(error)),
            Poll::Pending => {
                self.metrics.back_pressure_total.increment(1);
                Poll::Pending
            }
        }
    }

    /// Calls the underlying [`PollSender`]'s `send_item`, incrementing the appropriate
    /// metrics depending on the result.
    pub fn send_item(&mut self, item: T) -> Result<(), PollSendError<T>> {
        match self.sender.send_item(item) {
            Ok(()) => {
                self.metrics.messages_sent_total.increment(1);
                Ok(())
            }
            Err(error) => Err(error),
        }
    }
}

impl<T> Clone for MeteredPollSender<T> {
    fn clone(&self) -> Self {
        Self { sender: self.sender.clone(), metrics: self.metrics.clone() }
    }
}

/// Throughput metrics for [`MeteredPollSender`]
#[derive(Clone, Metrics)]
#[metrics(dynamic = true)]
struct MeteredPollSenderMetrics {
    /// Number of messages sent
    messages_sent_total: Counter,
    /// Number of delayed message deliveries caused by a full channel
    back_pressure_total: Counter,
}

/// Shared state for tracking memory budget across sender and receiver.
///
/// `used` is a pure accounting counter — it does not gate access to any other
/// shared memory, so all operations on it use [`Ordering::Relaxed`]. Cross-thread
/// publication of message contents is handled by the underlying mpsc channel.
#[derive(Debug)]
struct MemoryBudget {
    /// Current number of bytes used by buffered messages.
    used: AtomicUsize,
    /// Maximum allowed bytes.
    max_bytes: usize,
}

/// Guard that releases memory budget when dropped.
///
/// Holds the size of the message and a reference to the shared budget counter.
/// When dropped, it atomically decreases the used counter.
#[derive(Debug)]
struct BudgetGuard {
    size: usize,
    budget: Arc<MemoryBudget>,
}

impl Drop for BudgetGuard {
    fn drop(&mut self) {
        self.budget.used.fetch_sub(self.size, Ordering::Relaxed);
    }
}

/// Message envelope that holds the memory budget while the message sits in the channel.
///
/// The guard is dropped (releasing the budget) as soon as the receiver dequeues
/// the message via [`MemoryBoundedReceiver::recv`] / [`MemoryBoundedReceiver::poll_recv`],
/// so the budget tracks bytes *currently in the channel queue*, not bytes in flight
/// downstream of the receiver.
#[derive(Debug)]
struct Budgeted<T> {
    msg: T,
    _guard: BudgetGuard,
}

/// A sender that enforces a byte budget before enqueueing messages.
///
/// Uses a shared atomic counter to track memory usage. Each message's size is added
/// to the counter on send and subtracted when the message is dequeued by the receiver.
///
/// The current call sites (specifically [`crate::common::mpsc::MemoryBoundedSender`] used
/// for the `NetworkManager → TransactionsManager` channel) have a single producer driven
/// from a single `poll`, so the `fetch_add → check → fetch_sub-on-overflow` reservation
/// pattern can never race with itself. The atomic is still used so the receiver can
/// release budget from a different task.
#[derive(Debug, Clone)]
pub struct MemoryBoundedSender<T: InMemorySize> {
    /// The underlying unbounded metered sender
    inner: UnboundedMeteredSender<Budgeted<T>>,
    /// Shared memory budget tracker
    budget: Arc<MemoryBudget>,
}

impl<T: InMemorySize> MemoryBoundedSender<T> {
    /// Tries to send a message if there is sufficient budget.
    ///
    /// Returns `TrySendError::Full` if insufficient budget is available.
    pub fn try_send(&self, msg: T) -> Result<(), TrySendError<T>> {
        let size = msg.size();

        // Reserve budget: add first, check after
        let prev = self.budget.used.fetch_add(size, Ordering::Relaxed);
        if prev.saturating_add(size) > self.budget.max_bytes {
            // Over budget, undo
            self.budget.used.fetch_sub(size, Ordering::Relaxed);
            return Err(TrySendError::Full(msg));
        }

        let guard = BudgetGuard { size, budget: Arc::clone(&self.budget) };
        let budgeted = Budgeted { msg, _guard: guard };

        self.inner.send(budgeted).map_err(|e| {
            // Guard will be dropped here, releasing the budget
            TrySendError::Closed(e.0.msg)
        })
    }
}

/// A receiver for memory-bounded messages.
///
/// On receive, the budget reserved for the message is released immediately and the
/// inner `T` is yielded — callers do not need to opt into any wrapper type.
#[derive(Debug)]
pub struct MemoryBoundedReceiver<T> {
    /// The underlying unbounded metered receiver
    inner: UnboundedMeteredReceiver<Budgeted<T>>,
}

impl<T> MemoryBoundedReceiver<T> {
    /// Receives the next message, returning `None` if the channel is closed.
    ///
    /// Releases the message's reserved budget before returning.
    pub async fn recv(&mut self) -> Option<T> {
        self.inner.recv().await.map(unwrap_budgeted)
    }

    /// Polls to receive the next message on this channel.
    ///
    /// Releases the message's reserved budget before returning.
    pub fn poll_recv(&mut self, cx: &mut Context<'_>) -> Poll<Option<T>> {
        self.inner.poll_recv(cx).map(|opt| opt.map(unwrap_budgeted))
    }
}

/// Releases the budget guard and returns the inner message.
fn unwrap_budgeted<T>(b: Budgeted<T>) -> T

{
    // Destructuring binds `_guard` so it is dropped when this function returns,
    // which runs `BudgetGuard::drop` and releases the reserved bytes.
    let Budgeted { msg, _guard } = b;
    msg
}


/// Holds the size of the message and a reference to the shared budget counter.
/// When dropped, it atomically decreases the used counter.
#[derive(Debug)]
struct BudgetGuard {
    size: usize,
    budget: Arc<MemoryBudget>,
}

impl Drop for BudgetGuard {
    fn drop(&mut self) {
        self.budget.used.fetch_sub(self.size, Ordering::Relaxed);
    }
}

/// Message envelope that holds the memory budget while the message sits in the channel.
///
/// The guard is dropped (releasing the budget) as soon as the receiver dequeues
/// the message via [`MemoryBoundedReceiver::recv`] / [`MemoryBoundedReceiver::poll_recv`],
/// so the budget tracks bytes *currently in the channel queue*, not bytes in flight
/// downstream of the receiver.
#[derive(Debug)]
struct Budgeted<T> {
    msg: T,
    _guard: BudgetGuard,
}

/// A sender that enforces a byte budget before enqueueing messages.
///
/// Uses a shared atomic counter to track memory usage. Each message's size is added
/// to the counter on send and subtracted when the message is dequeued by the receiver.
///
/// The current call sites (specifically [`crate::common::mpsc::MemoryBoundedSender`] used
/// for the `NetworkManager → TransactionsManager` channel) have a single producer driven
/// from a single `poll`, so the `fetch_add → check → fetch_sub-on-overflow` reservation
/// pattern can never race with itself. The atomic is still used so the receiver can
/// release budget from a different task.
#[derive(Debug, Clone)]
pub struct MemoryBoundedSender<T: InMemorySize> {
    /// The underlying unbounded metered sender
    inner: UnboundedMeteredSender<Budgeted<T>>,
    /// Shared memory budget tracker
    budget: Arc<MemoryBudget>,
}

impl<T: InMemorySize> MemoryBoundedSender<T> {
    /// Tries to send a message if there is sufficient budget.
    ///
    /// Returns `TrySendError::Full` if insufficient budget is available.
    pub fn try_send(&self, msg: T) -> Result<(), TrySendError<T>> {
        let size = msg.size();

        // Reserve budget: add first, check after
        let prev = self.budget.used.fetch_add(size, Ordering::Relaxed);
        if prev.saturating_add(size) > self.budget.max_bytes {
            // Over budget, undo
            self.budget.used.fetch_sub(size, Ordering::Relaxed);
            return Err(TrySendError::Full(msg));
        }

        let guard = BudgetGuard { size, budget: Arc::clone(&self.budget) };
        let budgeted = Budgeted { msg, _guard: guard };

        self.inner.send(budgeted).map_err(|e| {
            // Guard will be dropped here, releasing the budget
            TrySendError::Closed(e.0.msg)
        })
    }
}

/// A receiver for memory-bounded messages.
///
/// On receive, the budget reserved for the message is released immediately and the
/// inner `T` is yielded — callers do not need to opt into any wrapper type.
#[derive(Debug)]
pub struct MemoryBoundedReceiver<T> {
    /// The underlying unbounded metered receiver
    inner: UnboundedMeteredReceiver<Budgeted<T>>,
}

impl<T> MemoryBoundedReceiver<T> {
    /// Receives the next message, returning `None` if the channel is closed.
    ///
    /// Releases the message's reserved budget before returning.
    pub async fn recv(&mut self) -> Option<T> {
        self.inner.recv().await.map(unwrap_budgeted)
    }

    /// Polls to receive the next message on this channel.
    ///
    /// Releases the message's reserved budget before returning.
    pub fn poll_recv(&mut self, cx: &mut Context<'_>) -> Poll<Option<T>> {
        self.inner.poll_recv(cx).map(|opt| opt.map(unwrap_budgeted))
    }
}

/// Releases the budget guard and returns the inner message.
fn unwrap_budgeted<T>(b: Budgeted<T>) -> T {
    // Destructuring binds `_guard` so it is dropped when this function returns,
    // which runs `BudgetGuard::drop` and releases the reserved bytes.
    let Budgeted { msg, _guard } = b;
    msg
}

impl<T> Stream for MemoryBoundedReceiver<T> {
    type Item = T;

    fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
        self.poll_recv(cx)
    }
}

/// Creates a new memory-bounded channel with the given byte budget.
///
/// The budget tracks bytes currently buffered in the channel; it is reserved on
/// [`MemoryBoundedSender::try_send`] and released as soon as the receiver dequeues
/// the message.
pub fn memory_bounded_channel<T: InMemorySize>(
    max_bytes: usize,
    scope: &'static str,
) -> (MemoryBoundedSender<T>, MemoryBoundedReceiver<T>) {
    let (tx, rx) = metered_unbounded_channel(scope);
    let budget = Arc::new(MemoryBudget { used: AtomicUsize::new(0), max_bytes });

    let sender = MemoryBoundedSender { inner: tx, budget };
    let receiver = MemoryBoundedReceiver { inner: rx };

    (sender, receiver)
}


, TryRecvError> {
        let msg = self.receiver.try_recv()?;
        self.metrics.messages_received_total.increment(1);
        Ok(msg)
    }

    /// Closes the receiving half of a channel without dropping it.
    pub fn close(&mut self) {
        self.receiver.close();
    }

    /// Polls to receive the next message on this channel.
    pub fn poll_recv(&mut self, cx: &mut Context<'_>) -> Poll<Option<T>> {
        let msg = ready!(self.receiver.poll_recv(cx));
        if msg.is_some() {
            self.metrics.messages_received_total.increment(1);
        }
        Poll::Ready(msg)
    }
}

impl<T> Stream for MeteredReceiver<T> {
    type Item = T;

    fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
        self.poll_recv(cx)
    }
}

/// Throughput metrics for [`MeteredSender`]
#[derive(Clone, Metrics)]
#[metrics(dynamic = true)]
pub struct MeteredSenderMetrics {
    /// Number of messages sent
    messages_sent_total: Counter,
    /// Number of failed message deliveries
    send_errors_total: Counter,
}

/// Throughput metrics for [`MeteredReceiver`]
#[derive(Clone, Metrics)]
#[metrics(dynamic = true)]
struct MeteredReceiverMetrics {
    /// Number of messages received
    messages_received_total: Counter,
}

/// A wrapper type around [`PollSender`] that updates metrics on send.
#[derive(Debug)]
pub struct MeteredPollSender<T> {
    /// The [`PollSender`] that this wraps around.
    sender: PollSender<T>,
    /// Holds metrics for this type.
    metrics: MeteredPollSenderMetrics,
}

impl<T: Send + 'static> MeteredPollSender<T> {
    /// Creates a new [`MeteredPollSender`] wrapping around the provided [`PollSender`].
    pub fn new(sender: PollSender<T>, scope: &'static str) -> Self {
        Self { sender, metrics: MeteredPollSenderMetrics::new(scope) }
    }

    /// Returns the underlying [`PollSender`].
    pub const fn inner(&self) -> &PollSender<T> {
        &self.sender
    }

    /// Calls the underlying [`PollSender`]'s `poll_reserve`, incrementing the appropriate
    /// metrics depending on the result.
    pub fn poll_reserve(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), PollSendError<T>>> {
        match self.sender.poll_reserve(cx) {
            Poll::Ready(Ok(permit)) => Poll::Ready(Ok(permit)),
            Poll::Ready(Err(error)) => Poll::Ready(Err(error)),
            Poll::Pending => {
                self.metrics.back_pressure_total.increment(1);
                Poll::Pending
            }
        }
    }

    /// Calls the underlying [`PollSender`]'s `send_item`, incrementing the appropriate
    /// metrics depending on the result.
    pub fn send_item(&mut self, item: T) -> Result<(), PollSendError<T>> {
        match self.sender.send_item(item) {
            Ok(()) => {
                self.metrics.messages_sent_total.increment(1);
                Ok(())
            }
            Err(error) => Err(error),
        }
    }
}

impl<T> Clone for MeteredPollSender<T> {
    fn clone(&self) -> Self {
        Self { sender: self.sender.clone(), metrics: self.metrics.clone() }
    }
}

/// Throughput metrics for [`MeteredPollSender`]
#[derive(Clone, Metrics)]
#[metrics(dynamic = true)]
struct MeteredPollSenderMetrics {
    /// Number of messages sent
    messages_sent_total: Counter,
    /// Number of delayed message deliveries caused by a full channel
    back_pressure_total: Counter,
}

/// Shared state for tracking memory budget across sender and receiver.
///
/// `used` is a pure accounting counter — it does not gate access to any other
/// shared memory, so all operations on it use [`Ordering::Relaxed`]. Cross-thread
/// publication of message contents is handled by the underlying mpsc channel.
#[derive(Debug)]
struct MemoryBudget {
    /// Current number of bytes used by buffered messages.
    used: AtomicUsize,
    /// Maximum allowed bytes.
    max_bytes: usize,
}

/// Guard that releases memory budget when dropped.
///
/// Holds the size of the message and a reference to the shared budget counter.
/// When dropped, it atomically decreases the used counter.
#[derive(Debug)]
struct BudgetGuard {
    size: usize,
    budget: Arc<MemoryBudget>,
}

impl Drop for BudgetGuard {
    fn drop(&mut self) {
        self.budget.used.fetch_sub(self.size, Ordering::Relaxed);
    }
}

/// Message envelope that holds the memory budget while the message sits in the channel.
///
/// The guard is dropped (releasing the budget) as soon as the receiver dequeues
/// the message via [`MemoryBoundedReceiver::recv`] / [`MemoryBoundedReceiver::poll_recv`],
/// so the budget tracks bytes *currently in the channel queue*, not bytes in flight
/// downstream of the receiver.
#[derive(Debug)]
struct Budgeted<T> {
    msg: T,
    _guard: BudgetGuard,
}

/// A sender that enforces a byte budget before enqueueing messages.
///
/// Uses a shared atomic counter to track memory usage. Each message's size is added
/// to the counter on send and subtracted when the message is dequeued by the receiver.
///
/// The current call sites (specifically [`crate::common::mpsc::MemoryBoundedSender`] used
/// for the `NetworkManager → TransactionsManager` channel) have a single producer driven
/// from a single `poll`, so the `fetch_add → check → fetch_sub-on-overflow` reservation
/// pattern can never race with itself. The atomic is still used so the receiver can
/// release budget from a different task.
#[derive(Debug, Clone)]
pub struct MemoryBoundedSender<T: InMemorySize> {
    /// The underlying unbounded metered sender
    inner: UnboundedMeteredSender<Budgeted<T>>,
    /// Shared memory budget tracker
    budget: Arc<MemoryBudget>,
}

impl<T: InMemorySize> MemoryBoundedSender<T> {
    /// Tries to send a message if there is sufficient budget.
    ///
    /// Returns `TrySendError::Full` if insufficient budget is available.
    pub fn try_send(&self, msg: T) -> Result<(), TrySendError<T>> {
        let size = msg.size();

        // Reserve budget: add first, check after
        let prev = self.budget.used.fetch_add(size, Ordering::Relaxed);
        if prev.saturating_add(size) > self.budget.max_bytes {
            // Over budget, undo
            self.budget.used.fetch_sub(size, Ordering::Relaxed);
            return Err(TrySendError::Full(msg));
        }

        let guard = BudgetGuard { size, budget: Arc::clone(&self.budget) };
        let budgeted = Budgeted { msg, _guard: guard };

        self.inner.send(budgeted).map_err(|e| {
            // Guard will be dropped here, releasing the budget
            TrySendError::Closed(e.0.msg)
        })
    }
}

/// A receiver for memory-bounded messages.
///
/// On receive, the budget reserved for the message is released immediately and the
/// inner `T` is yielded — callers do not need to opt into any wrapper type.
#[derive(Debug)]
pub struct MemoryBoundedReceiver<T> {
    /// The underlying unbounded metered receiver
    inner: UnboundedMeteredReceiver<Budgeted<T>>,
}

impl<T> MemoryBoundedReceiver<T> {
    /// Receives the next message, returning `None` if the channel is closed.
    ///
    /// Releases the message's reserved budget before returning.
    pub async fn recv(&mut self) -> Option<T> {
        self.inner.recv().await.map(unwrap_budgeted)
    }

    /// Polls to receive the next message on this channel.
    ///
    /// Releases the message's reserved budget before returning.
    pub fn poll_recv(&mut self, cx: &mut Context<'_>) -> Poll<Option<T>> {
        self.inner.poll_recv(cx).map(|opt| opt.map(unwrap_budgeted))
    }
}

/// Releases the budget guard and returns the inner message.
fn unwrap_budgeted<T>(b: Budgeted<T>) -> T {
    // Destructuring binds `_guard` so it is dropped when this function returns,
    // which runs `BudgetGuard::drop` and releases the reserved bytes.
    let Budgeted { msg, _guard } = b;
    msg
}

impl<T> Stream for MemoryBoundedReceiver<T> {
    type Item = T;

}

/// Creates a new memory-bounded channel with the given byte budget.
///
/// The budget tracks bytes currently buffered in the channel; it is reserved on
/// [`MemoryBoundedSender::try_send`] and released as soon as the receiver dequeues
/// the message.
pub fn memory_bounded_channel<T: InMemorySize>(
    max_bytes: usize,
    scope: &'static str,
) -> (MemoryBoundedSender<T>, MemoryBoundedReceiver<T>) {
    let (tx, rx) = metered_unbounded_channel(scope);
    let budget = Arc::new(MemoryBudget { used: AtomicUsize::new(0), max_bytes });

    let sender = MemoryBoundedSender { inner: tx, budget };
    let receiver = MemoryBoundedReceiver { inner: rx };

    (sender, receiver)
}


r.
/// When dropped, it atomically decreases the used counter.
#[derive(Debug)]
struct BudgetGuard {
    size: usize,
    budget: Arc<MemoryBudget>,
}

impl Drop for BudgetGuard {
    fn drop(&mut self) {
        self.budget.used.fetch_sub(self.size, Ordering::Relaxed);
    }
}

/// Message envelope that holds the memory budget while the message sits in the channel.
///
/// The guard is dropped (releasing the budget) as soon as the receiver dequeues
/// the message via [`MemoryBoundedReceiver::recv`] / [`MemoryBoundedReceiver::poll_recv`],
/// so the budget tracks bytes *currently in the channel queue*, not bytes in flight
/// downstream of the receiver.
#[derive(Debug)]
struct Budgeted<T> {
    msg: T,
    _guard: BudgetGuard,
}

/// A sender that enforces a byte budget before enqueueing messages.
///
/// Uses a shared atomic counter to track memory usage. Each message's size is added
/// to the counter on send and subtracted when the message is dequeued by the receiver.
///
/// The current call sites (specifically [`crate::common::mpsc::MemoryBoundedSender`] used
/// for the `NetworkManager → TransactionsManager` channel) have a single producer driven
/// from a single `poll`, so the `fetch_add → check → fetch_sub-on-overflow` reservation
/// pattern can never race with itself. The atomic is still used so the receiver can
/// release budget from a different task.
#[derive(Debug, Clone)]
pub struct MemoryBoundedSender<T: InMemorySize> {
    /// The underlying unbounded metered sender
    inner: UnboundedMeteredSender<Budgeted<T>>,
    /// Shared memory budget tracker
    budget: Arc<MemoryBudget>,
}

impl<T: InMemorySize> MemoryBoundedSender<T> {
    /// Tries to send a message if there is sufficient budget.
    ///
    /// Returns `TrySendError::Full` if insufficient budget is available.
    pub fn try_send(&self, msg: T) -> Result<(), TrySendError<T>> {
        let size = msg.size();

        // Reserve budget: add first, check after
        let prev = self.budget.used.fetch_add(size, Ordering::Relaxed);
        if prev.saturating_add(size) > self.budget.max_bytes {
            // Over budget, undo
            self.budget.used.fetch_sub(size, Ordering::Relaxed);
            return Err(TrySendError::Full(msg));
        }

        let guard = BudgetGuard { size, budget: Arc::clone(&self.budget) };
        let budgeted = Budgeted { msg, _guard: guard };

        self.inner.send(budgeted).map_err(|e| {
            // Guard will be dropped here, releasing the budget
            TrySendError::Closed(e.0.msg)
        })
    }
}

/// A receiver for memory-bounded messages.
///
/// On receive, the budget reserved for the message is released immediately and the
/// inner `T` is yielded — callers do not need to opt into any wrapper type.
#[derive(Debug)]
pub struct MemoryBoundedReceiver<T> {
    /// The underlying unbounded metered receiver
    inner: UnboundedMeteredReceiver<Budgeted<T>>,
}

impl<T> MemoryBoundedReceiver<T> {
    /// Receives the next message, returning `None` if the channel is closed.
    ///
    /// Releases the message's reserved budget before returning.
    pub async fn recv(&mut self) -> Option<T> {
        self.inner.recv().await.map(unwrap_budgeted)
    }

    /// Polls to receive the next message on this channel.
    ///
    /// Releases the message's reserved budget before returning.
    pub fn poll_recv(&mut self, cx: &mut Context<'_>) -> Poll<Option<T>> {
        self.inner.poll_recv(cx).map(|opt| opt.map(unwrap_budgeted))
    }
}

/// Releases the budget guard and returns the inner message.
fn unwrap_budgeted<T>(b: Budgeted<T>) -> T {
    // Destructuring binds `_guard` so it is dropped when this function returns,
    // which runs `BudgetGuard::drop` and releases the reserved bytes.
    let Budgeted { msg, _guard } = b;
    msg
}

impl<T> Stream for MemoryBoundedReceiver<T> {
    type Item = T;

    fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>>


d_total.increment(1);
        }
        Poll::Ready(msg)
    }
}

impl<T> Stream for MeteredReceiver<T> {
    type Item = T;

    fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
        self.poll_recv(cx)
    }
}

/// Throughput metrics for [`MeteredSender`]
#[derive(Clone, Metrics)]
#[metrics(dynamic = true)]
pub struct MeteredSenderMetrics {
    /// Number of messages sent
    messages_sent_total: Counter,
    /// Number of failed message deliveries
    send_errors_total: Counter,
}

/// Throughput metrics for [`MeteredReceiver`]
#[derive(Clone, Metrics)]
#[metrics(dynamic = true)]
struct MeteredReceiverMetrics {
    /// Number of messages received
    messages_received_total: Counter,
}

/// A wrapper type around [`PollSender`] that updates metrics on send.
#[derive(Debug)]
pub struct MeteredPollSender<T> {
    /// The [`PollSender`] that this wraps around.
    sender: PollSender<T>,
    /// Holds metrics for this type.
    metrics: MeteredPollSenderMetrics,
}

impl<T: Send + 'static> MeteredPollSender<T> {
    /// Creates a new [`MeteredPollSender`] wrapping around the provided [`PollSender`].
    pub fn new(sender: PollSender<T>, scope: &'static str) -> Self {
        Self { sender, metrics: MeteredPollSenderMetrics::new(scope) }
    }

    /// Returns the underlying [`PollSender`].
    pub const fn inner(&self) -> &PollSender<T> {
        &self.sender
    }

    /// Calls the underlying [`PollSender`]'s `poll_reserve`, incrementing the appropriate
    /// metrics depending on the result.
    pub fn poll_reserve(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), PollSendError<T>>> {
        match self.sender.poll_reserve(cx) {
            Poll::Ready(Ok(permit)) => Poll::Ready(Ok(permit)),
            Poll::Ready(Err(error)) => Poll::Ready(Err(error)),
            Poll::Pending => {
                self.metrics.back_pressure_total.increment(1);
                Poll::Pending
            }
        }
    }

    /// Calls the underlying [`PollSender`]'s `send_item`, incrementing the appropriate
    /// metrics depending on the result.
    pub fn send_item(&mut self, item: T) -> Result<(), PollSendError<T>> {
        match self.sender.send_item(item) {
            Ok(()) => {
                self.metrics.messages_sent_total.increment(1);
                Ok(())
            }
            Err(error) => Err(error),
        }
    }
}

impl<T> Clone for MeteredPollSender<T> {
    fn clone(&self) -> Self {
        Self { sender: self.sender.clone(), metrics: self.metrics.clone() }
    }
}

/// Throughput metrics for [`MeteredPollSender`]
#[derive(Clone, Metrics)]
#[metrics(dynamic = true)]
struct MeteredPollSenderMetrics {
    /// Number of messages sent
    messages_sent_total: Counter,
    /// Number of delayed message deliveries caused by a full channel
    back_pressure_total: Counter,
}

/// Shared state for tracking memory budget across sender and receiver.
///
/// `used` is a pure accounting counter — it does not gate access to any other
/// shared memory, so all operations on it use [`Ordering::Relaxed`]. Cross-thread
/// publication of message contents is handled by the underlying mpsc channel.
#[derive(Debug)]
struct MemoryBudget {
    /// Current number of bytes used by buffered messages.
    used: AtomicUsize,
    /// Maximum allowed bytes.
    max_bytes: usize,
}

/// Guard that releases memory budget when dropped.
///
/// Holds the size of the message and a reference to the shared budget counter.
/// When dropped, it atomically decreases the used counter.
#[derive(Debug)]
struct BudgetGuard {
    size: usize,
    budget: Arc<MemoryBudget>,
}

impl Drop for BudgetGuard {
    fn drop(&mut self) {
        self.budget.used.fetch_sub(self.size, Ordering::Relaxed);
    }
}

/// Message envelope that holds the memory budget while the message sits in the channel.
///
/// The guard is dropped (releasing the budget) as soon as the receiver dequeues
/// the message via [`MemoryBoundedReceiver::recv`] / [`MemoryBoundedReceiver::poll_recv`],
/// so the budget tracks bytes *currently in the channel queue*, not bytes in flight
/// downstream of the receiver.
#[derive(Debug)]
struct Budgeted<T> {
    msg: T,
    _guard: BudgetGuard,
}

/// A sender that enforces a byte budget before enqueueing messages.
///
/// Uses a shared atomic counter to track memory usage. Each message's size is added
/// to the counter on send and subtracted when the message is dequeued by the receiver.
///
/// The current call sites (specifically [`crate::common::mpsc::MemoryBoundedSender`] used
/// for the `NetworkManager → TransactionsManager` channel) have a single producer driven
/// from a single `poll`, so the `fetch_add → check → fetch_sub-on-overflow` reservation
/// pattern can never race with itself. The atomic is still used so the receiver can
/// release budget from a different task.
#[derive(Debug, Clone)]
pub struct MemoryBoundedSender<T: InMemorySize> {
    /// The underlying unbounded metered sender
    inner: UnboundedMeteredSender<Budgeted<T>>,
    /// Shared memory budget tracker
    budget: Arc<MemoryBudget>,
}

impl<T: InMemorySize> MemoryBoundedSender<T> {
    /// Tries to send a message if there is sufficient budget.
    ///
    /// Returns `TrySendError::Full` if insufficient budget is available.
    pub fn try_send(&self, msg: T) -> Result<(), TrySendError<T>> {
        let size = msg.size();

        // Reserve budget: add first, check after
        let prev = self.budget.used.fetch_add(size, Ordering::Relaxed);
        if prev.saturating_add(size) > self.budget.max_bytes {
            // Over budget, undo
            self.budget.used.fetch_sub(size, Ordering::Relaxed);
            return Err(TrySendError::Full(msg));
        }

        let guard = BudgetGuard { size, budget: Arc::clone(&self.budget) };
        let budgeted = Budgeted { msg, _guard: guard };

        self.inner.send(budgeted).map_err(|e| {
            // Guard will be dropped here, releasing the budget
            TrySendError::Closed(e.0.msg)
        })
    }
}

/// A receiver for memory-bounded messages.
///
/// On receive, the budget reserved for the message is released immediately and the
/// inner `T` is yielded — callers do not need to opt into any wrapper type.
#[derive(Debug)]
pub struct MemoryBoundedReceiver<T> {
    /// The underlying unbounded metered receiver
    inner: UnboundedMeteredReceiver<Budgeted<T>>,
}

impl<T> MemoryBoundedReceiver<T> {
    /// Receives the next message, returning `None` if the channel is closed.
    ///
    /// Releases the message's reserved budget before returning.
    pub async fn recv(&mut self) -> Option<T> {
        self.inner.recv().await.map(unwrap_budgeted)
    }

    /// Polls to receive the next message on this channel.
    ///
    /// Releases the message's reserved budget before returning.
    pub fn poll_recv(&mut self, cx: &mut Context<'_>) -> Poll<Option<T>> {
        self.inner.poll_recv(cx).map(|opt| opt.map(unwrap_budgeted))
    }
}

/// Releases the budget guard and returns the inner message.
fn unwrap_budgeted<T>(b: Budgeted<T>) -> T {
    // Destructuring binds `_guard` so it is dropped when this function returns,
    // which runs `BudgetGuard::drop` and releases the reserved bytes.
    let Budgeted { msg, _guard } = b;
    msg
}

impl<T> Stream for MemoryBoundedReceiver<T> {
    type Item = T;

    fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
        self.poll_recv(cx)
    }
}

/// Creates a new memory-bounded channel with the given byte budget.
///
/// The budget tracks bytes currently buffered in the channel; it is reserved on
/// [`MemoryBoundedSender::try_send`] and released as soon as the receiver dequeues
/// the message.

pub fn memory_bounded_channel<T: InMemorySize>(
    max_bytes: usize,
    scope: &'static str,
) -> (MemoryBoundedSender<T>, MemoryBoundedReceiver<T>) {
    let (tx, rx) = metered_unbounded_channel(scope);
    let budget = Arc::new(MemoryBudget { used: AtomicUsize::new(0), max_bytes });

    let sender = MemoryBoundedSender { inner: tx, budget };
    let receiver = MemoryBoundedReceiver { inner: rx };

    (sender, receiver)
}


tream for MeteredReceiver<T> {
    type Item = T;

    fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
        self.poll_recv(cx)
    }
}

/// Throughput metrics for [`MeteredSender`]
#[derive(Clone, Metrics)]
#[metrics(dynamic = true)]
pub struct MeteredSenderMetrics {
    /// Number of messages sent
    messages_sent_total: Counter,
    /// Number of failed message deliveries
    send_errors_total: Counter,
}

/// Throughput metrics for [`MeteredReceiver`]
#[derive(Clone, Metrics)]
#[metrics(dynamic = true)]
struct MeteredReceiverMetrics {
    /// Number of messages received
    messages_received_total: Counter,
}

/// A wrapper type around [`PollSender`] that updates metrics on send.
#[derive(Debug)]
pub struct MeteredPollSender<T> {
    /// The [`PollSender`] that this wraps around.
    sender: PollSender<T>,
    /// Holds metrics for this type.
    metrics: MeteredPollSenderMetrics,
}

impl<T: Send + 'static> MeteredPollSender<T> {
    /// Creates a new [`MeteredPollSender`] wrapping around the provided [`PollSender`].
    pub fn new(sender: PollSender<T>, scope: &'static str) -> Self {
        Self { sender, metrics: MeteredPollSenderMetrics::new(scope) }
    }

    /// Returns the underlying [`PollSender`].
    pub const fn inner(&self) -> &PollSender<T> {
        &self.sender
    }

    /// Calls the underlying [`PollSender`]'s `poll_reserve`, incrementing the appropriate
    /// metrics depending on the result.
    pub fn poll_reserve(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), PollSendError<T>>> {
        match self.sender.poll_reserve(cx) {
            Poll::Ready(Ok(permit)) => Poll::Ready(Ok(permit)),
            Poll::Ready(Err(error)) => Poll::Ready(Err(error)),
            Poll::Pending => {
                self.metrics.back_pressure_total.increment(1);
                Poll::Pending
            }
        }
    }

    /// Calls the underlying [`PollSender`]'s `send_item`, incrementing the appropriate
    /// metrics depending on the result.
    pub fn send_item(&mut self, item: T) -> Result<(), PollSendError<T>> {
        match self.sender.send_item(item) {
            Ok(()) => {
                self.metrics.messages_sent_total.increment(1);
                Ok(())
            }
            Err(error) => Err(error),
        }
    }
}

impl<T> Clone for MeteredPollSender<T> {
    fn clone(&self) -> Self {
        Self { sender: self.sender.clone(), metrics: self.metrics.clone() }
    }
}

/// Throughput metrics for [`MeteredPollSender`]
#[derive(Clone, Metrics)]
#[metrics(dynamic = true)]
struct MeteredPollSenderMetrics {
    /// Number of messages sent
    messages_sent_total: Counter,
    /// Number of delayed message deliveries caused by a full channel
    back_pressure_total: Counter,
}

/// Shared state for tracking memory budget across sender and receiver.
///
/// `used` is a pure accounting counter — it does not gate access to any other
/// shared memory, so all operations on it use [`Ordering::Relaxed`]. Cross-thread
/// publication of message contents is handled by the underlying mpsc channel.
#[derive(Debug)]
struct MemoryBudget {
    /// Current number of bytes used by buffered messages.
    used: AtomicUsize,
    /// Maximum allowed bytes.
    max_bytes: usize,
}

/// Guard that releases memory budget when dropped.
///
/// Holds the size of the message and a reference to the shared budget counter.
/// When dropped, it atomically decreases the used counter.
#[derive(Debug)]
struct BudgetGuard {
    size: usize,
    budget: Arc<MemoryBudget>,
}

impl Drop for BudgetGuard {
    fn drop(&mut self) {
        self.budget.used.fetch_sub(self.size, Ordering::Relaxed);
    }
}

/// Message envelope that holds the memory budget while the message sits in the channel.
///
/// The guard is dropped (releasing the budget) as soon as the receiver dequeues
/// the message via [`MemoryBoundedReceiver::recv`] / [`MemoryBoundedReceiver::poll_recv`],
/// so the budget tracks bytes *currently in the channel queue*, not bytes in flight
/// downstream of the receiver.
#[derive(Debug)]
struct Budgeted<T> {
    msg: T,
    _guard: BudgetGuard,
}

/// A sender that enforces a byte budget before enqueueing messages.
///
/// Uses a shared atomic counter to track memory usage. Each message's size is added
/// to the counter on send and subtracted when the message is dequeued by the receiver.
///
/// The current call sites (specifically [`crate::common::mpsc::MemoryBoundedSender`] used
/// for the `NetworkManager → TransactionsManager` channel) have a single producer driven
/// from a single `poll`, so the `fetch_add → check → fetch_sub-on-overflow` reservation
/// pattern can never race with itself. The atomic is still used so the receiver can
/// release budget from a different task.
#[derive(Debug, Clone)]
pub struct MemoryBoundedSender<T: InMemorySize> {
    /// The underlying unbounded metered sender
    inner: UnboundedMeteredSender<Budgeted<T>>,
    /// Shared memory budget tracker
    budget: Arc<MemoryBudget>,
}

impl<T: InMemorySize> MemoryBoundedSender<T> {
    /// Tries to send a message if there is sufficient budget.
    ///
    /// Returns `TrySendError::Full` if insufficient budget is available.
    pub fn try_send(&self, msg: T) -> Result<(), TrySendError<T>> {
        let size = msg.size();

        // Reserve budget: add first, check after
        let prev = self.budget.used.fetch_add(size, Ordering::Relaxed);
        if prev.saturating_add(size) > self.budget.max_bytes {
            // Over budget, undo
            self.budget.used.fetch_sub(size, Ordering::Relaxed);
            return Err(TrySendError::Full(msg));
        }

        let guard = BudgetGuard { size, budget: Arc::clone(&self.budget) };
        let budgeted = Budgeted { msg, _guard: guard };

        self.inner.send(budgeted).map_err(|e| {
            // Guard will be dropped here, releasing the budget
            TrySendError::Closed(e.0.msg)
        })
    }
}

/// A receiver for memory-bounded messages.
///
/// On receive, the budget reserved for the message is released immediately and the
/// inner `T` is yielded — callers do not need to opt into any wrapper type.
#[derive(Debug)]
pub struct MemoryBoundedReceiver<T> {
    /// The underlying unbounded metered receiver
    inner: UnboundedMeteredReceiver<Budgeted<T>>,
}

impl<T> MemoryBoundedReceiver<T> {
    /// Receives the next message, returning `None` if the channel is closed.
    ///
    /// Releases the message's reserved budget before returning.
    pub async fn recv(&mut self) -> Option<T> {
        self.inner.recv().await.map(unwrap_budgeted)
    }

    /// Polls to receive the next message on this channel.
    ///
    /// Releases the message's reserved budget before returning.
    pub fn poll_recv(&mut self, cx: &mut Context<'_>) -> Poll<Option<T>> {
        self.inner.poll_recv(cx).map(|opt| opt.map(unwrap_budgeted))
    }
}

/// Releases the budget guard and returns the inner message.
fn unwrap_budgeted<T>(b: Budgeted<T>) -> T {
    // Destructuring binds `_guard` so it is dropped when this function returns,
    // which runs `BudgetGuard::drop` and releases the reserved bytes.
    let Budgeted { msg, _guard } = b;
    msg
}

impl<T> Stream for MemoryBoundedReceiver<T> {
    type Item = T;

    fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
        self.poll_recv(cx)
    }
}

/// Creates a new memory-bounded channel with the given byte budget.
///
/// The budget tracks bytes currently buffered in the channel; it is reserved on
/// [`MemoryBoundedSender::try_send`] and released as soon as the receiver dequeues
/// the message.
pub fn memory_bounded_channel<T: InMemorySize>(
    max_bytes: usize,
    scope: &'static str,
) -> (MemoryBoundedSender<T>, MemoryBoundedReceiver<T>)

{
    let (tx, rx) = metered_unbounded_channel(scope);
    let budget = Arc::new(MemoryBudget { used: AtomicUsize::new(0), max_bytes });

    let sender = MemoryBoundedSender { inner: tx, budget };
    let receiver = MemoryBoundedReceiver { inner: rx };

    (sender, receiver)
}


//! Additional helpers for executing tracing calls

use std::{
    any::Any,
    cell::RefCell,
    future::Future,
    panic::{catch_unwind, AssertUnwindSafe},
    pin::Pin,
    sync::{
        atomic::{AtomicUsize, Ordering},
        Arc, OnceLock,
    },
    task::{ready, Context, Poll},
    thread,
};

use tokio::sync::{oneshot, AcquireError, OwnedSemaphorePermit, Semaphore};

/// RPC Tracing call guard semaphore.
///
/// This is used to restrict the number of concurrent RPC requests to tracing methods like
/// `debug_traceTransaction` as well as `eth_getProof` because they can consume a lot of
/// memory and CPU.
///
/// This types serves as an entry guard for the [`BlockingTaskPool`] and is used to rate limit
/// parallel blocking tasks in the pool.
#[derive(Clone, Debug)]
pub struct BlockingTaskGuard(Arc<Semaphore>);

impl BlockingTaskGuard {
    /// Create a new `BlockingTaskGuard` with the given maximum number of blocking tasks in
    /// parallel.
    pub fn new(max_blocking_tasks: usize) -> Self {
        Self(Arc::new(Semaphore::new(max_blocking_tasks)))
    }

    /// See also [`Semaphore::acquire_owned`]
    pub async fn acquire_owned(self) -> Result<OwnedSemaphorePermit, AcquireError> {
        self.0.acquire_owned().await
    }

    /// See also [`Semaphore::acquire_many_owned`]
    pub async fn acquire_many_owned(self, n: u32) -> Result<OwnedSemaphorePermit, AcquireError> {
        self.0.acquire_many_owned(n).await
    }
}

/// Used to execute blocking tasks on a rayon threadpool from within a tokio runtime.
///
/// This is a dedicated threadpool for blocking tasks which are CPU bound.
/// RPC calls that perform blocking IO (disk lookups) are not executed on this pool but on the tokio
/// runtime's blocking pool, which performs poorly with CPU bound tasks (see
/// <https://ryhl.io/blog/async-what-is-blocking/>). Once the tokio blocking
/// pool is saturated it is converted into a queue, blocking tasks could then interfere with the
/// queue and block other RPC calls.
///
/// See also [tokio-docs] for more information.
///
/// [tokio-docs]: https://docs.rs/tokio/latest/tokio/index.html#cpu-bound-tasks-and-blocking-code
#[derive(Clone, Debug)]
pub struct BlockingTaskPool {
    pool: Arc<rayon::ThreadPool>,
}

impl BlockingTaskPool {
    /// Create a new `BlockingTaskPool` with the given threadpool.
    pub fn new(pool: rayon::ThreadPool) -> Self {
        Self { pool: Arc::new(pool) }
    }

    /// Convenience function to start building a new threadpool.
    pub fn builder() -> rayon::ThreadPoolBuilder {
        rayon::ThreadPoolBuilder::new()
    }

    /// Convenience function to build a new threadpool with the default configuration.
    ///
    /// Uses [`rayon::ThreadPoolBuilder::build`](rayon::ThreadPoolBuilder::build) defaults.
    /// If a different stack size or other parameters are needed, they can be configured via
    /// [`rayon::ThreadPoolBuilder`] returned by [`Self::builder`].
    pub fn build() -> Result<Self, rayon::ThreadPoolBuildError> {
        Self::builder().build().map(Self::new)
    }

    /// Asynchronous wrapper around Rayon's
    /// [`ThreadPool::spawn`](rayon::ThreadPool::spawn).
    ///
    /// Runs a function on the configured threadpool, returning a future that resolves with the
    /// function's return value.
    ///
    /// If the function panics, the future will resolve to an error.
    pub fn spawn<F, R>(&self, func: F) -> BlockingTaskHandle<R>
    where
        F: FnOnce() -> R + Send + 'static,
        R: Send + 'static,
    {
        let (tx, rx) = oneshot::channel();

        self.pool.spawn(move || {
            let _result = tx.send(catch_unwind(AssertUnwindSafe(func)));
        });

        BlockingTaskHandle { rx }
    }

    /// Asynchronous wrapper around Rayon's
    /// [`ThreadPool::spawn_fifo`](rayon::ThreadPool::spawn_fifo).
    ///
    /// Runs a function on the configured threadpool, returning a future that resolves with the
    /// function's return value.
    ///
    /// If the function panics, the future will resolve to an error.
    pub fn spawn_fifo<F, R>(&self, func: F) -> BlockingTaskHandle<R>
    where
        F: FnOnce() -> R


//! Additional helpers for executing tracing calls

use std::{
    any::Any,
    cell::RefCell,
    future::Future,
    panic::{catch_unwind, AssertUnwindSafe},
    pin::Pin,
    sync::{
        atomic::{AtomicUsize, Ordering},
        Arc, OnceLock,
    },
    task::{ready, Context, Poll},
    thread,
};

use tokio::sync::{oneshot, AcquireError, OwnedSemaphorePermit, Semaphore};

/// RPC Tracing call guard semaphore.
///
/// This is used to restrict the number of concurrent RPC requests to tracing methods like
/// `debug_traceTransaction` as well as `eth_getProof` because they can consume a lot of
/// memory and CPU.
///
/// This types serves as an entry guard for the [`BlockingTaskPool`] and is used to rate limit
/// parallel blocking tasks in the pool.
#[derive(Clone, Debug)]
pub struct BlockingTaskGuard(Arc<Semaphore>);

impl BlockingTaskGuard {
    /// Create a new `BlockingTaskGuard` with the given maximum number of blocking tasks in
    /// parallel.
    pub fn new(max_blocking_tasks: usize) -> Self {
        Self(Arc::new(Semaphore::new(max_blocking_tasks)))
    }

    /// See also [`Semaphore::acquire_owned`]
    pub async fn acquire_owned(self) -> Result<OwnedSemaphorePermit, AcquireError> {
        self.0.acquire_owned().await
    }

    /// See also [`Semaphore::acquire_many_owned`]
    pub async fn acquire_many_owned(self, n: u32) -> Result<OwnedSemaphorePermit, AcquireError> {
        self.0.acquire_many_owned(n).await
    }
}

/// Used to execute blocking tasks on a rayon threadpool from within a tokio runtime.
///
/// This is a dedicated threadpool for blocking tasks which are CPU bound.
/// RPC calls that perform blocking IO (disk lookups) are not executed on this pool but on the tokio
/// runtime's blocking pool, which performs poorly with CPU bound tasks (see
/// <https://ryhl.io/blog/async-what-is-blocking/>). Once the tokio blocking
/// pool is saturated it is converted into a queue, blocking tasks could then interfere with the
/// queue and block other RPC calls.
///
/// See also [tokio-docs] for more information.
///
/// [tokio-docs]: https://docs.rs/tokio/latest/tokio/index.html#cpu-bound-tasks-and-blocking-code
#[derive(Clone, Debug)]
pub struct BlockingTaskPool {
    pool: Arc<rayon::ThreadPool>,
}

impl BlockingTaskPool {
    /// Create a new `BlockingTaskPool` with the given threadpool.
    pub fn new(pool: rayon::ThreadPool) -> Self {
        Self { pool: Arc::new(pool) }
    }

    /// Convenience function to start building a new threadpool.
    pub fn builder() -> rayon::ThreadPoolBuilder {
        rayon::ThreadPoolBuilder::new()
    }

    /// Convenience function to build a new threadpool with the default configuration.
    ///
    /// Uses [`rayon::ThreadPoolBuilder::build`](rayon::ThreadPoolBuilder::build) defaults.
    /// If a different stack size or other parameters are needed, they can be configured via
    /// [`rayon::ThreadPoolBuilder`] returned by [`Self::builder`].
    pub fn build() -> Result<Self, rayon::ThreadPoolBuildError> {
        Self::builder().build().map(Self::new)
    }

    /// Asynchronous wrapper around Rayon's
    /// [`ThreadPool::spawn`](rayon::ThreadPool::spawn).
    ///
    /// Runs a function on the configured threadpool, returning a future that resolves with the
    /// function's return value.
    ///
    /// If the function panics, the future will resolve to an error.
    pub fn spawn<F, R>(&self, func: F) -> BlockingTaskHandle<R>
    where
        F: FnOnce() -> R + Send + 'static,
        R: Send + 'static,
    {
        let (tx, rx) = oneshot::channel();

        self.pool.spawn(move || {
            let _result = tx.send(catch_unwind(AssertUnwindSafe(func)));
        });

        BlockingTaskHandle { rx }
    }

    /// Asynchronous wrapper around Rayon's
    /// [`ThreadPool::spawn_fifo`](rayon::ThreadPool::spawn_fifo).
    ///
    /// Runs a function on the configured threadpool, returning a future that resolves with the
    /// function's return value.
    ///
    /// If the function panics, the future will resolve to an error.
    pub fn spawn_fifo<F, R>(&self, func: F) -> BlockingTaskHandle<R>
    where
        F: FnOnce() -> R + Send + 'static,
        R: Send + 'static,
    {
        let (tx, rx) = oneshot::channel();

        self.pool.spawn_fifo(move || {
            let _result = tx.send(catch_unwind(AssertUnwindSafe(func)));
        });

        BlockingTaskHandle { rx }
    }
}

/// Async handle for a blocking task running in a Rayon thread pool.
///
/// ## Panics
///
/// If polled from outside a tokio runtime.
#[derive(Debug)]
#[must_use = "futures do nothing unless you `.await` or poll them"]
#[pin_project::pin_project]
pub struct BlockingTaskHandle<T> {
    #[pin]
    pub(crate) rx: oneshot::Receiver<thread::Result<T>>,
}

impl<T> Future for BlockingTaskHandle<T> {
    type Output = thread::Result<T>;

    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
        match ready!(self.project().rx.poll(cx)) {
            Ok(res) => Poll::Ready(res),
            Err(_) => Poll::Ready(Err(Box::<TokioBlockingTaskError>::default())),
        }
    }
}

/// An error returned when the Tokio channel is dropped while awaiting a result.
///
/// This should only happen
#[derive(Debug, Default, thiserror::Error)]
#[error("tokio channel dropped while awaiting result")]
#[non_exhaustive]
pub struct TokioBlockingTaskError;

thread_local! {
    static WORKER: RefCell<Worker> = const { RefCell::new(Worker::new()) };
}

/// A rayon thread pool with per-thread [`Worker`] state.
///
/// Each thread in the pool has its own [`Worker`] that can hold arbitrary state via
/// [`Worker::init`]. The state is thread-local and accessible during [`install`](Self::install)
/// calls.
///
/// The pool supports multiple init/clear cycles, allowing reuse of the same threads with
/// different state configurations.
///
/// The underlying rayon pool is created lazily on first access.
#[derive(Debug)]
pub struct WorkerPool {
    pool: OnceLock<rayon::ThreadPool>,
    num_threads: usize,
    thread_name_prefix: &'static str,
}

impl WorkerPool {
    /// Creates a new lazy `WorkerPool` with the given number of threads and a thread name prefix.
    ///
    /// The underlying rayon pool is not created until the first method that requires it is called.
    /// Thread names follow the pattern `"{prefix}-{index:02}"`.
    pub const fn new(num_threads: usize, thread_name_prefix: &'static str) -> Self {
        Self { pool: OnceLock::new(), num_threads, thread_name_prefix }
    }

    /// Returns a reference to the underlying rayon pool, creating it on first access.
    fn pool(&self) -> &rayon::ThreadPool {
        self.pool.get_or_init(|| {
            let prefix = self.thread_name_prefix;
            build_pool_with_panic_handler(
                rayon::ThreadPoolBuilder::new()
                    .num_threads(self.num_threads)
                    .thread_name(move |i| format!("{prefix}-{i:02}")),
            )
            .unwrap_or_else(|err| panic!("failed to build {prefix} worker pool: {err}"))
        })
    }

    /// Returns `true` if the underlying rayon pool has been initialized.
    pub fn is_initialized(&self) -> bool {
        self.pool.get().is_some()
    }

    /// Returns the total number of threads in the underlying rayon pool.
    pub fn current_num_threads(&self) -> usize {
        self.pool().current_num_threads()
    }

    /// Initializes per-thread [`Worker`] state on every thread in the pool.
    pub fn init<T: 'static>(&self, f: impl Fn(Option<&mut T>) -> T + Sync) {
        self.broadcast(self.pool().current_num_threads(), |worker| {
            worker.init::<T>(&f);
        });
    }

    /// Runs a closure on `num_threads` threads in the pool, giving mutable access to each
    /// thread's [`Worker`].
    ///
    /// Use this to initialize or re-initialize per-thread state via [`Worker::init`].
    /// Only `num_threads` threads execute the closure; the rest skip it.
    pub fn broadcast(&self, num_threads: usize, f: impl Fn(&mut Worker) + Sync) {
        if num_threads >= self.pool().current_num_threads() {
            // Fast path: run on every thread, no atomic coordination needed.
            self.pool().broadcast(|_| {
                WORKER.with_borrow_mut(|worker| f(worker));
            }


//! Additional helpers for executing tracing calls

use std::{
    any::Any,
    cell::RefCell,
    future::Future,
    panic::{catch_unwind, AssertUnwindSafe},
    pin::Pin,
    sync::{
        atomic::{AtomicUsize, Ordering},
        Arc, OnceLock,
    },
    task::{ready, Context, Poll},
    thread,
};
use tokio::sync::{oneshot, AcquireError, OwnedSemaphorePermit, Semaphore};

/// RPC Tracing call guard semaphore.
///
/// This is used to restrict the number of concurrent RPC requests to tracing methods like
/// `debug_traceTransaction` as well as `eth_getProof` because they can consume a lot of
/// memory and CPU.
///
/// This types serves as an entry guard for the [`BlockingTaskPool`] and is used to rate limit
/// parallel blocking tasks in the pool.
#[derive(Clone, Debug)]
pub struct BlockingTaskGuard(Arc<Semaphore>);

impl BlockingTaskGuard {
    /// Create a new `BlockingTaskGuard` with the given maximum number of blocking tasks in
    /// parallel.
    pub fn new(max_blocking_tasks: usize) -> Self {
        Self(Arc::new(Semaphore::new(max_blocking_tasks)))
    }

    /// See also [`Semaphore::acquire_owned`]
    pub async fn acquire_owned(self) -> Result<OwnedSemaphorePermit, AcquireError> {
        self.0.acquire_owned().await
    }

    /// See also [`Semaphore::acquire_many_owned`]
    pub async fn acquire_many_owned(self, n: u32) -> Result<OwnedSemaphorePermit, AcquireError> {
        self.0.acquire_many_owned(n).await
    }
}

/// Used to execute blocking tasks on a rayon threadpool from within a tokio runtime.
///
/// This is a dedicated threadpool for blocking tasks which are CPU bound.
/// RPC calls that perform blocking IO (disk lookups) are not executed on this pool but on the tokio
/// runtime's blocking pool, which performs poorly with CPU bound tasks (see
/// <https://ryhl.io/blog/async-what-is-blocking/>). Once the tokio blocking
/// pool is saturated it is converted into a queue, blocking tasks could then interfere with the
/// queue and block other RPC calls.
///
/// See also [tokio-docs] for more information.
///
/// [tokio-docs]: https://docs.rs/tokio/latest/tokio/index.html#cpu-bound-tasks-and-blocking-code
#[derive(Clone, Debug)]
pub struct BlockingTaskPool {
    pool: Arc<rayon::ThreadPool>,
}

impl BlockingTaskPool {
    /// Create a new `BlockingTaskPool` with the given threadpool.
    pub fn new(pool: rayon::ThreadPool) -> Self {
        Self { pool: Arc::new(pool) }
    }

    /// Convenience function to start building a new threadpool.
    pub fn builder() -> rayon::ThreadPoolBuilder {
        rayon::ThreadPoolBuilder::new()
    }

    /// Convenience function to build a new threadpool with the default configuration.
    ///
    /// Uses [`rayon::ThreadPoolBuilder::build`](rayon::ThreadPoolBuilder::build) defaults.
    /// If a different stack size or other parameters are needed, they can be configured via
    /// [`rayon::ThreadPoolBuilder`] returned by [`Self::builder`].
    pub fn build() -> Result<Self, rayon::ThreadPoolBuildError> {
        Self::builder().build().map(Self::new)
    }

    /// Asynchronous wrapper around Rayon's
    /// [`ThreadPool::spawn`](rayon::ThreadPool::spawn).
    ///
    /// Runs a function on the configured threadpool, returning a future that resolves with the
    /// function's return value.
    ///
    /// If the function panics, the future will resolve to an error.
    pub fn spawn<F, R>(&self, func: F) -> BlockingTaskHandle<R>
    where
        F: FnOnce() -> R + Send + 'static,
        R: Send + 'static,
    {
        let (tx, rx) = oneshot::channel();

        self.pool.spawn(move || {
            let _result = tx.send(catch_unwind(AssertUnwindSafe(func)));
        });

        BlockingTaskHandle { rx }
    }

    /// Asynchronous wrapper around Rayon's
    /// [`ThreadPool::spawn_fifo`](rayon::ThreadPool::spawn_fifo).
    ///
    /// Runs a function on the configured threadpool, returning a future that resolves with the
    /// function's return value.
    ///
    /// If the function panics, the future will resolve to an error.
    pub fn spawn_fifo<F, R>(&self, func: F) -> BlockingTaskHandle<R>
    where
        F: FnOnce() -> R + Send + 'static,
        R: Send + 'static,
    {
        let (tx, rx) = oneshot::channel();

        self.pool.spawn_fifo(move || {
            let _result = tx.send(catch_unwind(AssertUnwindSafe(func)));
        });

        BlockingTaskHandle { rx }
    }
}

/// Async handle for a blocking task running in a Rayon thread pool.
///
/// ## Panics
///
/// If polled from outside a tokio runtime.
#[derive(Debug)]
#[must_use = "


//! Additional helpers for executing tracing calls

use std::{
    any::Any,
    cell::RefCell,
    future::Future,
    panic::{catch_unwind, AssertUnwindSafe},
    pin::Pin,
    sync::{
        atomic::{AtomicUsize, Ordering},
        Arc, OnceLock,
    },
    task::{ready, Context, Poll},
    thread,
};
use tokio::sync::{oneshot, AcquireError, OwnedSemaphorePermit, Semaphore};

/// RPC Tracing call guard semaphore.
///
/// This is used to restrict the number of concurrent RPC requests to tracing methods like
/// `debug_traceTransaction` as well as `eth_getProof` because they can consume a lot of
/// memory and CPU.
///
/// This types serves as an entry guard for the [`BlockingTaskPool`] and is used to rate limit
/// parallel blocking tasks in the pool.
#[derive(Clone, Debug)]
pub struct BlockingTaskGuard(Arc<Semaphore>);

impl BlockingTaskGuard {
    /// Create a new `BlockingTaskGuard` with the given maximum number of blocking tasks in
    /// parallel.
    pub fn new(max_blocking_tasks: usize) -> Self {
        Self(Arc::new(Semaphore::new(max_blocking_tasks)))
    }

    /// See also [`Semaphore::acquire_owned`]
    pub async fn acquire_owned(self) -> Result<OwnedSemaphorePermit, AcquireError> {
        self.0.acquire_owned().await
    }

    /// See also [`Semaphore::acquire_many_owned`]
    pub async fn acquire_many_owned(self, n: u32) -> Result<OwnedSemaphorePermit, AcquireError> {
        self.0.acquire_many_owned(n).await
    }
}

/// Used to execute blocking tasks on a rayon threadpool from within a tokio runtime.
///
/// This is a dedicated threadpool for blocking tasks which are CPU bound.
/// RPC calls that perform blocking IO (disk lookups) are not executed on this pool but on the tokio
/// runtime's blocking pool, which performs poorly with CPU bound tasks (see
/// <https://ryhl.io/blog/async-what-is-blocking/>). Once the tokio blocking
/// pool is saturated it is converted into a queue, blocking tasks could then interfere with the
/// queue and block other RPC calls.
///
/// See also [tokio-docs] for more information.
///
/// [tokio-docs]: https://docs.rs/tokio/latest/tokio/index.html#cpu-bound-tasks-and-blocking-code
#[derive(Clone, Debug)]
pub struct BlockingTaskPool {
    pool: Arc<rayon::ThreadPool>,
}

impl BlockingTaskPool {
    /// Create a new `BlockingTaskPool` with the given threadpool.
    pub fn new(pool: rayon::ThreadPool) -> Self {
        Self { pool: Arc::new(pool) }
    }

    /// Convenience function to start building a new threadpool.
    pub fn builder() -> rayon::ThreadPoolBuilder {
        rayon::ThreadPoolBuilder::new()
    }

    /// Convenience function to build a new threadpool with the default configuration.
    ///
    /// Uses [`rayon::ThreadPoolBuilder::build`](rayon::ThreadPoolBuilder::build) defaults.
    /// If a different stack size or other parameters are needed, they can be configured via
    /// [`rayon::ThreadPoolBuilder`] returned by [`Self::builder`].
    pub fn build() -> Result<Self, rayon::ThreadPoolBuildError> {
        Self::builder().build().map(Self::new)
    }

    /// Asynchronous wrapper around Rayon's
    /// [`ThreadPool::spawn`](rayon::ThreadPool::spawn).
    ///
    /// Runs a function on the configured threadpool, returning a future that resolves with the
    /// function's return value.
    ///
    /// If the function panics, the future will resolve to an error.
    pub fn spawn<F, R>(&self, func: F) -> BlockingTaskHandle<R>
    where
        F: FnOnce() -> R + Send + 'static,
        R: Send + 'static,
    {
        let (tx, rx) = oneshot::channel();

        self.pool.spawn(move || {
            let _result = tx.send(catch_unwind(AssertUnwindSafe(func)));
        });

        BlockingTaskHandle { rx }
    }

    /// Asynchronous wrapper around Rayon's
    /// [`ThreadPool::spawn_fifo`](rayon::ThreadPool::spawn_fifo).
    ///
    /// Runs a function on the configured threadpool, returning a future that resolves with the
    /// function's return value.
    ///
    /// If the function panics, the future will resolve to an error.
    pub fn spawn_fifo<F, R>(&self, func: F) -> BlockingTaskHandle<R>
    where
        F: FnOnce() -> R + Send + 'static,
        R: Send + 'static,
    {
        let (tx, rx) = oneshot::channel();

        self.pool.spawn_fifo(move || {
            let _result = tx.send(catch_unwind(AssertUnwindSafe(func)));
        });

        BlockingTaskHandle { rx }
    }
}

/// Async handle for a blocking task running in a Rayon thread pool.
///
/// ## Panics
///
/// If polled from outside a tokio runtime.
#[derive(Debug)]
#[must_use = "futures do nothing unless you `.await` or poll them"]
#[pin_project::pin_project]
pub struct BlockingTaskHandle<T> {
    #[pin]
    pub(crate) rx: oneshot::Receiver<thread::Result<T>>,
}

impl<T> Future for BlockingTaskHandle<T> {
    type Output = thread::Result<T>;

    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
        match ready!(self.project().rx.poll(cx)) {
            Ok(res) => Poll::Ready(res),
            Err(_) => Poll::Ready(Err(Box::<TokioBlockingTaskError>::default())),
        }
    }
}


//! Additional helpers for executing tracing calls

use std::{
    any::Any,
    cell::RefCell,
    future::Future,
    panic::{catch_unwind, AssertUnwindSafe},
    pin::Pin,
    sync::{
        atomic::{AtomicUsize, Ordering},
        Arc, OnceLock,
    },
    task::{ready, Context, Poll},
    thread,
};
use tokio::sync::{oneshot, AcquireError, OwnedSemaphorePermit, Semaphore};

/// RPC Tracing call guard semaphore.
///
/// This is used to restrict the number of concurrent RPC requests to tracing methods like
/// `debug_traceTransaction` as well as `eth_getProof` because they can consume a lot of
/// memory and CPU.
///
/// This types serves as an entry guard for the [`BlockingTaskPool`] and is used to rate limit
/// parallel blocking tasks in the pool.
#[derive(Clone, Debug)]
pub struct BlockingTaskGuard(Arc<Semaphore>);

impl BlockingTaskGuard {
    /// Create a new `BlockingTaskGuard` with the given maximum number of blocking tasks in
    /// parallel.

    pub fn new(max_blocking_tasks: usize) -> Self {
        Self(Arc::new(Semaphore::new(max_blocking_tasks)))
    }

    /// See also [`Semaphore::acquire_owned`]
    pub async fn acquire_owned(self) -> Result<OwnedSemaphorePermit, AcquireError> {
        self.0.acquire_owned().await
    }

    /// See also [`Semaphore::acquire_many_owned`]
    pub async fn acquire_many_owned(self, n: u32) -> Result<OwnedSemaphorePermit, AcquireError> {
        self.0.acquire_many_owned(n).await
    }
}

/// Used to execute blocking tasks on a rayon threadpool from within a tokio runtime.
///
/// This is a dedicated threadpool for blocking tasks which are CPU bound.
/// RPC calls that perform blocking IO (disk lookups) are not executed on this pool but on the tokio
/// runtime's blocking pool, which performs poorly with CPU bound tasks (see
/// <https://ryhl.io/blog/async-what-is-blocking/>). Once the tokio blocking
/// pool is saturated it is converted into a queue, blocking tasks could then interfere with the
/// queue and block other RPC calls.
///
/// See also [tokio-docs] for more information.
///
/// [tokio-docs]: https://docs.rs/tokio/latest/tokio/index.html#cpu-bound-tasks-and-blocking-code
#[derive(Clone, Debug)]
pub struct BlockingTaskPool {
    pool: Arc<rayon::ThreadPool>,
}

impl BlockingTaskPool {
    /// Create a new `BlockingTaskPool` with the given threadpool.
    pub fn new(pool: rayon::ThreadPool) -> Self {
        Self { pool: Arc::new(pool) }
    }

    /// Convenience function to start building a new threadpool.
    pub fn builder() -> rayon::ThreadPoolBuilder {
        rayon::ThreadPoolBuilder::new()
    }

    /// Convenience function to build a new threadpool with the default configuration.
    ///
    /// Uses [`rayon::ThreadPoolBuilder::build`](rayon::ThreadPoolBuilder::build) defaults.
    /// If a different stack size or other parameters are needed, they can be configured via
    /// [`rayon::ThreadPoolBuilder`] returned by [`Self::builder`].
    pub fn build() -> Result<Self, rayon::ThreadPoolBuildError> {
        Self::builder().build().map(Self::new)
    }

    /// Asynchronous wrapper around Rayon's
    /// [`ThreadPool::spawn`](rayon::ThreadPool::spawn).
    ///
    /// Runs a function on the configured threadpool, returning a future that resolves with the
    /// function's return value.
    ///
    /// If the function panics, the future will resolve to an error.
    pub fn spawn<F, R>(&self, func: F) -> BlockingTaskHandle<R>
    where
        F: FnOnce() -> R + Send + 'static,
        R: Send + 'static,
    {
        let (tx, rx) = oneshot::channel();

        self.pool.spawn(move || {
            let _result = tx.send(catch_unwind(AssertUnwindSafe(func)));
        });

        BlockingTaskHandle { rx }
    }

    /// Asynchronous wrapper around Rayon's
    /// [`ThreadPool::spawn_fifo`](rayon::ThreadPool::spawn_fifo).
    ///
    /// Runs a function on the configured threadpool, returning a future that resolves with the
    /// function's return value.
    ///
    /// If the function panics, the future will resolve to an error.
    pub fn spawn_fifo<F, R>(&self, func: F) -> BlockingTaskHandle<R>
    where
        F: FnOnce() -> R + Send + 'static,
        R: Send + 'static,
    {
        let (tx, rx) = oneshot::channel();

        self.pool.spawn_fifo(move || {
            let _result = tx.send(catch_unwind(AssertUnwindSafe(func)));
        });

        BlockingTaskHandle { rx }
    }
}

/// Async handle for a blocking task running in a Rayon thread pool.
///
/// ## Panics
///
/// If polled from outside a tokio runtime.
#[derive(Debug)]
#[must_use = "futures do nothing unless you `.await` or poll them"]
#[pin_project::pin_project]
pub struct BlockingTaskHandle<T> {
    #[pin]
    pub(crate) rx: oneshot::Receiver<thread::Result<T>>,
}

impl<T> Future for BlockingTaskHandle<T> {
    type Output = thread::Result<T>;

    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
        match ready!(self.project().rx.poll(cx)) {
            Ok(res) =


//! Additional helpers for executing tracing calls

use std::{
    any::Any,
    cell::RefCell,
    future::Future,
    panic::{catch_unwind, AssertUnwindSafe},
    pin::Pin,
    sync::{
        atomic::{AtomicUsize, Ordering},
        Arc, OnceLock,
    },
    task::{ready, Context, Poll},
    thread,
};
use tokio::sync::{oneshot, AcquireError, OwnedSemaphorePermit, Semaphore};

/// RPC Tracing call guard semaphore.
///
/// This is used to restrict the number of concurrent RPC requests to tracing methods like
/// `debug_traceTransaction` as well as `eth_getProof` because they can consume a lot of
/// memory and CPU.
///
/// This types serves as an entry guard for the [`BlockingTaskPool`] and is used to rate limit
/// parallel blocking tasks in the pool.
#[derive(Clone, Debug)]
pub struct BlockingTaskGuard(Arc<Semaphore>);

impl BlockingTaskGuard {
    /// Create a new `BlockingTaskGuard` with the given maximum number of blocking tasks in
    /// parallel.
    pub fn new(max_blocking_tasks: usize) -> Self

{
        Self(Arc::new(Semaphore::new(max_blocking_tasks)))
    }

    /// See also [`Semaphore::acquire_owned`]
    pub async fn acquire_owned(self) -> Result<OwnedSemaphorePermit, AcquireError> {
        self.0.acquire_owned().await
    }

    /// See also [`Semaphore::acquire_many_owned`]
    pub async fn acquire_many_owned(self, n: u32) -> Result<OwnedSemaphorePermit, AcquireError> {
        self.0.acquire_many_owned(n).await
    }
}

/// Used to execute blocking tasks on a rayon threadpool from within a tokio runtime.
///
/// This is a dedicated threadpool for blocking tasks which are CPU bound.
/// RPC calls that perform blocking IO (disk lookups) are not executed on this pool but on the tokio
/// runtime's blocking pool, which performs poorly with CPU bound tasks (see
/// <https://ryhl.io/blog/async-what-is-blocking/>). Once the tokio blocking
/// pool is saturated it is converted into a queue, blocking tasks could then interfere with the
/// queue and block other RPC calls.
///
/// See also [tokio-docs] for more information.
///
/// [tokio-docs]: https://docs.rs/tokio/latest/tokio/index.html#cpu-bound-tasks-and-blocking-code
#[derive(Clone, Debug)]
pub struct BlockingTaskPool {
    pool: Arc<rayon::ThreadPool>,
}

impl BlockingTaskPool {
    /// Create a new `BlockingTaskPool` with the given threadpool.
    pub fn new(pool: rayon::ThreadPool) -> Self {
        Self { pool: Arc::new(pool) }
    }

    /// Convenience function to start building a new threadpool.
    pub fn builder() -> rayon::ThreadPoolBuilder {
        rayon::ThreadPoolBuilder::new()
    }

    /// Convenience function to build a new threadpool with the default configuration.
    ///
    /// Uses [`rayon::ThreadPoolBuilder::build`](rayon::ThreadPoolBuilder::build) defaults.
    /// If a different stack size or other parameters are needed, they can be configured via
    /// [`rayon::ThreadPoolBuilder`] returned by [`Self::builder`].
    pub fn build() -> Result<Self, rayon::ThreadPoolBuildError> {
        Self::builder().build().map(Self::new)
    }

    /// Asynchronous wrapper around Rayon's
    /// [`ThreadPool::spawn`](rayon::ThreadPool::spawn).
    ///
    /// Runs a function on the configured threadpool, returning a future that resolves with the
    /// function's return value.
    ///
    /// If the function panics, the future will resolve to an error.
    pub fn spawn<F, R>(&self, func: F) -> BlockingTaskHandle<R>
    where
        F: FnOnce() -> R + Send + 'static,
        R: Send + 'static,
    {
        let (tx, rx) = oneshot::channel();

        self.pool.spawn(move || {
            let _result = tx.send(catch_unwind(AssertUnwindSafe(func)));
        });

        BlockingTaskHandle { rx }
    }

    /// Asynchronous wrapper around Rayon's
    /// [`ThreadPool::spawn_fifo`](rayon::ThreadPool::spawn_fifo).
    ///
    /// Runs a function on the configured threadpool, returning a future that resolves with the
    /// function's return value.
    ///
    /// If the function panics, the future will resolve to an error.
    pub fn spawn_fifo<F, R>(&self, func: F) -> BlockingTaskHandle<R>
    where
        F: FnOnce() -> R + Send + 'static,
        R: Send + 'static,
    {
        let (tx, rx) = oneshot::channel();

        self.pool.spawn_fifo(move || {
            let _result = tx.send(catch_unwind(AssertUnwindSafe(func)));
        });

        BlockingTaskHandle { rx }
    }
}

/// Async handle for a blocking task running in a Rayon thread pool.
///
/// ## Panics
///
/// If polled from outside a tokio runtime.
#[derive(Debug)]
#[must_use = "futures do nothing unless you `.await` or poll them"]
#[pin_project::pin_project]
pub struct BlockingTaskHandle<T> {
    #[pin]
    pub(crate) rx: oneshot::Receiver<thread::Result<T>>,
}

impl<T> Future for BlockingTaskHandle<T> {
    type Output = thread::Result<T>;

    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
        match ready!(self.project().rx.poll(cx)) {
            Ok(res) => Poll::Ready(res),


//! Additional helpers for executing tracing calls

use std::{
    any::Any,
    cell::RefCell,
    future::Future,
    panic::{catch_unwind, AssertUnwindSafe},
    pin::Pin,
    sync::{
        atomic::{AtomicUsize, Ordering},
        Arc, OnceLock,
    },
    task::{ready, Context, Poll},
    thread,
};
use tokio::sync::{oneshot, AcquireError, OwnedSemaphorePermit, Semaphore};

/// RPC Tracing call guard semaphore.
///
/// This is used to restrict the number of concurrent RPC requests to tracing methods like
/// `debug_traceTransaction` as well as `eth_getProof` because they can consume a lot of
/// memory and CPU.
///
/// This types serves as an entry guard for the [`BlockingTaskPool`] and is used to rate limit
/// parallel blocking tasks in the pool.
#[derive(Clone, Debug)]
pub struct BlockingTaskGuard(Arc<Semaphore>);

impl BlockingTaskGuard {
    /// Create a new `BlockingTaskGuard` with the given maximum number of blocking tasks in
    /// parallel.
    pub fn new(max_blocking_tasks: usize) -> Self {
        Self(Arc::new(Semaphore::new(max_blocking_tasks)))
    }

    /// See also [`Semaphore::acquire_owned`]

    pub async fn acquire_owned(self) -> Result<OwnedSemaphorePermit, AcquireError> {
        self.0.acquire_owned().await
    }

    /// See also [`Semaphore::acquire_many_owned`]
    pub async fn acquire_many_owned(self, n: u32) -> Result<OwnedSemaphorePermit, AcquireError> {
        self.0.acquire_many_owned(n).await
    }
}

/// Used to execute blocking tasks on a rayon threadpool from within a tokio runtime.
///
/// This is a dedicated threadpool for blocking tasks which are CPU bound.
/// RPC calls that perform blocking IO (disk lookups) are not executed on this pool but on the tokio
/// runtime's blocking pool, which performs poorly with CPU bound tasks (see
/// <https://ryhl.io/blog/async-what-is-blocking/>). Once the tokio blocking
/// pool is saturated it is converted into a queue, blocking tasks could then interfere with the
/// queue and block other RPC calls.
///
/// See also [tokio-docs] for more information.
///
/// [tokio-docs]: https://docs.rs/tokio/latest/tokio/index.html#cpu-bound-tasks-and-blocking-code
#[derive(Clone, Debug)]
pub struct BlockingTaskPool {
    pool: Arc<rayon::ThreadPool>,
}

impl BlockingTaskPool {
    /// Create a new `BlockingTaskPool` with the given threadpool.
    pub fn new(pool: rayon::ThreadPool) -> Self {
        Self { pool: Arc::new(pool) }
    }

    /// Convenience function to start building a new threadpool.
    pub fn builder() -> rayon::ThreadPoolBuilder {
        rayon::ThreadPoolBuilder::new()
    }

    /// Convenience function to build a new threadpool with the default configuration.
    ///
    /// Uses [`rayon::ThreadPoolBuilder::build`](rayon::ThreadPoolBuilder::build) defaults.
    /// If a different stack size or other parameters are needed, they can be configured via
    /// [`rayon::ThreadPoolBuilder`] returned by [`Self::builder`].
    pub fn build() -> Result<Self, rayon::ThreadPoolBuildError> {
        Self::builder().build().map(Self::new)
    }

    /// Asynchronous wrapper around Rayon's
    /// [`ThreadPool::spawn`](rayon::ThreadPool::spawn).
    ///
    /// Runs a function on the configured threadpool, returning a future that resolves with the
    /// function's return value.
    ///
    /// If the function panics, the future will resolve to an error.
    pub fn spawn<F, R>(&self, func: F) -> BlockingTaskHandle<R>
    where
        F: FnOnce() -> R + Send + 'static,
        R: Send + 'static,
    {
        let (tx, rx) = oneshot::channel();

        self.pool.spawn(move || {
            let _result = tx.send(catch_unwind(AssertUnwindSafe(func)));
        });

        BlockingTaskHandle { rx }
    }

    /// Asynchronous wrapper around Rayon's
    /// [`ThreadPool::spawn_fifo`](rayon::ThreadPool::spawn_fifo).
    ///
    /// Runs a function on the configured threadpool, returning a future that resolves with the
    /// function's return value.
    ///
    /// If the function panics, the future will resolve to an error.
    pub fn spawn_fifo<F, R>(&self, func: F) -> BlockingTaskHandle<R>
    where
        F: FnOnce() -> R + Send + 'static,
        R: Send + 'static,
    {
        let (tx, rx) = oneshot::channel();

        self.pool.spawn_fifo(move || {
            let _result = tx.send(catch_unwind(AssertUnwindSafe(func)));
        });

        BlockingTaskHandle { rx }
    }
}

/// Async handle for a blocking task running in a Rayon thread pool.
///
/// ## Panics
///
/// If polled from outside a tokio runtime.
#[derive(Debug)]
#[must_use = "futures do nothing unless you `.await` or poll them"]
#[pin_project::pin_project]
pub struct BlockingTaskHandle<T> {
    #[pin]
    pub(crate) rx: oneshot::Receiver<thread::Result<T>>,
}

impl<T> Future for BlockingTaskHandle<T> {
    type Output = thread::Result<T>;

    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
        match ready!(self.project().rx.poll(cx)) {
            Ok(res) => Poll::Ready(res),
            Err(_) => Poll::Ready(Err(Box::<TokioBlockingTaskError>::default())),
        }
    }
}

/// An error returned when the Tokio channel is dropped while awaiting a result.
///
/// This should only happen
#[derive(Debug, Default, thiserror::Error)]
#[error("tokio channel dropped while awaiting result")]
#[non_exhaustive]
pub struct TokioBlockingTaskError;

thread_local! {
    static WORKER: RefCell<Worker> = const { RefCell::new(Worker::new()) };
}

/// A rayon thread pool with per-thread [`Worker`] state.
///
/// Each thread in the pool has its own [`Worker`] that can hold arbitrary state via
/// [`Worker::init`]. The state is thread-local and accessible during [`install`](Self::install)
/// calls.
///
/// The pool supports multiple init/clear cycles, allowing reuse of the same threads with
/// different state configurations.
///
/// The underlying rayon pool is created lazily on first access.
#[derive(Debug)]
pub struct WorkerPool {
    pool: OnceLock<rayon::ThreadPool>,
    num_threads: usize,
    thread_name_prefix: &'static str,
}

impl WorkerPool {
    /// Creates a new lazy `WorkerPool` with the given number of threads and a thread name prefix.
    ///
    /// The underlying rayon pool is not created until the first method that requires it is called.
    /// Thread names follow the pattern `"{prefix}-{index:02}"`.
    pub const fn new(num_threads: usize, thread_name_prefix: &'static str) -> Self {
        Self { pool: OnceLock::new(), num_threads, thread_name_prefix }
    }

    /// Returns a reference to the underlying rayon pool, creating it on first access.
    fn pool(&self) -> &rayon::ThreadPool {
        self.pool.get_or_init(|| {
            let prefix = self.thread_name_prefix;
            build_pool_with_panic_handler(
                rayon::ThreadPoolBuilder::new()
                    .num_threads(self.num_threads)
                    .thread_name(move |i| format!("{prefix}-{i:02}")),
            )
            .unwrap_or_else(|err| panic!("failed to build {prefix} worker pool: {err}"))
        })
    }

    /// Returns `true` if the underlying rayon pool has been initialized.
    pub fn is_initialized(&self) -> bool {
        self.pool.get().is_some()
    }

    /// Returns the total number of threads in the underlying rayon pool.
    pub fn current_num_threads(&self) -> usize {
        self.pool().current_num_threads()
    }

    /// Initializes per-thread [`Worker`] state on every thread in the pool.
    pub fn init<T: 'static>(&self, f: impl Fn(Option<&mut T>) -> T + Sync) {
        self.broadcast(self.pool().current_num_threads(), |worker| {
            worker.init::<T>(&f);
        });
    }

    /// Runs a closure on `num_threads` threads in the pool, giving mutable access to each
    /// thread's [`Worker`].
    ///
    /// Use this to initialize or re-initialize per-thread state via [`Worker::init`].
    /// Only `num_threads` threads execute the closure; the rest skip it.
    pub fn broadcast(&self, num_threads: usize, f: impl Fn(&mut Worker) + Sync) {
        if num_threads >= self.pool().current_num_threads() {
            // Fast path: run on every thread, no atomic coordination needed.
            self.pool().broadcast(|_| {
                WORKER.with_borrow_mut(|worker| f(worker));
            });
        } else {
            let remaining = AtomicUsize::new(num_threads);
            self.pool().broadcast(|_| {
                // Atomically claim a slot; threads that can't decrement skip the closure.
                let mut current = remaining.load(Ordering::Relaxed);
                loop {
                    if current == 0 {
                        return;
                    }
                    match remaining.compare_exchange_weak(
                        current,
                        current - 1,
                        Ordering::Relaxed,
                        Ordering::Relaxed,
                    ) {
                        Ok(_) => break,
                        Err(actual) => current = actual,
                    }
                }
                WORKER.with_borrow_mut(|worker| f(worker));
            });