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