alloy_provider/provider/
get_block.rs

1use std::{fmt::Debug, marker::PhantomData};
2
3use crate::{utils, ProviderCall};
4use alloy_eips::{BlockId, BlockNumberOrTag};
5use alloy_json_rpc::RpcRecv;
6use alloy_network::BlockResponse;
7use alloy_network_primitives::BlockTransactionsKind;
8use alloy_primitives::{Address, BlockHash, B256, B64};
9use alloy_rpc_client::{ClientRef, RpcCall};
10use alloy_transport::{TransportError, TransportResult};
11use serde_json::Value;
12
13/// The parameters for an `eth_getBlockBy{Hash, Number}` RPC request.
14///
15/// Default is "latest" block with transaction hashes.
16#[derive(Clone, Debug, Default)]
17pub struct EthGetBlockParams {
18    block: BlockId,
19    kind: BlockTransactionsKind,
20}
21
22impl serde::Serialize for EthGetBlockParams {
23    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
24    where
25        S: serde::Serializer,
26    {
27        use serde::ser::SerializeTuple;
28
29        let mut tuple = serializer.serialize_tuple(2)?;
30        match self.block {
31            BlockId::Hash(hash) => tuple.serialize_element(&hash.block_hash)?,
32            BlockId::Number(number) => tuple.serialize_element(&number)?,
33        }
34        if self.kind.is_hashes() {
35            tuple.serialize_element(&false)?;
36        } else {
37            tuple.serialize_element(&true)?
38        };
39        tuple.end()
40    }
41}
42
43impl EthGetBlockParams {
44    /// Instantiate [`EthGetBlockParams`] with the given block and kind.
45    pub fn new(block: BlockId, kind: BlockTransactionsKind) -> Self {
46        Self { block, kind }
47    }
48}
49
50/// A builder for an `"eth_getBlockByHash"` request. This type is returned by the
51/// [`Provider::call`] method.
52///
53/// [`Provider::call`]: crate::Provider::call
54#[must_use = "EthGetBlockBy must be awaited to execute the request"]
55//#[derive(Clone, Debug)]
56pub struct EthGetBlock<BlockResp>
57where
58    BlockResp: alloy_network::BlockResponse + RpcRecv,
59{
60    inner: GetBlockInner<BlockResp>,
61    block: BlockId,
62    kind: BlockTransactionsKind,
63    _pd: std::marker::PhantomData<BlockResp>,
64}
65
66impl<BlockResp> EthGetBlock<BlockResp>
67where
68    BlockResp: alloy_network::BlockResponse + RpcRecv,
69{
70    /// Create a new [`EthGetBlock`] request to get the block by hash i.e call
71    /// `"eth_getBlockByHash"`.
72    pub fn by_hash(hash: BlockHash, client: ClientRef<'_>) -> Self {
73        let params = EthGetBlockParams::default();
74        let call = client.request("eth_getBlockByHash", params);
75        Self::new_rpc(hash.into(), call)
76    }
77
78    /// Create a new [`EthGetBlock`] request to get the block by number i.e call
79    /// `"eth_getBlockByNumber"`.
80    pub fn by_number(number: BlockNumberOrTag, client: ClientRef<'_>) -> Self {
81        let params = EthGetBlockParams::default();
82
83        if number.is_pending() {
84            return Self::new_pending_rpc(client.request("eth_getBlockByNumber", params));
85        }
86
87        Self::new_rpc(number.into(), client.request("eth_getBlockByNumber", params))
88    }
89}
90
91impl<BlockResp> EthGetBlock<BlockResp>
92where
93    BlockResp: alloy_network::BlockResponse + RpcRecv,
94{
95    /// Create a new [`EthGetBlock`] request with the given [`RpcCall`].
96    pub fn new_rpc(block: BlockId, inner: RpcCall<EthGetBlockParams, Option<BlockResp>>) -> Self {
97        Self {
98            block,
99            inner: GetBlockInner::RpcCall(inner),
100            kind: BlockTransactionsKind::Hashes,
101            _pd: PhantomData,
102        }
103    }
104
105    /// Create a new [`EthGetBlock`] request with the given [`RpcCall`] for pending block.
106    pub fn new_pending_rpc(inner: RpcCall<EthGetBlockParams, Value>) -> Self {
107        Self {
108            block: BlockId::pending(),
109            inner: GetBlockInner::PendingBlock(inner),
110            kind: BlockTransactionsKind::Hashes,
111            _pd: PhantomData,
112        }
113    }
114
115    /// Create a new [`EthGetBlock`] request with a closure that returns a [`ProviderCall`].
116    pub fn new_provider(block: BlockId, producer: ProviderCallProducer<BlockResp>) -> Self {
117        Self {
118            block,
119            inner: GetBlockInner::ProviderCall(producer),
120            kind: BlockTransactionsKind::Hashes,
121            _pd: PhantomData,
122        }
123    }
124
125    /// Set the [`BlockTransactionsKind`] for the request.
126    pub fn kind(mut self, kind: BlockTransactionsKind) -> Self {
127        self.kind = kind;
128        self
129    }
130
131    /// Set the [`BlockTransactionsKind`] to [`BlockTransactionsKind::Full`].
132    pub fn full(mut self) -> Self {
133        self.kind = BlockTransactionsKind::Full;
134        self
135    }
136
137    /// Set the [`BlockTransactionsKind`] to [`BlockTransactionsKind::Hashes`].
138    pub fn hashes(mut self) -> Self {
139        self.kind = BlockTransactionsKind::Hashes;
140        self
141    }
142}
143
144impl<BlockResp> std::future::IntoFuture for EthGetBlock<BlockResp>
145where
146    BlockResp: alloy_network::BlockResponse + RpcRecv,
147{
148    type Output = TransportResult<Option<BlockResp>>;
149
150    type IntoFuture = ProviderCall<EthGetBlockParams, Option<BlockResp>>;
151
152    fn into_future(self) -> Self::IntoFuture {
153        match self.inner {
154            GetBlockInner::RpcCall(call) => {
155                let rpc_call =
156                    call.map_params(|_params| EthGetBlockParams::new(self.block, self.kind));
157
158                let fut = async move {
159                    let resp = rpc_call.await?;
160                    let result =
161                        if self.kind.is_hashes() { utils::convert_to_hashes(resp) } else { resp };
162                    Ok(result)
163                };
164
165                ProviderCall::BoxedFuture(Box::pin(fut))
166            }
167            GetBlockInner::PendingBlock(call) => {
168                let rpc_call =
169                    call.map_params(|_params| EthGetBlockParams::new(self.block, self.kind));
170
171                let map_fut = async move {
172                    let mut block = rpc_call.await?;
173
174                    if block.is_null() {
175                        return Ok(None);
176                    }
177
178                    // Ref: <https://github.com/alloy-rs/alloy/issues/2117>
179                    // Geth ref: <https://github.com/ethereum/go-ethereum/blob/ebff2f42c0fbb4ebee43b0e73e39b658305a8a9b/internal/ethapi/api.go#L470-L471>
180                    tracing::trace!(pending_block = ?block.to_string());
181                    if block.get("hash").map_or(true, |v| v.is_null()) {
182                        block["hash"] = Value::String(format!("{}", B256::ZERO));
183                    }
184
185                    if block.get("nonce").map_or(true, |v| v.is_null()) {
186                        block["nonce"] = Value::String(format!("{}", B64::ZERO));
187                    }
188
189                    if block.get("miner").map_or(true, |v| v.is_null())
190                        || block.get("beneficiary").map_or(true, |v| v.is_null())
191                    {
192                        block["miner"] = Value::String(format!("{}", Address::ZERO));
193                    }
194
195                    let block = serde_json::from_value(block.clone())
196                        .map_err(|e| TransportError::deser_err(e, block.to_string()))?;
197
198                    let block = if self.kind.is_hashes() {
199                        utils::convert_to_hashes(Some(block))
200                    } else {
201                        Some(block)
202                    };
203
204                    Ok(block)
205                };
206
207                ProviderCall::BoxedFuture(Box::pin(map_fut))
208            }
209            GetBlockInner::ProviderCall(producer) => producer(self.kind),
210        }
211    }
212}
213
214impl<BlockResp> core::fmt::Debug for EthGetBlock<BlockResp>
215where
216    BlockResp: BlockResponse + RpcRecv,
217{
218    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
219        f.debug_struct("EthGetBlock").field("block", &self.block).field("kind", &self.kind).finish()
220    }
221}
222
223type ProviderCallProducer<BlockResp> =
224    Box<dyn Fn(BlockTransactionsKind) -> ProviderCall<EthGetBlockParams, Option<BlockResp>> + Send>;
225
226enum GetBlockInner<BlockResp>
227where
228    BlockResp: BlockResponse + RpcRecv,
229{
230    /// [`RpcCall`] with params that get wrapped into [`EthGetBlockParams`] in the future.
231    RpcCall(RpcCall<EthGetBlockParams, Option<BlockResp>>),
232    /// Pending Block Call
233    ///
234    /// This has been made explicit to handle cases where fields such as `hash`, `nonce`, `miner`
235    /// are either missing or set to null causing deserilization issues. See: <https://github.com/alloy-rs/alloy/issues/2117>
236    ///
237    /// This is specifically true in case of the response is returned from a geth node. See: <https://github.com/ethereum/go-ethereum/blob/ebff2f42c0fbb4ebee43b0e73e39b658305a8a9b/internal/ethapi/api.go#L470-L471>
238    ///
239    /// In such case, we first deserialize to [`Value`] and then check if the fields are missing or
240    /// set to null. If so, we set them to default values.  
241    PendingBlock(RpcCall<EthGetBlockParams, Value>),
242    /// Closure that produces a [`ProviderCall`] given [`BlockTransactionsKind`].
243    ProviderCall(ProviderCallProducer<BlockResp>),
244}
245
246impl<BlockResp> core::fmt::Debug for GetBlockInner<BlockResp>
247where
248    BlockResp: BlockResponse + RpcRecv,
249{
250    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
251        match self {
252            Self::RpcCall(call) => f.debug_tuple("RpcCall").field(call).finish(),
253            Self::PendingBlock(call) => f.debug_tuple("PendingBlockCall").field(call).finish(),
254            Self::ProviderCall(_) => f.debug_struct("ProviderCall").finish(),
255        }
256    }
257}
258
259#[cfg(test)]
260mod tests {
261    use super::*;
262    use crate::{Provider, ProviderBuilder};
263
264    // <https://github.com/alloy-rs/alloy/issues/2117>
265    #[tokio::test]
266    async fn test_pending_block_deser() {
267        let provider =
268            ProviderBuilder::new().on_http("https://binance.llamarpc.com".parse().unwrap());
269
270        let _block = provider.get_block_by_number(BlockNumberOrTag::Pending).await.unwrap();
271    }
272}