forest/rpc/methods/
sync.rs1mod types;
5
6use crate::blocks::{Block, FullTipset, GossipBlock};
7use crate::chain;
8use crate::chain_sync::{BlockValidationOutcome, SyncStatusReport, TipsetValidator};
9use crate::libp2p::{IdentTopic, NetworkMessage, PUBSUB_BLOCK_STR};
10use crate::prelude::*;
11use crate::rpc::{ApiPaths, Ctx, Permission, RpcMethod, ServerError};
12use enumflags2::BitFlags;
13use fvm_ipld_encoding::to_vec;
14use std::time::Duration;
15use tokio::sync::broadcast::error::RecvError;
16pub use types::*;
17
18pub enum SyncCheckBad {}
19impl RpcMethod<1> for SyncCheckBad {
20 const NAME: &'static str = "Filecoin.SyncCheckBad";
21 const PARAM_NAMES: [&'static str; 1] = ["cid"];
22 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
23 const PERMISSION: Permission = Permission::Read;
24 const DESCRIPTION: &'static str =
25 "Returns the reason the given block is marked bad, or an empty string if it is not.";
26
27 type Params = (Cid,);
28 type Ok = String;
29
30 async fn handle(
31 ctx: Ctx,
32 (cid,): Self::Params,
33 _: &http::Extensions,
34 ) -> Result<Self::Ok, ServerError> {
35 Ok(ctx
36 .bad_blocks
37 .as_ref()
38 .context("bad block cache is disabled")?
39 .get(&cid)
40 .map(|_| "bad".to_string())
41 .unwrap_or_default())
42 }
43}
44
45pub enum SyncMarkBad {}
46impl RpcMethod<1> for SyncMarkBad {
47 const NAME: &'static str = "Filecoin.SyncMarkBad";
48 const PARAM_NAMES: [&'static str; 1] = ["cid"];
49 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
50 const PERMISSION: Permission = Permission::Admin;
51 const DESCRIPTION: &'static str = "Marks the block with the given CID as bad.";
52
53 type Params = (Cid,);
54 type Ok = ();
55
56 async fn handle(
57 ctx: Ctx,
58 (cid,): Self::Params,
59 _: &http::Extensions,
60 ) -> Result<Self::Ok, ServerError> {
61 ctx.bad_blocks
62 .as_ref()
63 .context("bad block cache is disabled")?
64 .push(cid);
65 Ok(())
66 }
67}
68
69pub enum SyncSnapshotProgress {}
70impl RpcMethod<0> for SyncSnapshotProgress {
71 const NAME: &'static str = "Forest.SyncSnapshotProgress";
72 const PARAM_NAMES: [&'static str; 0] = [];
73 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
74 const PERMISSION: Permission = Permission::Read;
75 const DESCRIPTION: &'static str =
76 "Returns the snapshot download progress. Return Null if the tracking isn't started";
77
78 type Params = ();
79 type Ok = SnapshotProgressState;
80
81 async fn handle(
82 ctx: Ctx,
83 (): Self::Params,
84 _: &http::Extensions,
85 ) -> Result<Self::Ok, ServerError> {
86 Ok(ctx.get_snapshot_progress_tracker())
87 }
88}
89
90pub enum SyncStatus {}
91impl RpcMethod<0> for SyncStatus {
92 const NAME: &'static str = "Forest.SyncStatus";
93 const PARAM_NAMES: [&'static str; 0] = [];
94 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
95 const PERMISSION: Permission = Permission::Read;
96 const DESCRIPTION: &'static str = "Returns the current sync status of the node.";
97
98 type Params = ();
99 type Ok = Arc<SyncStatusReport>;
100
101 async fn handle(
102 ctx: Ctx,
103 (): Self::Params,
104 _: &http::Extensions,
105 ) -> Result<Self::Ok, ServerError> {
106 let sync_status = ctx.sync_status.load().shallow_clone();
107 Ok(sync_status)
108 }
109}
110
111pub enum SyncSubmitBlock {}
112impl RpcMethod<1> for SyncSubmitBlock {
113 const NAME: &'static str = "Filecoin.SyncSubmitBlock";
114 const PARAM_NAMES: [&'static str; 1] = ["block"];
115 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
116 const PERMISSION: Permission = Permission::Write;
117 const DESCRIPTION: &'static str = "Submits a newly created block to the network.";
118
119 type Params = (GossipBlock,);
120 type Ok = ();
121
122 async fn handle(
125 ctx: Ctx,
126 (block_msg,): Self::Params,
127 _: &http::Extensions,
128 ) -> Result<Self::Ok, ServerError> {
129 let genesis_network_name = ctx.chain_config().network.genesis_name();
130 let encoded_message = to_vec(&block_msg)?;
131 let pubsub_block_str = format!("{PUBSUB_BLOCK_STR}/{genesis_network_name}");
132 let (bls_messages, secp_messages) =
133 chain::store::block_messages(ctx.db(), &block_msg.header)?;
134 let block_cid = *block_msg.header.cid();
135 let block = Block {
136 header: block_msg.header,
137 bls_messages,
138 secp_messages,
139 };
140 let ts = FullTipset::from(block);
141 let genesis_ts = ctx.chain_store().genesis_tipset();
142
143 TipsetValidator(&ts)
144 .validate(
145 ctx.chain_store(),
146 ctx.bad_blocks.as_ref(),
147 &genesis_ts,
148 ctx.chain_config().block_delay_secs,
149 )
150 .context("failed to validate the tipset")?;
151
152 let mut outcomes = ctx.block_validation_subscriber.subscribe();
154 ctx.tipset_send
155 .try_send(ts)
156 .context("tipset queue is full")?;
157
158 let block_delay_secs = ctx.chain_config().block_delay_secs.into();
163 let verdict = tokio::time::timeout(Duration::from_secs(block_delay_secs), async {
164 loop {
165 match outcomes.recv().await {
166 Ok((cid, outcome)) if cid == block_cid => return Some(outcome),
167 Ok(_) | Err(RecvError::Lagged(_)) => {}
168 Err(RecvError::Closed) => return None,
169 }
170 }
171 })
172 .await;
173
174 match verdict {
179 Ok(Some(BlockValidationOutcome::Rejected)) => {
180 return Err(anyhow::anyhow!(
181 "submitted block {block_cid} was rejected during validation"
182 )
183 .into());
184 }
185 Ok(Some(BlockValidationOutcome::Applied)) | Ok(None) => {}
186 Err(_elapsed) => tracing::warn!(
187 %block_cid,
188 block_delay_secs,
189 "SyncSubmitBlock: no validation verdict within one block time; publishing best-effort"
190 ),
191 }
192 ctx.network_send().send(NetworkMessage::PubsubMessage {
193 topic: IdentTopic::new(pubsub_block_str),
194 message: encoded_message,
195 })?;
196 Ok(())
197 }
198}
199
200#[cfg(test)]
201mod tests {
202 use std::sync::Arc;
203
204 use super::*;
205 use crate::chain_sync::NodeSyncStatus;
206 use crate::libp2p::NetworkMessage;
207 use crate::rpc::RPCState;
208 use crate::rpc::test_utils::chain_store;
209
210 fn ctx() -> (Arc<RPCState>, flume::Receiver<NetworkMessage>) {
211 RPCState::for_tests(chain_store()).unwrap()
212 }
213
214 #[tokio::test]
215 async fn set_check_bad() {
216 let (ctx, _) = ctx();
217
218 let cid = "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4"
219 .parse::<Cid>()
220 .unwrap();
221
222 let reason = SyncCheckBad::handle(ctx.clone(), (cid,), &Default::default())
223 .await
224 .unwrap();
225 assert_eq!(reason, "");
226
227 SyncMarkBad::handle(ctx.clone(), (cid,), &Default::default())
229 .await
230 .unwrap();
231
232 let reason = SyncCheckBad::handle(ctx.clone(), (cid,), &Default::default())
233 .await
234 .unwrap();
235 assert_eq!(reason, "bad");
236 }
237
238 #[tokio::test]
239 async fn sync_status_test() {
240 let (ctx, _) = ctx();
241
242 let st_copy = ctx.sync_status.clone();
243
244 let sync_status = SyncStatus::handle(ctx.clone(), (), &Default::default())
245 .await
246 .unwrap();
247 assert_eq!(sync_status, st_copy.load().clone());
248
249 st_copy.store(
251 st_copy
252 .load()
253 .as_ref()
254 .clone()
255 .with_status(NodeSyncStatus::Syncing)
256 .with_current_head_epoch(4)
257 .into(),
258 );
259
260 let sync_status = SyncStatus::handle(ctx.clone(), (), &Default::default())
261 .await
262 .unwrap();
263
264 assert_eq!(sync_status, st_copy.load().clone());
265 }
266}