Skip to content

Commit 350c1da

Browse files
committed
Implement Filecoin.EthGetFilterChanges (#5383)
1 parent beca384 commit 350c1da

File tree

8 files changed

+209
-12
lines changed

8 files changed

+209
-12
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@
3333

3434
- [#5386](https://github.com/ChainSafe/forest/pull/5386) Add support for the `Filecoin.EthTraceTransaction` RPC method.
3535

36+
- [#5383](https://github.com/ChainSafe/forest/pull/5383) Add support for `Filecoin.EthGetFilterChanges` RPC method.
37+
3638
### Changed
3739

3840
### Removed

src/rpc/methods/eth.rs

+162-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ use crate::interpreter::VMTrace;
2424
use crate::lotus_json::{lotus_json_with_self, HasLotusJson};
2525
use crate::message::{ChainMessage, Message as _, SignedMessage};
2626
use crate::rpc::error::ServerError;
27-
use crate::rpc::eth::filter::{event::EventFilter, SkipEvent};
27+
use crate::rpc::eth::filter::{
28+
event::EventFilter, mempool::MempoolFilter, tipset::TipSetFilter, SkipEvent,
29+
};
2830
use crate::rpc::eth::types::{EthBlockTrace, EthTrace};
2931
use crate::rpc::types::{ApiTipsetKey, EventEntry, MessageLookup};
3032
use crate::rpc::EthEventHandler;
@@ -50,13 +52,15 @@ use crate::utils::encoding::from_slice_with_fallback;
5052
use crate::utils::multihash::prelude::*;
5153
use anyhow::{anyhow, bail, Context, Error, Result};
5254
use cid::Cid;
55+
use filter::{ParsedFilter, ParsedFilterTipsets};
5356
use fvm_ipld_blockstore::Blockstore;
5457
use fvm_ipld_encoding::{RawBytes, CBOR, DAG_CBOR, IPLD_RAW};
5558
use ipld_core::ipld::Ipld;
5659
use itertools::Itertools;
5760
use num::{BigInt, Zero as _};
5861
use schemars::JsonSchema;
5962
use serde::{Deserialize, Serialize};
63+
use std::ops::RangeInclusive;
6064
use std::str::FromStr;
6165
use std::sync::Arc;
6266
use utils::{decode_payload, lookup_eth_address};
@@ -2730,6 +2734,36 @@ where
27302734
.collect()
27312735
}
27322736

2737+
fn eth_filter_logs_from_tipsets(events: &[CollectedEvent]) -> anyhow::Result<Vec<EthHash>> {
2738+
events
2739+
.iter()
2740+
.map(|event| event.tipset_key.cid().map(Into::into))
2741+
.collect()
2742+
}
2743+
2744+
fn eth_filter_logs_from_messages<DB: Blockstore>(
2745+
ctx: &Ctx<DB>,
2746+
events: &[CollectedEvent],
2747+
) -> anyhow::Result<Vec<EthHash>> {
2748+
events
2749+
.iter()
2750+
.filter_map(|event| {
2751+
match eth_tx_hash_from_message_cid(
2752+
ctx.store(),
2753+
&event.msg_cid,
2754+
ctx.state_manager.chain_config().eth_chain_id,
2755+
) {
2756+
Ok(Some(hash)) => Some(Ok(hash)),
2757+
Ok(None) => {
2758+
tracing::warn!("Ignoring event");
2759+
None
2760+
}
2761+
Err(err) => Some(Err(err)),
2762+
}
2763+
})
2764+
.collect()
2765+
}
2766+
27332767
fn eth_filter_logs_from_events<DB: Blockstore>(
27342768
ctx: &Ctx<DB>,
27352769
events: &[CollectedEvent],
@@ -2775,6 +2809,19 @@ fn eth_filter_result_from_events<DB: Blockstore>(
27752809
)?))
27762810
}
27772811

