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:
Simon Saliba 2022-11-17 18:14:13 +01:00 committed by GitHub
parent eaf5605d4d
commit 3177ad55c1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 136 additions and 11 deletions

View File

@ -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()
} }

View File

@ -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()?;

View File

@ -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]

View File

@ -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

View File

@ -1,5 +1,4 @@
use std::{ use std::{
cmp,
collections::{BTreeMap, HashMap}, collections::{BTreeMap, HashMap},
str::FromStr, str::FromStr,
sync::Arc, sync::Arc,

View File

@ -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> {

View File

@ -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))?)
}
} }

View File

@ -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)?)
}
} }

View File

@ -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>>;
} }

17
execution/testdata/logs.json vendored Normal file
View File

@ -0,0 +1,17 @@
[
{
"transactionHash": "0x2dac1b27ab58b493f902dda8b63979a112398d747f1761c0891777c0983e591f",
"address": "0x326c977e6efc84e512bb9c30f76e30c160ed06fb",
"blockHash": "0x6663f197e991f5a0bb235f33ec554b9bd48c37b4f5002d7ac2abdfa99f86ac14",
"blockNumber": "0x72e9b5",
"data": "0x000000000000000000000000000000000000000000000001158e460913d00000",
"logIndex": "0x0",
"removed": false,
"topics": [
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
"0x0000000000000000000000004281ecf07378ee595c564a59048801330f3084ee",
"0x0000000000000000000000007daccf9b3c1ae2fa5c55f1c978aeef700bc83be0"
],
"transactionIndex": "0x0"
}
]

View File

@ -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();