Skip to main content

yevm_core/
rpc.rs

1use serde::de::DeserializeOwned;
2use serde_json::{Value, json};
3use yevm_base::{Acc, Int};
4use yevm_misc::{buf::Buf, http::Http};
5
6use crate::{
7    call::{Block, Head, Receipt, TxFull},
8    chain::Chain,
9    state::Account,
10};
11
12pub struct Rpc {
13    url: String,
14    http: Http,
15    pub block_number: u64,
16    pub block_hash: Int,
17}
18
19impl Rpc {
20    pub async fn latest(url: String) -> eyre::Result<Self> {
21        let http = Http::new();
22        let head = head(&http, &url, "latest".into()).await?;
23        Ok(Self {
24            url,
25            http,
26            block_number: head.number.as_u64(),
27            block_hash: head.hash,
28        })
29    }
30
31    pub async fn number(url: String, number: u64) -> eyre::Result<Self> {
32        let http = Http::new();
33        let head = head(&http, &url, format!("0x{:x}", number)).await?;
34        Ok(Self {
35            url,
36            http,
37            block_number: head.number.as_u64(),
38            block_hash: head.hash,
39        })
40    }
41
42    pub fn offline() -> Self {
43        Self {
44            url: "".to_string(),
45            http: Http::new(),
46            block_number: 0,
47            block_hash: Int::zero(),
48        }
49    }
50
51    pub fn reset(&mut self, number: u64, hash: Int) {
52        self.block_number = number;
53        self.block_hash = hash;
54    }
55
56    pub async fn chain_id(&self) -> eyre::Result<u64> {
57        let chain_id: Int = call(&self.http, &self.url, "eth_chainId", &[]).await?;
58        Ok(chain_id.as_u64())
59    }
60
61    pub async fn lookup(&self, block: u64, index: u64) -> eyre::Result<TxFull> {
62        let tx = call::<TxFull>(
63            &self.http,
64            &self.url,
65            "eth_getTransactionByBlockNumberAndIndex",
66            &[
67                Value::String(format!("0x{block:x}")),
68                Value::String(format!("0x{index:x}")),
69            ],
70        )
71        .await?;
72        Ok(tx)
73    }
74
75    pub async fn receipt(&self, hash: Int) -> eyre::Result<Receipt> {
76        receipt(&self.http, &self.url, hash).await
77    }
78}
79
80#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
81#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
82impl Chain for Rpc {
83    async fn get(&self, acc: &Acc, key: &Int) -> eyre::Result<Int> {
84        let value = call(
85            &self.http,
86            &self.url,
87            "eth_getStorageAt",
88            &[
89                Value::String(acc.to_string()),
90                Value::String(key.to_string()),
91                Value::String(self.block_hash.to_string()),
92            ],
93        )
94        .await?;
95        Ok(value)
96    }
97
98    async fn acc(&self, acc: &Acc) -> eyre::Result<Account> {
99        // TODO: consider firing sub-calls concurrently to speed this up
100        Ok(Account {
101            value: self.balance(acc).await?,
102            nonce: self.nonce(acc).await?.into(),
103            code: self.code(acc).await?,
104        })
105    }
106
107    async fn code(&self, acc: &Acc) -> eyre::Result<(Buf, Int)> {
108        let code: Buf = call(
109            &self.http,
110            &self.url,
111            "eth_getCode",
112            &[
113                Value::String(acc.to_string()),
114                Value::String(self.block_hash.to_string()),
115            ],
116        )
117        .await?;
118        let hash = yevm_misc::keccak256(code.as_slice());
119        Ok((code, hash))
120    }
121
122    async fn nonce(&self, acc: &Acc) -> eyre::Result<u64> {
123        let nonce: Int = call(
124            &self.http,
125            &self.url,
126            "eth_getTransactionCount",
127            &[
128                Value::String(acc.to_string()),
129                Value::String(self.block_hash.to_string()),
130            ],
131        )
132        .await?;
133        Ok(nonce.as_u64())
134    }
135
136    async fn balance(&self, acc: &Acc) -> eyre::Result<Int> {
137        let balance = call(
138            &self.http,
139            &self.url,
140            "eth_getBalance",
141            &[
142                Value::String(acc.to_string()),
143                Value::String(self.block_hash.to_string()),
144            ],
145        )
146        .await?;
147        Ok(balance)
148    }
149
150    async fn head(&self, number: u64) -> eyre::Result<Head> {
151        let head = call(
152            &self.http,
153            &self.url,
154            "eth_getBlockByNumber",
155            &[Value::String(format!("0x{:x}", number)), Value::Bool(false)],
156        )
157        .await?;
158        Ok(head)
159    }
160
161    async fn block(&self, number: u64) -> eyre::Result<Block> {
162        let block = call(
163            &self.http,
164            &self.url,
165            "eth_getBlockByNumber",
166            &[Value::String(format!("0x{:x}", number)), Value::Bool(true)],
167        )
168        .await?;
169        Ok(block)
170    }
171
172    async fn chain_id(&self) -> eyre::Result<u64> {
173        let chain_id: Int = call(&self.http, &self.url, "eth_chainId", &[]).await?;
174        Ok(chain_id.as_u64())
175    }
176}
177
178async fn head(http: &Http, url: &str, arg: String) -> eyre::Result<Head> {
179    let head = call(
180        http,
181        url,
182        "eth_getBlockByNumber",
183        &[Value::String(arg), Value::Bool(false)],
184    )
185    .await?;
186    Ok(head)
187}
188
189async fn receipt(http: &Http, url: &str, hash: Int) -> eyre::Result<Receipt> {
190    let head = call(
191        http,
192        url,
193        "eth_getTransactionReceipt",
194        &[Value::String(hash.to_string())],
195    )
196    .await?;
197    Ok(head)
198}
199
200async fn call<R: DeserializeOwned>(
201    http: &Http,
202    url: &str,
203    method: &str,
204    params: &[Value],
205) -> eyre::Result<R> {
206    let body = json!({
207        "jsonrpc": "2.0",
208        "method": method,
209        "params": params,
210        "id": 1,
211    });
212    let json: Value = http.post(url, &body).await?;
213    if std::env::var("DEBUG").is_ok() {
214        // Just for debugging until proper logging is implemented
215        let body = serde_json::to_string_pretty(&body).unwrap();
216        let json = serde_json::to_string_pretty(&json).unwrap();
217        println!("{} -> {}", body, json);
218    }
219    if let Some(error) = json.get("error") {
220        if let Some(message) = error.as_str() {
221            eyre::bail!(message.to_owned());
222        }
223        if let Some(message) = error.get("message") {
224            eyre::bail!(message.to_string());
225        }
226        let message = serde_json::to_string(error)?;
227        eyre::bail!(message);
228    }
229    if let Some(result) = json.get("result") {
230        if result.is_null() {
231            eyre::bail!("result is null");
232        } else {
233            let ret = serde_json::from_value::<R>(result.to_owned())?;
234            return Ok(ret);
235        }
236    }
237    eprint!("JSON: {:#?}", json);
238    eyre::bail!("missing: result & error")
239}
240
241#[cfg(test)]
242mod tests {
243    use yevm_base::int;
244
245    use super::*;
246
247    #[tokio::test]
248    #[ignore = "makes RPC call to a public node"]
249    async fn test_latest() -> eyre::Result<()> {
250        let http = Http::new();
251        let head = head(
252            &http,
253            "https://ethereum-rpc.publicnode.com",
254            "latest".to_string(),
255        )
256        .await?;
257        let (number, hash) = (head.number.as_u64(), head.hash);
258        assert_ne!(hash, Int::ZERO);
259        assert!(number > 24697386);
260        Ok(())
261    }
262
263    #[tokio::test]
264    #[ignore = "makes RPC call to a public node"]
265    async fn test_receipt() -> eyre::Result<()> {
266        let hash = int("0xd3aafbde18d85a863399c94ffec80af928bb3ebecc3685f1c784245deff04c04");
267        let http = Http::new();
268        let _ = receipt(&http, "https://ethereum-rpc.publicnode.com", hash).await?;
269        Ok(())
270    }
271}