2812+
fn eth_filter_result_from_tipsets(events: &[CollectedEvent]) -> anyhow::Result<EthFilterResult> {
2813+
Ok(EthFilterResult::Txs(eth_filter_logs_from_tipsets(events)?))
2814+
}
2815+
2816+
fn eth_filter_result_from_messages<DB: Blockstore>(
2817+
ctx: &Ctx<DB>,
2818+
events: &[CollectedEvent],
2819+
) -> anyhow::Result<EthFilterResult> {
2820+
Ok(EthFilterResult::Txs(eth_filter_logs_from_messages(
2821+
ctx, events,
2822+
)?))
2823+
}
2824+
27782825
pub enum EthGetLogs {}
27792826
impl RpcMethod<1> for EthGetLogs {
27802827
const NAME: &'static str = "Filecoin.EthGetLogs";
@@ -2847,6 +2894,120 @@ impl RpcMethod<1> for EthGetFilterLogs {
28472894
}
28482895
}
28492896

2897+
pub enum EthGetFilterChanges {}
2898+
impl RpcMethod<1> for EthGetFilterChanges {
2899+
const NAME: &'static str = "Filecoin.EthGetFilterChanges";
2900+
const NAME_ALIAS: Option<&'static str> = Some("eth_getFilterChanges");
2901+
const N_REQUIRED_PARAMS: usize = 1;
2902+
const PARAM_NAMES: [&'static str; 1] = ["filterId"];
2903+
const API_PATHS: ApiPaths = ApiPaths::V1;
2904+
const PERMISSION: Permission = Permission::Write;
2905+
const DESCRIPTION: Option<&'static str> =
2906+
Some("Returns event logs which occured since the last poll");
2907+
2908+
type Params = (FilterID,);
2909+
type Ok = EthFilterResult;
2910+
async fn handle(
2911+
ctx: Ctx<impl Blockstore + Send + Sync + 'static>,
2912+
(filter_id,): Self::Params,
2913+
) -> Result<Self::Ok, ServerError> {
2914+
let eth_event_handler = ctx.eth_event_handler.clone();
2915+
if let Some(store) = &eth_event_handler.filter_store {
2916+
let filter = store.get(&filter_id)?;
2917+
if let Some(event_filter) = filter.as_any().downcast_ref::<EventFilter>() {
2918+
let events = ctx
2919+
.eth_event_handler
2920+
.get_events_for_parsed_filter(
2921+
&ctx,
2922+
&event_filter.into(),
2923+
SkipEvent::OnUnresolvedAddress,
2924+
)
2925+
.await?;
2926+
let recent_events: Vec<CollectedEvent> = events
2927+
.clone()
2928+
.into_iter()
2929+
.filter(|event| !event_filter.collected.contains(event))
2930+
.collect();
2931+
let filter = Arc::new(EventFilter {
2932+
id: event_filter.id.clone(),
2933+
tipsets: event_filter.tipsets.clone(),
2934+
addresses: event_filter.addresses.clone(),
2935+
keys_with_codec: event_filter.keys_with_codec.clone(),
2936+
max_results: event_filter.max_results,
2937+
collected: events.clone(),
2938+
});
2939+
store.update(filter);
2940+
return Ok(eth_filter_result_from_events(&ctx, &recent_events)?);
2941+
}
2942+
if let Some(tipset_filter) = filter.as_any().downcast_ref::<TipSetFilter>() {
2943+
let events = ctx
2944+
.eth_event_handler
2945+
.get_events_for_parsed_filter(
2946+
&ctx,
2947+
&ParsedFilter::new_with_tipset(ParsedFilterTipsets::Range(
2948+
// heaviest tipset doesn't have events because its messages haven't been executed yet
2949+
RangeInclusive::new(
2950+
tipset_filter
2951+
.collected
2952+
.unwrap_or(ctx.chain_store().heaviest_tipset().epoch() - 1),
2953+
// Use -1 to indicate that the range extends until the latest available tipset.
2954+
-1,
2955+
),
2956+
)),
2957+
SkipEvent::OnUnresolvedAddress,
2958+
)
2959+
.await?;
2960+
let new_collected = events
2961+
.iter()
2962+
.max_by_key(|event| event.height)
2963+
.map(|e| e.height);
2964+
if let Some(height) = new_collected {
2965+
let filter = Arc::new(TipSetFilter {
2966+
id: tipset_filter.id.clone(),
2967+
max_results: tipset_filter.max_results,
2968+
collected: Some(height),
2969+
});
2970+
store.update(filter);
2971+
}
2972+
return Ok(eth_filter_result_from_tipsets(&events)?);
2973+
}
2974+
if let Some(mempool_filter) = filter.as_any().downcast_ref::<MempoolFilter>() {
2975+
let events = ctx
2976+
.eth_event_handler
2977+
.get_events_for_parsed_filter(
2978+
&ctx,
2979+
&ParsedFilter::new_with_tipset(ParsedFilterTipsets::Range(
2980+
// heaviest tipset doesn't have events because its messages haven't been executed yet
2981+
RangeInclusive::new(
2982+
mempool_filter
2983+
.collected
2984+
.unwrap_or(ctx.chain_store().heaviest_tipset().epoch() - 1),
2985+
// Use -1 to indicate that the range extends until the latest available tipset.
2986+
-1,
2987+
),
2988+
)),
2989+
SkipEvent::OnUnresolvedAddress,
2990+
)
2991+
.await?;
2992+
let new_collected = events
2993+
.iter()
2994+
.max_by_key(|event| event.height)
2995+
.map(|e| e.height);
2996+
if let Some(height) = new_collected {
2997+
let filter = Arc::new(MempoolFilter {
2998+
id: mempool_filter.id.clone(),
2999+
max_results: mempool_filter.max_results,
3000+
collected: Some(height),
3001+
});
3002+
store.update(filter);
3003+
}
3004+
return Ok(eth_filter_result_from_messages(&ctx, &events)?);
3005+
}
3006+
}
3007+
Err(anyhow::anyhow!("method not supported").into())
3008+
}
3009+
}
3010+
28503011
pub enum EthTraceBlock {}
28513012
impl RpcMethod<1> for EthTraceBlock {
28523013
const NAME: &'static str = "Filecoin.EthTraceBlock";

src/rpc/methods/eth/filter/event.rs

+9-3
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,17 @@ use std::any::Any;
1313
#[allow(dead_code)]
1414
#[derive(Debug, PartialEq)]
1515
pub struct EventFilter {
16+
// Unique id used to identify the filter
1617
pub id: FilterID,
18+
// Tipsets to filter
1719
pub tipsets: ParsedFilterTipsets,
18-
pub addresses: Vec<Address>, // list of actor addresses that are extpected to emit the event
19-
pub keys_with_codec: HashMap<String, Vec<ActorEventBlock>>, // map of key names to a list of alternate values that may match
20-
pub max_results: usize, // maximum number of results to collect
20+
// list of actor addresses that are extpected to emit the event
21+
pub addresses: Vec<Address>,
22+
// Map of key names to a list of alternate values that may match
23+
pub keys_with_codec: HashMap<String, Vec<ActorEventBlock>>,
24+
// Maximum number of results to collect
25+
pub max_results: usize,
26+
// Collected events
2127
pub collected: Vec<CollectedEvent>,
2228
}
2329

src/rpc/methods/eth/filter/mempool.rs

+12-3
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
use crate::rpc::eth::{filter::Filter, filter::FilterManager, FilterID};
55
use crate::rpc::Arc;
6+
use crate::shim::fvm_shared_latest::clock::ChainEpoch;
67
use ahash::AHashMap as HashMap;
78
use anyhow::{Context, Result};
89
use parking_lot::RwLock;
@@ -13,14 +14,22 @@ use std::any::Any;
1314
#[allow(dead_code)]
1415
#[derive(Debug, PartialEq)]
1516
pub struct MempoolFilter {
16-
id: FilterID, // Unique id used to identify the filter
17-
max_results: usize, // maximum number of results to collect
17+
// Unique id used to identify the filter
18+
pub id: FilterID,
19+
// Maximum number of results to collect
20+
pub max_results: usize,
21+
// Epoch at which the results were collected
22+
pub collected: Option<ChainEpoch>,
1823
}
1924

2025
impl MempoolFilter {
2126
pub fn new(max_results: usize) -> Result<Arc<Self>, uuid::Error> {
2227
let id = FilterID::new()?;
23-
Ok(Arc::new(Self { id, max_results }))
28+
Ok(Arc::new(Self {
29+
id,
30+
max_results,
31+
collected: None,
32+
}))
2433
}
2534
}
2635

src/rpc/methods/eth/filter/mod.rs

+9-2
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@
1515
//! - **TipSet Filter**: Tracks changes in the blockchain's tipset (the latest set of blocks).
1616
//! - **Mempool Filter**: Monitors the Ethereum mempool for new pending transactions that meet certain criteria.
1717
pub mod event;
18-
mod mempool;
18+
pub mod mempool;
1919
mod store;
20-
mod tipset;
20+
pub mod tipset;
2121

2222
use super::get_tipset_from_hash;
2323
use super::BlockNumberOrHash;
@@ -643,6 +643,13 @@ pub struct ParsedFilter {
643643
}
644644

645645
impl ParsedFilter {
646+
pub fn new_with_tipset(tipsets: ParsedFilterTipsets) -> Self {
647+
ParsedFilter {
648+
tipsets,
649+
addresses: vec![],
650+
keys: HashMap::new(),
651+
}
652+
}
646653
pub fn from_actor_event_filter(
647654
chain_height: ChainEpoch,
648655
_max_filter_height_range: ChainEpoch,

src/rpc/methods/eth/filter/tipset.rs

+12-3
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
use crate::rpc::eth::{filter::Filter, filter::FilterManager, FilterID};
55
use crate::rpc::Arc;
6+
use crate::shim::fvm_shared_latest::clock::ChainEpoch;
67
use ahash::AHashMap as HashMap;
78
use anyhow::{Context, Result};
89
use parking_lot::RwLock;
@@ -11,14 +12,22 @@ use std::any::Any;
1112
#[allow(dead_code)]
1213
#[derive(Debug, PartialEq)]
1314
pub struct TipSetFilter {
14-
id: FilterID,
15-
max_results: usize,
15+
// Unique id used to identify the filter
16+
pub id: FilterID,
17+
// Maximum number of results to collect
18+
pub max_results: usize,
19+
// Epoch at which the results were collected
20+
pub collected: Option<ChainEpoch>,
1621
}
1722

1823
impl TipSetFilter {
1924
pub fn new(max_results: usize) -> Result<Arc<Self>, uuid::Error> {
2025
let id = FilterID::new()?;
21-
Ok(Arc::new(Self { id, max_results }))
26+
Ok(Arc::new(Self {
27+
id,
28+
max_results,
29+
collected: None,
30+
}))
2231
}
2332
}
2433

src/rpc/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ macro_rules! for_each_rpc_method {
9292
$callback!($crate::rpc::eth::EthGetCode);
9393
$callback!($crate::rpc::eth::EthGetLogs);
9494
$callback!($crate::rpc::eth::EthGetFilterLogs);
95+
$callback!($crate::rpc::eth::EthGetFilterChanges);
9596
$callback!($crate::rpc::eth::EthGetMessageCidByTransactionHash);
9697
$callback!($crate::rpc::eth::EthGetStorageAt);
9798
$callback!($crate::rpc::eth::EthGetTransactionByHash);

src/tool/subcommands/api_cmd/api_compare_tests.rs

+2
Original file line numberDiff line numberDiff line change
@@ -1643,6 +1643,8 @@ fn eth_tests_with_tipset<DB: Blockstore>(store: &Arc<DB>, shared_tipset: &Tipset
16431643
.sort_policy(SortPolicy::All),
16441644
RpcTest::identity(EthGetFilterLogs::request((FilterID::new().unwrap(),)).unwrap())
16451645
.policy_on_rejected(PolicyOnRejected::PassWithIdenticalError),
1646+
RpcTest::identity(EthGetFilterChanges::request((FilterID::new().unwrap(),)).unwrap())
1647+
.policy_on_rejected(PolicyOnRejected::PassWithIdenticalError),
16461648
RpcTest::identity(EthGetTransactionHashByCid::request((block_cid,)).unwrap()),
16471649
RpcTest::identity(
16481650
EthTraceBlock::request((ExtBlockNumberOrHash::from_block_number(

0 commit comments

Comments
 (0)