linera_ethereum/
client.rs

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