mod types;
use crate::blocks::{Block, FullTipset, GossipBlock};
use crate::chain;
use crate::chain_sync::{BlockValidationOutcome, SyncStatusReport, TipsetValidator};
use crate::libp2p::{IdentTopic, NetworkMessage, PUBSUB_BLOCK_STR};
use crate::prelude::*;
use crate::rpc::{ApiPaths, Ctx, Permission, RpcMethod, ServerError};
use enumflags2::BitFlags;
use fvm_ipld_encoding::to_vec;
use std::time::Duration;
use tokio::sync::broadcast::error::RecvError;
pub use types::*;
pub enum SyncCheckBad {}
impl RpcMethod<1> for SyncCheckBad {
const NAME: &'static str = "Filecoin.SyncCheckBad";
const PARAM_NAMES: [&'static str; 1] = ["cid"];
const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
const PERMISSION: Permission = Permission::Read;
const DESCRIPTION: &'static str =
"Returns the reason the given block is marked bad, or an empty string if it is not.";
type Params = (Cid,);
type Ok = String;
async fn handle(
ctx: Ctx,
(cid,): Self::Params,
_: &http::Extensions,
) -> Result<Self::Ok, ServerError> {
Ok(ctx
.bad_blocks
.as_ref()
.context("bad block cache is disabled")?
.get(&cid)
.map(|_| "bad".to_string())
.unwrap_or_default())
}
}
pub enum SyncMarkBad {}
impl RpcMethod<1> for SyncMarkBad {
const NAME: &'static str = "Filecoin.SyncMarkBad";
const PARAM_NAMES: [&'static str; 1] = ["cid"];
const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
const PERMISSION: Permission = Permission::Admin;
const DESCRIPTION: &'static str = "Marks the block with the given CID as bad.";
type Params = (Cid,);
type Ok = ();
async fn handle(
ctx: Ctx,
(cid,): Self::Params,
_: &http::Extensions,
) -> Result<Self::Ok, ServerError> {
ctx.bad_blocks
.as_ref()
.context("bad block cache is disabled")?
.push(cid);
Ok(())
}
}
pub enum SyncSnapshotProgress {}
impl RpcMethod<0> for SyncSnapshotProgress {
const NAME: &'static str = "Forest.SyncSnapshotProgress";
const PARAM_NAMES: [&'static str; 0] = [];
const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
const PERMISSION: Permission = Permission::Read;
const DESCRIPTION: &'static str =
"Returns the snapshot download progress. Return Null if the tracking isn't started";
type Params = ();
type Ok = SnapshotProgressState;
async fn handle(
ctx: Ctx,
(): Self::Params,
_: &http::Extensions,
) -> Result<Self::Ok, ServerError> {
Ok(ctx.get_snapshot_progress_tracker())
}
}
pub enum SyncStatus {}
impl RpcMethod<0> for SyncStatus {
const NAME: &'static str = "Forest.SyncStatus";
const PARAM_NAMES: [&'static str; 0] = [];
const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
const PERMISSION: Permission = Permission::Read;
const DESCRIPTION: &'static str = "Returns the current sync status of the node.";
type Params = ();
type Ok = Arc<SyncStatusReport>;
async fn handle(
ctx: Ctx,
(): Self::Params,
_: &http::Extensions,
) -> Result<Self::Ok, ServerError> {
let sync_status = ctx.sync_status.load().shallow_clone();
Ok(sync_status)
}
}
pub enum SyncSubmitBlock {}
impl RpcMethod<1> for SyncSubmitBlock {
const NAME: &'static str = "Filecoin.SyncSubmitBlock";
const PARAM_NAMES: [&'static str; 1] = ["block"];
const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
const PERMISSION: Permission = Permission::Write;
const DESCRIPTION: &'static str = "Submits a newly created block to the network.";
type Params = (GossipBlock,);
type Ok = ();
async fn handle(
ctx: Ctx,
(block_msg,): Self::Params,
_: &http::Extensions,
) -> Result<Self::Ok, ServerError> {
let genesis_network_name = ctx.chain_config().network.genesis_name();
let encoded_message = to_vec(&block_msg)?;
let pubsub_block_str = format!("{PUBSUB_BLOCK_STR}/{genesis_network_name}");
let (bls_messages, secp_messages) =
chain::store::block_messages(ctx.db(), &block_msg.header)?;
let block_cid = *block_msg.header.cid();
let block = Block {
header: block_msg.header,
bls_messages,
secp_messages,
};
let ts = FullTipset::from(block);
let genesis_ts = ctx.chain_store().genesis_tipset();
TipsetValidator(&ts)
.validate(
ctx.chain_store(),
ctx.bad_blocks.as_ref(),
&genesis_ts,
ctx.chain_config().block_delay_secs,
)
.context("failed to validate the tipset")?;
let mut outcomes = ctx.block_validation_subscriber.subscribe();
ctx.tipset_send
.try_send(ts)
.context("tipset queue is full")?;
let block_delay_secs = ctx.chain_config().block_delay_secs.into();
let verdict = tokio::time::timeout(Duration::from_secs(block_delay_secs), async {
loop {
match outcomes.recv().await {
Ok((cid, outcome)) if cid == block_cid => return Some(outcome),
Ok(_) | Err(RecvError::Lagged(_)) => {}
Err(RecvError::Closed) => return None,
}
}
})
.await;
match verdict {
Ok(Some(BlockValidationOutcome::Rejected)) => {
return Err(anyhow::anyhow!(
"submitted block {block_cid} was rejected during validation"
)
.into());
}
Ok(Some(BlockValidationOutcome::Applied)) | Ok(None) => {}
Err(_elapsed) => tracing::warn!(
%block_cid,
block_delay_secs,
"SyncSubmitBlock: no validation verdict within one block time; publishing best-effort"
),
}
ctx.network_send().send(NetworkMessage::PubsubMessage {
topic: IdentTopic::new(pubsub_block_str),
message: encoded_message,
})?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use super::*;
use crate::chain_sync::NodeSyncStatus;
use crate::libp2p::NetworkMessage;
use crate::rpc::RPCState;
use crate::rpc::test_utils::chain_store;
fn ctx() -> (Arc<RPCState>, flume::Receiver<NetworkMessage>) {
RPCState::for_tests(chain_store()).unwrap()
}
#[tokio::test]
async fn set_check_bad() {
let (ctx, _) = ctx();
let cid = "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4"
.parse::<Cid>()
.unwrap();
let reason = SyncCheckBad::handle(ctx.clone(), (cid,), &Default::default())
.await
.unwrap();
assert_eq!(reason, "");
SyncMarkBad::handle(ctx.clone(), (cid,), &Default::default())
.await
.unwrap();
let reason = SyncCheckBad::handle(ctx.clone(), (cid,), &Default::default())
.await
.unwrap();
assert_eq!(reason, "bad");
}
#[tokio::test]
async fn sync_status_test() {
let (ctx, _) = ctx();
let st_copy = ctx.sync_status.clone();
let sync_status = SyncStatus::handle(ctx.clone(), (), &Default::default())
.await
.unwrap();
assert_eq!(sync_status, st_copy.load().clone());
st_copy.store(
st_copy
.load()
.as_ref()
.clone()
.with_status(NodeSyncStatus::Syncing)
.with_current_head_epoch(4)
.into(),
);
let sync_status = SyncStatus::handle(ctx.clone(), (), &Default::default())
.await
.unwrap();
assert_eq!(sync_status, st_copy.load().clone());
}
}