1use 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
41pub 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
70pub 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
91pub 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 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
158pub 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
183pub 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
205pub 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
230pub 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 let cid = ctx.mpool.push_untrusted(message).await?;
252 Ok(cid)
253 }
254}
255
256pub 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 MpoolBatchPush::handle(ctx, (messages,), ext).await
276 }
277}
278
279pub 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}