Skip to main content

linera_ethereum/
client.rs

1// Copyright (c) Zefchain Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4//! RPC client traits for querying an Ethereum node.
5
6use std::fmt::Debug;
7
8use alloy::rpc::types::eth::{
9    request::{TransactionInput, TransactionRequest},
10    BlockId, BlockNumberOrTag, Filter, Log,
11};
12use alloy_primitives::{Address, Bytes, B256, U256, U64};
13use async_trait::async_trait;
14use linera_base::ensure;
15use serde::{de::DeserializeOwned, Deserialize, Serialize};
16use serde_json::value::RawValue;
17
18use crate::common::{
19    event_name_from_expanded, parse_log, EthereumEvent, EthereumQueryError, EthereumServiceError,
20};
21
22/// A basic RPC client for making JSON queries
23#[async_trait]
24pub trait JsonRpcClient {
25    /// The error type returned by the client's requests.
26    type Error: From<serde_json::Error> + From<EthereumQueryError>;
27
28    /// The inner function that has to be implemented and access the client
29    async fn request_inner(&self, payload: Vec<u8>) -> Result<Vec<u8>, Self::Error>;
30
31    /// Gets a new ID for the next message.
32    async fn get_id(&self) -> u64;
33
34    /// The function doing the parsing of the input and output.
35    async fn request<T, R>(&self, method: &str, params: T) -> Result<R, Self::Error>
36    where
37        T: Debug + Serialize + Send + Sync,
38        R: DeserializeOwned + Send,
39    {
40        let id = self.get_id().await;
41        let payload = JsonRpcRequest::new(id, method, params);
42        let payload = serde_json::to_vec(&payload)?;
43        let body = self.request_inner(payload).await?;
44        let result = serde_json::from_slice::<JsonRpcResponse>(&body)?;
45        let raw = result.result;
46        let res = serde_json::from_str(raw.get())?;
47        ensure!(id == result.id, EthereumQueryError::IdIsNotMatching);
48        ensure!(
49            "2.0" == result.jsonrpc,
50            EthereumQueryError::WrongJsonRpcVersion
51        );
52        Ok(res)
53    }
54}
55
56#[derive(Serialize, Deserialize, Debug)]
57struct JsonRpcRequest<'a, T> {
58    id: u64,
59    jsonrpc: &'a str,
60    method: &'a str,
61    params: T,
62}
63
64impl<'a, T> JsonRpcRequest<'a, T> {
65    /// Creates a new JSON RPC request, the id does not matter
66    pub fn new(id: u64, method: &'a str, params: T) -> Self {
67        Self {
68            id,
69            jsonrpc: "2.0",
70            method,
71            params,
72        }
73    }
74}
75
76/// A JSON-RPC response as returned by an Ethereum node.
77#[derive(Debug, Deserialize)]
78pub struct JsonRpcResponse {
79    id: u64,
80    jsonrpc: String,
81    result: Box<RawValue>,
82}
83
84/// The basic Ethereum queries that can be used from a smart contract and do not require
85/// gas to be executed.
86#[async_trait]
87pub trait EthereumQueries {
88    /// The error type returned by the queries.
89    type Error;
90
91    /// Lists all the accounts of the Ethereum node.
92    async fn get_accounts(&self) -> Result<Vec<String>, Self::Error>;
93
94    /// Gets the latest block number of the Ethereum node.
95    async fn get_block_number(&self) -> Result<u64, Self::Error>;
96
97    /// Gets the balance of the specified address at the specified block number.
98    /// if no block number is specified then the balance of the latest block is
99    /// returned.
100    async fn get_balance(&self, address: &str, block_number: u64) -> Result<U256, Self::Error>;
101
102    /// Reads the events of the smart contract.
103    ///
104    /// This is done from a specified `contract_address` and `event_name_expanded`.
105    /// That is one should have `MyEvent(type1 indexed,type2)` instead
106    /// of the usual `MyEvent(type1,type2)`
107    ///
108    /// The `from_block` is inclusive.
109    /// The `to_block` is exclusive (contrary to Ethereum where it is inclusive)
110    async fn read_events(
111        &self,
112        contract_address: &str,
113        event_name_expanded: &str,
114        from_block: u64,
115        to_block: u64,
116    ) -> Result<Vec<EthereumEvent>, Self::Error>;
117
118    /// The operation done with `eth_call` on Ethereum returns
119    /// a result but are not committed to the blockchain. This can be useful for example
120    /// for executing function that are const and allow to inspect
121    /// the contract without modifying it.
122    async fn non_executive_call(
123        &self,
124        contract_address: &str,
125        data: Bytes,
126        from: &str,
127        block: u64,
128    ) -> Result<Bytes, Self::Error>;
129
130    /// Returns the chain ID reported by the connected EVM node.
131    async fn get_chain_id(&self) -> Result<u64, Self::Error>;
132
133    /// Checks whether a block hash is finalized and canonical on the EVM chain.
134    ///
135    /// Confirms the block exists, is at or below the latest finalized height, and
136    /// is the canonical block at that height. `eth_getBlockByHash` also returns
137    /// orphaned/uncle blocks (with their original, now non-canonical number, which
138    /// can be at or below the finalized height), so the canonical block at the
139    /// height must additionally be checked to have this exact hash.
140    /// Returns `Err(BlockNotFound)` if the hash does not exist on chain.
141    async fn is_block_hash_finalized(&self, block_hash: B256) -> Result<bool, Self::Error>;
142}
143
144pub(crate) fn get_block_id(block_number: u64) -> BlockId {
145    let number = BlockNumberOrTag::Number(block_number);
146    BlockId::Number(number)
147}
148
149#[async_trait]
150impl<C> EthereumQueries for C
151where
152    C: JsonRpcClient + Sync,
153    EthereumServiceError: From<<C as JsonRpcClient>::Error>,
154{
155    type Error = EthereumServiceError;
156
157    async fn get_accounts(&self) -> Result<Vec<String>, Self::Error> {
158        let results: Vec<String> = self.request("eth_accounts", ()).await?;
159        Ok(results
160            .into_iter()
161            .map(|x| x.to_lowercase())
162            .collect::<Vec<_>>())
163    }
164
165    async fn get_block_number(&self) -> Result<u64, Self::Error> {
166        let result = self.request::<_, U64>("eth_blockNumber", ()).await?;
167        Ok(result.to::<u64>())
168    }
169
170    async fn get_chain_id(&self) -> Result<u64, Self::Error> {
171        let result = self.request::<_, U64>("eth_chainId", ()).await?;
172        Ok(result.to::<u64>())
173    }
174
175    async fn get_balance(&self, address: &str, block_number: u64) -> Result<U256, Self::Error> {
176        let address = address.parse::<Address>()?;
177        let tag = get_block_id(block_number);
178        Ok(self.request("eth_getBalance", (address, tag)).await?)
179    }
180
181    async fn read_events(
182        &self,
183        contract_address: &str,
184        event_name_expanded: &str,
185        from_block: u64,
186        to_block: u64,
187    ) -> Result<Vec<EthereumEvent>, Self::Error> {
188        let contract_address = contract_address.parse::<Address>()?;
189        let event_name = event_name_from_expanded(event_name_expanded);
190        let filter = Filter::new()
191            .address(contract_address)
192            .event(&event_name)
193            .from_block(from_block)
194            .to_block(to_block - 1);
195        let events = self
196            .request::<_, Vec<Log>>("eth_getLogs", (filter,))
197            .await?;
198        events
199            .into_iter()
200            .map(|x| parse_log(event_name_expanded, &x))
201            .collect::<Result<_, _>>()
202    }
203
204    async fn non_executive_call(
205        &self,
206        contract_address: &str,
207        data: Bytes,
208        from: &str,
209        block: u64,
210    ) -> Result<Bytes, Self::Error> {
211        let contract_address = contract_address.parse::<Address>()?;
212        let from = from.parse::<Address>()?;
213        let input = TransactionInput::new(data);
214        let tx = TransactionRequest::default()
215            .from(from)
216            .to(contract_address)
217            .input(input);
218        let tag = get_block_id(block);
219        Ok(self.request::<_, Bytes>("eth_call", (tx, tag)).await?)
220    }
221
222    async fn is_block_hash_finalized(&self, block_hash: B256) -> Result<bool, Self::Error> {
223        // The block must exist; learn its height.
224        let block: Option<EthBlock> = self
225            .request("eth_getBlockByHash", (block_hash, false))
226            .await?;
227        let block = block.ok_or(EthereumServiceError::BlockNotFound)?;
228        let block_number = block.number.to::<u64>();
229
230        // It must be at or below the latest finalized height.
231        let finalized: EthBlock = self
232            .request("eth_getBlockByNumber", ("finalized", false))
233            .await?;
234        if block_number > finalized.number.to::<u64>() {
235            return Ok(false);
236        }
237
238        // `eth_getBlockByHash` also returns orphaned/uncle blocks, which carry
239        // their original (now non-canonical) number and can be at or below the
240        // finalized height. Require the canonical block at this height to have
241        // this exact hash; otherwise the supplied hash is a non-canonical block
242        // that must not be treated as finalized.
243        let canonical: Option<EthBlock> = self
244            .request(
245                "eth_getBlockByNumber",
246                (BlockNumberOrTag::Number(block_number), false),
247            )
248            .await?;
249        Ok(canonical.map(|canonical| canonical.hash) == Some(block_hash))
250    }
251}
252
253/// Minimal block response: the fields needed to verify finality and canonicality.
254#[derive(Deserialize)]
255struct EthBlock {
256    number: U64,
257    hash: B256,
258}