feat: add `get_logs` RPC method (#108)
* Implemented RPC method get_logs * Limit the max number of logs to 5 * remove unused import Co-authored-by: Noah Citron <noah@jeff.org>
This commit is contained in:
parent
eaf5605d4d
commit
3177ad55c1
|
@ -3,7 +3,7 @@ use std::sync::Arc;
|
||||||
|
|
||||||
use config::networks::Network;
|
use config::networks::Network;
|
||||||
use ethers::prelude::{Address, U256};
|
use ethers::prelude::{Address, U256};
|
||||||
use ethers::types::{Transaction, TransactionReceipt, H256};
|
use ethers::types::{Filter, Log, Transaction, TransactionReceipt, H256};
|
||||||
use eyre::{eyre, Result};
|
use eyre::{eyre, Result};
|
||||||
|
|
||||||
use common::types::BlockTag;
|
use common::types::BlockTag;
|
||||||
|
@ -272,6 +272,10 @@ impl<DB: Database> Client<DB> {
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn get_logs(&self, filter: &Filter) -> Result<Vec<Log>> {
|
||||||
|
self.node.read().await.get_logs(filter).await
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn get_gas_price(&self) -> Result<U256> {
|
pub async fn get_gas_price(&self) -> Result<U256> {
|
||||||
self.node.read().await.get_gas_price()
|
self.node.read().await.get_gas_price()
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use ethers::prelude::{Address, U256};
|
use ethers::prelude::{Address, U256};
|
||||||
use ethers::types::{Transaction, TransactionReceipt, H256};
|
use ethers::types::{Filter, Log, Transaction, TransactionReceipt, H256};
|
||||||
use eyre::{eyre, Result};
|
use eyre::{eyre, Result};
|
||||||
|
|
||||||
use common::errors::BlockNotFoundError;
|
use common::errors::BlockNotFoundError;
|
||||||
|
@ -198,6 +198,10 @@ impl Node {
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn get_logs(&self, filter: &Filter) -> Result<Vec<Log>> {
|
||||||
|
self.execution.get_logs(filter, &self.payloads).await
|
||||||
|
}
|
||||||
|
|
||||||
// assumes tip of 1 gwei to prevent having to prove out every tx in the block
|
// assumes tip of 1 gwei to prevent having to prove out every tx in the block
|
||||||
pub fn get_gas_price(&self) -> Result<U256> {
|
pub fn get_gas_price(&self) -> Result<U256> {
|
||||||
self.check_head_age()?;
|
self.check_head_age()?;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use ethers::{
|
use ethers::{
|
||||||
abi::AbiEncode,
|
abi::AbiEncode,
|
||||||
types::{Address, Transaction, TransactionReceipt, H256},
|
types::{Address, Filter, Log, Transaction, TransactionReceipt, H256},
|
||||||
};
|
};
|
||||||
use eyre::Result;
|
use eyre::Result;
|
||||||
use log::info;
|
use log::info;
|
||||||
|
@ -92,6 +92,8 @@ trait EthRpc {
|
||||||
) -> Result<Option<TransactionReceipt>, Error>;
|
) -> Result<Option<TransactionReceipt>, Error>;
|
||||||
#[method(name = "getTransactionByHash")]
|
#[method(name = "getTransactionByHash")]
|
||||||
async fn get_transaction_by_hash(&self, hash: &str) -> Result<Option<Transaction>, Error>;
|
async fn get_transaction_by_hash(&self, hash: &str) -> Result<Option<Transaction>, Error>;
|
||||||
|
#[method(name = "getLogs")]
|
||||||
|
async fn get_logs(&self, filter: Filter) -> Result<Vec<Log>, Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rpc(client, server, namespace = "net")]
|
#[rpc(client, server, namespace = "net")]
|
||||||
|
@ -220,6 +222,11 @@ impl EthRpcServer for RpcInner {
|
||||||
let hash = H256::from_slice(&convert_err(hex_str_to_bytes(hash))?);
|
let hash = H256::from_slice(&convert_err(hex_str_to_bytes(hash))?);
|
||||||
convert_err(node.get_transaction_by_hash(&hash).await)
|
convert_err(node.get_transaction_by_hash(&hash).await)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn get_logs(&self, filter: Filter) -> Result<Vec<Log>, Error> {
|
||||||
|
let node = self.node.read().await;
|
||||||
|
convert_err(node.get_logs(&filter).await)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use ethers::{
|
use ethers::{
|
||||||
abi::AbiDecode,
|
abi::AbiDecode,
|
||||||
types::{Address, H256},
|
types::{Address, H256, U256},
|
||||||
};
|
};
|
||||||
use eyre::Report;
|
use eyre::Report;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
@ -18,6 +18,12 @@ pub enum ExecutionError {
|
||||||
ReceiptRootMismatch(String),
|
ReceiptRootMismatch(String),
|
||||||
#[error("missing transaction for tx: {0}")]
|
#[error("missing transaction for tx: {0}")]
|
||||||
MissingTransaction(String),
|
MissingTransaction(String),
|
||||||
|
#[error("could not prove receipt for tx: {0}")]
|
||||||
|
NoReceiptForTransaction(String),
|
||||||
|
#[error("missing log for transaction: {0}, index: {1}")]
|
||||||
|
MissingLog(String, U256),
|
||||||
|
#[error("too many logs to prove: {0}, current limit is: {1}")]
|
||||||
|
TooManyLogsToProve(usize, usize),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Errors that can occur during evm.rs calls
|
/// Errors that can occur during evm.rs calls
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
use std::{
|
use std::{
|
||||||
cmp,
|
|
||||||
collections::{BTreeMap, HashMap},
|
collections::{BTreeMap, HashMap},
|
||||||
str::FromStr,
|
str::FromStr,
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
|
|
|
@ -3,9 +3,9 @@ use std::str::FromStr;
|
||||||
|
|
||||||
use ethers::abi::AbiEncode;
|
use ethers::abi::AbiEncode;
|
||||||
use ethers::prelude::{Address, U256};
|
use ethers::prelude::{Address, U256};
|
||||||
use ethers::types::{Transaction, TransactionReceipt, H256};
|
use ethers::types::{Filter, Log, Transaction, TransactionReceipt, H256};
|
||||||
use ethers::utils::keccak256;
|
use ethers::utils::keccak256;
|
||||||
use ethers::utils::rlp::{encode, RlpStream};
|
use ethers::utils::rlp::{encode, Encodable, RlpStream};
|
||||||
use eyre::Result;
|
use eyre::Result;
|
||||||
|
|
||||||
use common::utils::hex_str_to_bytes;
|
use common::utils::hex_str_to_bytes;
|
||||||
|
@ -21,6 +21,10 @@ use super::proof::{encode_account, verify_proof};
|
||||||
use super::rpc::ExecutionRpc;
|
use super::rpc::ExecutionRpc;
|
||||||
use super::types::{Account, ExecutionBlock};
|
use super::types::{Account, ExecutionBlock};
|
||||||
|
|
||||||
|
// We currently limit the max number of logs to fetch,
|
||||||
|
// to avoid blocking the client for too long.
|
||||||
|
const MAX_SUPPORTED_LOGS_NUMBER: usize = 5;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct ExecutionClient<R: ExecutionRpc> {
|
pub struct ExecutionClient<R: ExecutionRpc> {
|
||||||
pub rpc: R,
|
pub rpc: R,
|
||||||
|
@ -262,6 +266,51 @@ impl<R: ExecutionRpc> ExecutionClient<R> {
|
||||||
|
|
||||||
Ok(Some(tx))
|
Ok(Some(tx))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn get_logs(
|
||||||
|
&self,
|
||||||
|
filter: &Filter,
|
||||||
|
payloads: &BTreeMap<u64, ExecutionPayload>,
|
||||||
|
) -> Result<Vec<Log>> {
|
||||||
|
let logs = self.rpc.get_logs(filter).await?;
|
||||||
|
if logs.len() > MAX_SUPPORTED_LOGS_NUMBER {
|
||||||
|
return Err(
|
||||||
|
ExecutionError::TooManyLogsToProve(logs.len(), MAX_SUPPORTED_LOGS_NUMBER).into(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (_pos, log) in logs.iter().enumerate() {
|
||||||
|
// For every log
|
||||||
|
// Get the hash of the tx that generated it
|
||||||
|
let tx_hash = log
|
||||||
|
.transaction_hash
|
||||||
|
.ok_or(eyre::eyre!("tx hash not found in log"))?;
|
||||||
|
// Get its proven receipt
|
||||||
|
let receipt = self
|
||||||
|
.get_transaction_receipt(&tx_hash, payloads)
|
||||||
|
.await?
|
||||||
|
.ok_or(ExecutionError::NoReceiptForTransaction(tx_hash.to_string()))?;
|
||||||
|
|
||||||
|
// Check if the receipt contains the desired log
|
||||||
|
// Encoding logs for comparison
|
||||||
|
let receipt_logs_encoded = receipt
|
||||||
|
.logs
|
||||||
|
.iter()
|
||||||
|
.map(|log| log.rlp_bytes())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let log_encoded = log.rlp_bytes();
|
||||||
|
|
||||||
|
if !receipt_logs_encoded.contains(&log_encoded) {
|
||||||
|
return Err(ExecutionError::MissingLog(
|
||||||
|
tx_hash.to_string(),
|
||||||
|
log.log_index.unwrap(),
|
||||||
|
)
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Ok(logs);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn encode_receipt(receipt: &TransactionReceipt) -> Vec<u8> {
|
fn encode_receipt(receipt: &TransactionReceipt) -> Vec<u8> {
|
||||||
|
|
|
@ -7,7 +7,7 @@ use ethers::providers::{HttpRateLimitRetryPolicy, Middleware, Provider, RetryCli
|
||||||
use ethers::types::transaction::eip2718::TypedTransaction;
|
use ethers::types::transaction::eip2718::TypedTransaction;
|
||||||
use ethers::types::transaction::eip2930::AccessList;
|
use ethers::types::transaction::eip2930::AccessList;
|
||||||
use ethers::types::{
|
use ethers::types::{
|
||||||
BlockId, Bytes, EIP1186ProofResponse, Eip1559TransactionRequest, Transaction,
|
BlockId, Bytes, EIP1186ProofResponse, Eip1559TransactionRequest, Filter, Log, Transaction,
|
||||||
TransactionReceipt, H256, U256,
|
TransactionReceipt, H256, U256,
|
||||||
};
|
};
|
||||||
use eyre::Result;
|
use eyre::Result;
|
||||||
|
@ -120,4 +120,12 @@ impl ExecutionRpc for HttpRpc {
|
||||||
.await
|
.await
|
||||||
.map_err(|e| RpcError::new("get_transaction", e))?)
|
.map_err(|e| RpcError::new("get_transaction", e))?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn get_logs(&self, filter: &Filter) -> Result<Vec<Log>> {
|
||||||
|
Ok(self
|
||||||
|
.provider
|
||||||
|
.get_logs(filter)
|
||||||
|
.await
|
||||||
|
.map_err(|e| RpcError::new("get_logs", e))?)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ use std::{fs::read_to_string, path::PathBuf};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use common::utils::hex_str_to_bytes;
|
use common::utils::hex_str_to_bytes;
|
||||||
use ethers::types::{
|
use ethers::types::{
|
||||||
transaction::eip2930::AccessList, Address, EIP1186ProofResponse, Transaction,
|
transaction::eip2930::AccessList, Address, EIP1186ProofResponse, Filter, Log, Transaction,
|
||||||
TransactionReceipt, H256,
|
TransactionReceipt, H256,
|
||||||
};
|
};
|
||||||
use eyre::{eyre, Result};
|
use eyre::{eyre, Result};
|
||||||
|
@ -56,4 +56,9 @@ impl ExecutionRpc for MockRpc {
|
||||||
let tx = read_to_string(self.path.join("transaction.json"))?;
|
let tx = read_to_string(self.path.join("transaction.json"))?;
|
||||||
Ok(serde_json::from_str(&tx)?)
|
Ok(serde_json::from_str(&tx)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn get_logs(&self, _filter: &Filter) -> Result<Vec<Log>> {
|
||||||
|
let logs = read_to_string(self.path.join("logs.json"))?;
|
||||||
|
Ok(serde_json::from_str(&logs)?)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use ethers::types::{
|
use ethers::types::{
|
||||||
transaction::eip2930::AccessList, Address, EIP1186ProofResponse, Transaction,
|
transaction::eip2930::AccessList, Address, EIP1186ProofResponse, Filter, Log, Transaction,
|
||||||
TransactionReceipt, H256,
|
TransactionReceipt, H256,
|
||||||
};
|
};
|
||||||
use eyre::Result;
|
use eyre::Result;
|
||||||
|
@ -28,4 +28,5 @@ pub trait ExecutionRpc: Send + Clone + Sync + 'static {
|
||||||
async fn send_raw_transaction(&self, bytes: &Vec<u8>) -> Result<H256>;
|
async fn send_raw_transaction(&self, bytes: &Vec<u8>) -> Result<H256>;
|
||||||
async fn get_transaction_receipt(&self, tx_hash: &H256) -> Result<Option<TransactionReceipt>>;
|
async fn get_transaction_receipt(&self, tx_hash: &H256) -> Result<Option<TransactionReceipt>>;
|
||||||
async fn get_transaction(&self, tx_hash: &H256) -> Result<Option<Transaction>>;
|
async fn get_transaction(&self, tx_hash: &H256) -> Result<Option<Transaction>>;
|
||||||
|
async fn get_logs(&self, filter: &Filter) -> Result<Vec<Log>>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"transactionHash": "0x2dac1b27ab58b493f902dda8b63979a112398d747f1761c0891777c0983e591f",
|
||||||
|
"address": "0x326c977e6efc84e512bb9c30f76e30c160ed06fb",
|
||||||
|
"blockHash": "0x6663f197e991f5a0bb235f33ec554b9bd48c37b4f5002d7ac2abdfa99f86ac14",
|
||||||
|
"blockNumber": "0x72e9b5",
|
||||||
|
"data": "0x000000000000000000000000000000000000000000000001158e460913d00000",
|
||||||
|
"logIndex": "0x0",
|
||||||
|
"removed": false,
|
||||||
|
"topics": [
|
||||||
|
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
|
||||||
|
"0x0000000000000000000000004281ecf07378ee595c564a59048801330f3084ee",
|
||||||
|
"0x0000000000000000000000007daccf9b3c1ae2fa5c55f1c978aeef700bc83be0"
|
||||||
|
],
|
||||||
|
"transactionIndex": "0x0"
|
||||||
|
}
|
||||||
|
]
|
|
@ -1,7 +1,7 @@
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use ethers::types::{Address, H256, U256};
|
use ethers::types::{Address, Filter, H256, U256};
|
||||||
use ssz_rs::{List, Vector};
|
use ssz_rs::{List, Vector};
|
||||||
|
|
||||||
use common::utils::hex_str_to_bytes;
|
use common::utils::hex_str_to_bytes;
|
||||||
|
@ -99,6 +99,31 @@ async fn test_get_tx_not_included() {
|
||||||
assert!(tx_opt.is_none());
|
assert!(tx_opt.is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_get_logs() {
|
||||||
|
let execution = get_client();
|
||||||
|
let mut payload = ExecutionPayload::default();
|
||||||
|
payload.receipts_root = Vector::from_iter(
|
||||||
|
hex_str_to_bytes("dd82a78eccb333854f0c99e5632906e092d8a49c27a21c25cae12b82ec2a113f")
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
|
||||||
|
payload.transactions.push(List::from_iter(hex_str_to_bytes("0x02f8b20583623355849502f900849502f91082ea6094326c977e6efc84e512bb9c30f76e30c160ed06fb80b844a9059cbb0000000000000000000000007daccf9b3c1ae2fa5c55f1c978aeef700bc83be0000000000000000000000000000000000000000000000001158e460913d00000c080a0e1445466b058b6f883c0222f1b1f3e2ad9bee7b5f688813d86e3fa8f93aa868ca0786d6e7f3aefa8fe73857c65c32e4884d8ba38d0ecfb947fbffb82e8ee80c167").unwrap()));
|
||||||
|
|
||||||
|
let mut payloads = BTreeMap::new();
|
||||||
|
payloads.insert(7530933, payload);
|
||||||
|
|
||||||
|
let filter = Filter::new();
|
||||||
|
let logs = execution.get_logs(&filter, &payloads).await.unwrap();
|
||||||
|
|
||||||
|
let tx_hash =
|
||||||
|
H256::from_str("2dac1b27ab58b493f902dda8b63979a112398d747f1761c0891777c0983e591f").unwrap();
|
||||||
|
|
||||||
|
assert!(!logs.is_empty());
|
||||||
|
assert!(logs[0].transaction_hash.is_some());
|
||||||
|
assert!(logs[0].transaction_hash.unwrap() == tx_hash);
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_get_receipt() {
|
async fn test_get_receipt() {
|
||||||
let execution = get_client();
|
let execution = get_client();
|
||||||
|
|
Loading…
Reference in New Issue