reth_rpc_eth_types/utils.rs
1//! Commonly used code snippets
2
3use super::{EthApiError, EthResult};
4use alloy_consensus::TxReceipt;
5use reth_primitives_traits::{Recovered, SignedTransaction};
6use std::future::Future;
7
8/// Calculates the gas used and next log index for a transaction at the given index
9pub fn calculate_gas_used_and_next_log_index(
10 tx_index: u64,
11 all_receipts: &[impl TxReceipt],
12) -> (u64, usize) {
13 let mut gas_used = 0;
14 let mut next_log_index = 0;
15
16 if tx_index > 0 {
17 for receipt in all_receipts.iter().take(tx_index as usize) {
18 gas_used = receipt.cumulative_gas_used();
19 next_log_index += receipt.logs().len();
20 }
21 }
22
23 (gas_used, next_log_index)
24}
25
26/// Recovers a [`SignedTransaction`] from an enveloped encoded byte stream.
27///
28/// This is a helper function that returns the appropriate RPC-specific error if the input data is
29/// malformed.
30///
31/// This function uses [`alloy_eips::eip2718::Decodable2718::decode_2718_exact`] to ensure
32/// that the entire input buffer is consumed and no trailing bytes are allowed.
33///
34/// See [`alloy_eips::eip2718::Decodable2718::decode_2718_exact`]
35pub fn recover_raw_transaction<T: SignedTransaction>(data: &[u8]) -> EthResult<Recovered<T>> {
36 SignedTransaction::try_into_recovered(decode_raw_transaction::<T>(data)?)
37 .or(Err(EthApiError::InvalidTransactionSignature))
38}
39
40/// Decodes an EIP-2718 transaction without recovering its sender.
41///
42/// Rejects empty input, malformed transactions, and trailing bytes with RPC-specific errors.
43pub fn decode_raw_transaction<T: SignedTransaction>(data: &[u8]) -> EthResult<T> {
44 if data.is_empty() {
45 return Err(EthApiError::EmptyRawTransactionData)
46 }
47
48 T::decode_2718_exact(data).map_err(|_| EthApiError::FailedToDecodeSignedTransaction)
49}
50
51/// Performs a binary search within a given block range to find the desired block number.
52///
53/// The binary search is performed by calling the provided asynchronous `check` closure on the
54/// blocks of the range. The closure should return a future representing the result of performing
55/// the desired logic at a given block. The future resolves to an `bool` where:
56/// - `true` indicates that the condition has been matched, but we can try to find a lower block to
57/// make the condition more matchable.
58/// - `false` indicates that the condition not matched, so the target is not present in the current
59/// block and should continue searching in a higher range.
60///
61/// Args:
62/// - `low`: The lower bound of the block range (inclusive).
63/// - `high`: The upper bound of the block range (inclusive).
64/// - `check`: A closure that performs the desired logic at a given block.
65pub async fn binary_search<F, Fut, E>(low: u64, high: u64, check: F) -> Result<u64, E>
66where
67 F: Fn(u64) -> Fut,
68 Fut: Future<Output = Result<bool, E>>,
69{
70 let mut low = low;
71 let mut high = high;
72 let mut num = high;
73
74 while low <= high {
75 let mid = (low + high) / 2;
76 if check(mid).await? {
77 high = mid - 1;
78 num = mid;
79 } else {
80 low = mid + 1
81 }
82 }
83
84 Ok(num)
85}
86
87/// Calculates the blob gas used ratio for a block, accounting for the case where
88/// `max_blob_gas_per_block` is zero.
89///
90/// Returns `0.0` if `max_blob_gas_per_block` is `0`, otherwise returns the ratio
91/// `blob_gas_used/max_blob_gas_per_block`.
92pub fn checked_blob_gas_used_ratio(blob_gas_used: u64, max_blob_gas_per_block: u64) -> f64 {
93 if max_blob_gas_per_block == 0 {
94 0.0
95 } else {
96 blob_gas_used as f64 / max_blob_gas_per_block as f64
97 }
98}
99
100#[cfg(test)]
101mod tests {
102 use super::*;
103
104 #[tokio::test]
105 async fn test_binary_search() {
106 // in the middle
107 let num: Result<_, ()> =
108 binary_search(1, 10, |mid| Box::pin(async move { Ok(mid >= 5) })).await;
109 assert_eq!(num, Ok(5));
110
111 // in the upper
112 let num: Result<_, ()> =
113 binary_search(1, 10, |mid| Box::pin(async move { Ok(mid >= 7) })).await;
114 assert_eq!(num, Ok(7));
115
116 // in the lower
117 let num: Result<_, ()> =
118 binary_search(1, 10, |mid| Box::pin(async move { Ok(mid >= 1) })).await;
119 assert_eq!(num, Ok(1));
120
121 // higher than the upper
122 let num: Result<_, ()> =
123 binary_search(1, 10, |mid| Box::pin(async move { Ok(mid >= 11) })).await;
124 assert_eq!(num, Ok(10));
125 }
126
127 #[test]
128 fn test_checked_blob_gas_used_ratio() {
129 // No blob gas used, max blob gas per block is 0
130 assert_eq!(checked_blob_gas_used_ratio(0, 0), 0.0);
131 // Blob gas used is non-zero, max blob gas per block is 0 (division by zero protection)
132 assert_eq!(checked_blob_gas_used_ratio(50, 0), 0.0);
133 // Blob gas used is zero, max blob gas per block is non-zero
134 assert_eq!(checked_blob_gas_used_ratio(0, 100), 0.0);
135 // Blob gas used is non-zero, max blob gas per block is non-zero
136 assert_eq!(checked_blob_gas_used_ratio(50, 100), 0.5);
137 // Blob gas used is non-zero and equal to max blob gas per block
138 assert_eq!(checked_blob_gas_used_ratio(100, 100), 1.0);
139 }
140}