Skip to main content

forest/rpc/methods/
mpool.rs

1// Copyright 2019-2026 ChainSafe Systems
2// SPDX-License-Identifier: Apache-2.0, MIT
3
4use super::gas::estimate_message_gas;
5use crate::lotus_json::{LotusJson, NotNullVec, lotus_json_with_self};
6use crate::message::SignedMessage;
7use crate::prelude::*;
8use crate::rpc::error::ServerError;
9use crate::rpc::types::{ApiTipsetKey, MessageSendSpec};
10use crate::rpc::{ApiPaths, Ctx, Permission, RpcMethod};
11use crate::shim::{
12    address::{Address, Protocol},
13    message::Message,
14    percent::Percent,
15};
16use ahash::HashSet;
17use enumflags2::BitFlags;
18use schemars::JsonSchema;
19use serde::{Deserialize, Serialize};
20use std::time::Duration;
21
22#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
23#[serde(rename_all = "PascalCase")]
24pub struct ApiMpoolConfig {
25    #[schemars(with = "LotusJson<Vec<Address>>")]
26    #[serde(with = "crate::lotus_json")]
27    pub priority_addrs: Vec<Address>,
28    pub size_limit_high: i64,
29    pub size_limit_low: i64,
30    #[serde(with = "crate::lotus_json")]
31    #[schemars(with = "LotusJson<Percent>")]
32    pub replace_by_fee_ratio: Percent,
33    #[schemars(with = "LotusJson<Duration>")]
34    #[serde(with = "crate::lotus_json")]
35    pub prune_cooldown: Duration,
36    pub gas_limit_overestimation: f64,
37}
38
39lotus_json_with_self!(ApiMpoolConfig);
40
41/// Returns a copy of the current mpool config.
42pub enum MpoolGetConfig {}
43impl RpcMethod<0> for MpoolGetConfig {
44    const NAME: &'static str = "Filecoin.MpoolGetConfig";
45    const PARAM_NAMES: [&'static str; 0] = [];
46    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
47    const PERMISSION: Permission = Permission::Read;
48    const DESCRIPTION: &'static str = "Returns a copy of the current mpool config.";
49
50    type Params = ();
51    type Ok = ApiMpoolConfig;
52
53    async fn handle(
54        ctx: Ctx,
55        (): Self::Params,
56        _: &http::Extensions,
57    ) -> Result<Self::Ok, ServerError> {
58        let cfg = ctx.mpool.config();
59        Ok(ApiMpoolConfig {
60            priority_addrs: cfg.priority_addrs,
61            size_limit_high: cfg.size_limit_high,
62            size_limit_low: cfg.size_limit_low,
63            replace_by_fee_ratio: cfg.replace_by_fee_ratio,
64            prune_cooldown: cfg.prune_cooldown,
65            gas_limit_overestimation: cfg.gas_limit_overestimation,
66        })
67    }
68}
69
70/// Gets next nonce for the specified sender.
71pub enum MpoolGetNonce {}
72impl RpcMethod<1> for MpoolGetNonce {
73    const NAME: &'static str = "Filecoin.MpoolGetNonce";
74    const PARAM_NAMES: [&'static str; 1] = ["address"];
75    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
76    const PERMISSION: Permission = Permission::Read;
77    const DESCRIPTION: &'static str = "Returns the current nonce for the specified address.";
78
79    type Params = (Address,);
80    type Ok = u64;
81
82    async fn handle(
83        ctx: Ctx,
84        (address,): Self::Params,
85        _: &http::Extensions,
86    ) -> Result<Self::Ok, ServerError> {
87        Ok(ctx.mpool.get_sequence(&address).await?)
88    }
89}
90
91/// Return `Vec` of pending messages in `mpool`
92pub enum MpoolPending {}
93impl RpcMethod<1> for MpoolPending {
94    const NAME: &'static str = "Filecoin.MpoolPending";
95    const PARAM_NAMES: [&'static str; 1] = ["tipsetKey"];
96    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
97    const PERMISSION: Permission = Permission::Read;
98    const DESCRIPTION: &'static str = "Returns the pending messages for a given tipset.";
99
100    type Params = (ApiTipsetKey,);
101    type Ok = NotNullVec<SignedMessage>;
102
103    async fn handle(
104        ctx: Ctx,
105        (ApiTipsetKey(tipset_key),): Self::Params,
106        _: &http::Extensions,
107    ) -> Result<Self::Ok, ServerError> {
108        let mut ts = ctx
109            .chain_store()
110            .load_required_tipset_or_heaviest(&tipset_key)?;
111
112        let (mut pending, mpts) = ctx.mpool.pending();
113
114        let mut have_cids = HashSet::new();
115        for item in pending.iter() {
116            have_cids.insert(item.cid());
117        }
118
119        if mpts.epoch() > ts.epoch() {
120            return Ok(NotNullVec(pending.into_iter().collect()));
121        }
122
123        loop {
124            if mpts.epoch() == ts.epoch() {
125                if mpts == ts {
126                    break;
127                }
128
129                // mpts has different blocks than ts
130                let have = ctx.mpool.messages_for_blocks(ts.block_headers().iter())?;
131
132                for sm in have {
133                    have_cids.insert(sm.cid());
134                }
135            }
136
137            let msgs = ctx.mpool.messages_for_blocks(ts.block_headers().iter())?;
138
139            for m in msgs {
140                if have_cids.contains(&m.cid()) {
141                    continue;
142                }
143
144                have_cids.insert(m.cid());
145                pending.push(m);
146            }
147
148            if mpts.epoch() >= ts.epoch() {
149                break;
150            }
151
152            ts = ctx.chain_index().load_required_tipset(ts.parents())?;
153        }
154        Ok(NotNullVec(pending.into_iter().collect()))
155    }
156}
157
158/// Return `Vec` of pending messages for inclusion in the next block
159pub enum MpoolSelect {}
160impl RpcMethod<2> for MpoolSelect {
161    const NAME: &'static str = "Filecoin.MpoolSelect";
162    const PARAM_NAMES: [&'static str; 2] = ["tipsetKey", "ticketQuality"];
163    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
164    const PERMISSION: Permission = Permission::Read;
165    const DESCRIPTION: &'static str =
166        "Returns a list of pending messages for inclusion in the next block.";
167
168    type Params = (ApiTipsetKey, f64);
169    type Ok = Vec<SignedMessage>;
170
171    async fn handle(
172        ctx: Ctx,
173        (ApiTipsetKey(tipset_key), ticket_quality): Self::Params,
174        _: &http::Extensions,
175    ) -> Result<Self::Ok, ServerError> {
176        let ts = ctx
177            .chain_store()
178            .load_required_tipset_or_heaviest(&tipset_key)?;
179        Ok(ctx.mpool.select_messages(&ts, ticket_quality)?)
180    }
181}
182
183/// Add `SignedMessage` to `mpool`, return message CID
184pub enum MpoolPush {}
185impl RpcMethod<1> for MpoolPush {
186    const NAME: &'static str = "Filecoin.MpoolPush";
187    const PARAM_NAMES: [&'static str; 1] = ["message"];
188    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
189    const PERMISSION: Permission = Permission::Write;
190    const DESCRIPTION: &'static str = "Adds a signed message to the message pool.";
191
192    type Params = (SignedMessage,);
193    type Ok = Cid;
194
195    async fn handle(
196        ctx: Ctx,
197        (message,): Self::Params,
198        _: &http::Extensions,
199    ) -> Result<Self::Ok, ServerError> {
200        let cid = ctx.mpool.push(message).await?;
201        Ok(cid)
202    }
203}
204
205/// Add a batch of `SignedMessage`s to `mpool`, return message CIDs
206pub enum MpoolBatchPush {}
207impl RpcMethod<1> for MpoolBatchPush {
208    const NAME: &'static str = "Filecoin.MpoolBatchPush";
209    const PARAM_NAMES: [&'static str; 1] = ["messages"];
210    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
211    const PERMISSION: Permission = Permission::Write;
212    const DESCRIPTION: &'static str = "Adds a set of signed messages to the message pool.";
213
214    type Params = (Vec<SignedMessage>,);
215    type Ok = Vec<Cid>;
216
217    async fn handle(
218        ctx: Ctx,
219        (messages,): Self::Params,
220        _: &http::Extensions,
221    ) -> Result<Self::Ok, ServerError> {
222        let mut cids = vec![];
223        for msg in messages {
224            cids.push(ctx.mpool.push(msg).await?);
225        }
226        Ok(cids)
227    }
228}
229
230/// Add `SignedMessage` from untrusted source to `mpool`, return message CID
231pub enum MpoolPushUntrusted {}
232impl RpcMethod<1> for MpoolPushUntrusted {
233    const NAME: &'static str = "Filecoin.MpoolPushUntrusted";
234    const PARAM_NAMES: [&'static str; 1] = ["message"];
235    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
236    const PERMISSION: Permission = Permission::Write;
237    const DESCRIPTION: &'static str =
238        "Adds a message to the message pool with verification checks.";
239
240    type Params = (SignedMessage,);
241    type Ok = Cid;
242
243    async fn handle(
244        ctx: Ctx,
245        (message,): Self::Params,
246        _: &http::Extensions,
247    ) -> Result<Self::Ok, ServerError> {
248        // Lotus implements a few extra sanity checks that we skip. We skip them
249        // because those checks aren't used for messages received from peers and
250        // therefore aren't safety critical.
251        let cid = ctx.mpool.push_untrusted(message).await?;
252        Ok(cid)
253    }
254}
255
256/// Add a batch of `SignedMessage`s to `mpool`, return message CIDs
257pub enum MpoolBatchPushUntrusted {}
258impl RpcMethod<1> for MpoolBatchPushUntrusted {
259    const NAME: &'static str = "Filecoin.MpoolBatchPushUntrusted";
260    const PARAM_NAMES: [&'static str; 1] = ["messages"];
261    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
262    const PERMISSION: Permission = Permission::Write;
263    const DESCRIPTION: &'static str =
264        "Adds a set of messages to the message pool with additional verification checks.";
265
266    type Params = (Vec<SignedMessage>,);
267    type Ok = Vec<Cid>;
268
269    async fn handle(
270        ctx: Ctx,
271        (messages,): Self::Params,
272        ext: &http::Extensions,
273    ) -> Result<Self::Ok, ServerError> {
274        // Alias of MpoolBatchPush.
275        MpoolBatchPush::handle(ctx, (messages,), ext).await
276    }
277}
278
279/// Sign given `UnsignedMessage` and add it to `mpool`, return `SignedMessage`
280pub enum MpoolPushMessage {}
281impl RpcMethod<2> for MpoolPushMessage {
282    const NAME: &'static str = "Filecoin.MpoolPushMessage";
283    const PARAM_NAMES: [&'static str; 2] = ["message", "sendSpec"];
284    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
285    const PERMISSION: Permission = Permission::Sign;
286    const DESCRIPTION: &'static str =
287        "Assigns a nonce, signs, and pushes a message to the mempool.";
288
289    type Params = (Message, Option<MessageSendSpec>);
290    type Ok = SignedMessage;
291
292    async fn handle(
293        ctx: Ctx,
294        (message, send_spec): Self::Params,
295        extensions: &http::Extensions,
296    ) -> Result<Self::Ok, ServerError> {
297        let from = message.from;
298
299        let heaviest_tipset = ctx.chain_store().heaviest_tipset();
300        let key_addr = ctx
301            .state_manager
302            .resolve_to_deterministic_address(from, &heaviest_tipset)
303            .await?;
304
305        if message.sequence != 0 {
306            return Err(anyhow::anyhow!(
307                "Expected nonce for MpoolPushMessage is 0, and will be calculated for you"
308            )
309            .into());
310        }
311
312        let _sender_guard = ctx.mpool_locker.take_lock(key_addr).await;
313
314        let mut message =
315            estimate_message_gas(&ctx, message, send_spec, Default::default()).await?;
316        if message.gas_premium > message.gas_fee_cap {
317            return Err(anyhow::anyhow!(
318                "After estimation, gas premium is greater than gas fee cap"
319            )
320            .into());
321        }
322
323        if from.protocol() == Protocol::ID {
324            message.from = key_addr;
325        }
326
327        let balance =
328            super::wallet::WalletBalance::handle(ctx.clone(), (message.from,), extensions).await?;
329        let required_funds = &message.value + &message.gas_fee_cap * message.gas_limit;
330        if balance < required_funds {
331            return Err(anyhow::anyhow!(
332                "mpool push: not enough funds: {balance} < {required_funds}",
333            )
334            .into());
335        }
336
337        let key = crate::key_management::Key::try_from(crate::key_management::try_find(
338            &key_addr,
339            &ctx.keystore.as_ref().read(),
340        )?)?;
341        let eth_chain_id = ctx.chain_config().eth_chain_id;
342
343        let smsg = ctx
344            .nonce_tracker
345            .sign_and_push(&ctx.mpool, message, &key, eth_chain_id)
346            .await?;
347
348        Ok(smsg)
349    }
350}