Skip to main content

forest/rpc/methods/
miner.rs

1// Copyright 2019-2026 ChainSafe Systems
2// SPDX-License-Identifier: Apache-2.0, MIT
3
4use crate::beacon::BeaconEntry;
5use crate::blocks::{CachingBlockHeader, ElectionProof, RawBlockHeader, Ticket, TipsetKey};
6use crate::chain::{ChainStore, compute_base_fee};
7use crate::fil_cns::weight;
8use crate::interpreter::VMTrace;
9use crate::key_management::{Key, KeyStore};
10use crate::lotus_json::LotusJson;
11use crate::lotus_json::lotus_json_with_self;
12use crate::message::SignedMessage;
13use crate::networks::Height;
14use crate::prelude::*;
15use crate::rpc::reflect::Permission;
16use crate::rpc::types::{ApiTipsetKey, MiningBaseInfo};
17use crate::rpc::{ApiPaths, Ctx, RpcMethod, ServerError};
18use crate::shim::address::Address;
19use crate::shim::clock::ChainEpoch;
20use crate::shim::crypto::BLS_SIG_LEN;
21use crate::shim::crypto::{Signature, SignatureType};
22use crate::shim::sector::PoStProof;
23use crate::state_manager::ExecutedTipset;
24use crate::utils::db::CborStoreExt;
25use anyhow::Result;
26use bls_signatures::Serialize as _;
27use enumflags2::BitFlags;
28use fil_actors_shared::fvm_ipld_amt::Amtv0 as Amt;
29use fvm_ipld_encoding::tuple::*;
30use group::prime::PrimeCurveAffine as _;
31use parking_lot::RwLock;
32use schemars::JsonSchema;
33use serde::{Deserialize, Serialize};
34use std::sync::Arc;
35
36#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, JsonSchema)]
37#[serde(rename_all = "PascalCase")]
38pub struct BlockTemplate {
39    #[schemars(with = "LotusJson<Address>")]
40    #[serde(with = "crate::lotus_json")]
41    pub miner: Address,
42    #[schemars(with = "LotusJson<TipsetKey>")]
43    #[serde(with = "crate::lotus_json")]
44    pub parents: TipsetKey,
45    #[schemars(with = "LotusJson<Ticket>")]
46    #[serde(with = "crate::lotus_json")]
47    pub ticket: Ticket,
48    #[schemars(with = "LotusJson<ElectionProof>")]
49    #[serde(with = "crate::lotus_json")]
50    pub eproof: ElectionProof,
51    #[schemars(with = "LotusJson<Vec<BeaconEntry>>")]
52    #[serde(with = "crate::lotus_json")]
53    pub beacon_values: Vec<BeaconEntry>,
54    #[schemars(with = "LotusJson<Vec<SignedMessage>>")]
55    #[serde(with = "crate::lotus_json")]
56    pub messages: Vec<SignedMessage>,
57    #[schemars(with = "LotusJson<ChainEpoch>")]
58    #[serde(with = "crate::lotus_json")]
59    pub epoch: ChainEpoch,
60    pub timestamp: u64,
61    #[schemars(with = "LotusJson<Vec<PoStProof>>")]
62    #[serde(rename = "WinningPoStProof", with = "crate::lotus_json")]
63    pub winning_post_proof: Vec<PoStProof>,
64}
65
66lotus_json_with_self!(BlockTemplate);
67
68#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, JsonSchema)]
69#[serde(rename_all = "PascalCase")]
70pub struct BlockMessage {
71    #[schemars(with = "LotusJson<CachingBlockHeader>")]
72    #[serde(with = "crate::lotus_json")]
73    header: CachingBlockHeader,
74    #[schemars(with = "LotusJson<Vec<Cid>>")]
75    #[serde(with = "crate::lotus_json")]
76    bls_messages: Vec<Cid>,
77    #[schemars(with = "LotusJson<Vec<Cid>>")]
78    #[serde(with = "crate::lotus_json")]
79    secpk_messages: Vec<Cid>,
80}
81
82lotus_json_with_self!(BlockMessage);
83
84#[derive(Serialize_tuple)]
85struct MessageMeta {
86    bls_messages: Cid,
87    secpk_messages: Cid,
88}
89
90pub enum MinerCreateBlock {}
91impl RpcMethod<1> for MinerCreateBlock {
92    const NAME: &'static str = "Filecoin.MinerCreateBlock";
93    const PARAM_NAMES: [&'static str; 1] = ["blockTemplate"];
94    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
95    const PERMISSION: Permission = Permission::Write;
96    const DESCRIPTION: &'static str = "Fills and signs a block template on behalf of the given miner, returning a suitable block header.";
97
98    type Params = (BlockTemplate,);
99    type Ok = BlockMessage;
100
101    async fn handle(
102        ctx: Ctx,
103        (block_template,): Self::Params,
104        _: &http::Extensions,
105    ) -> Result<Self::Ok, ServerError> {
106        let store = ctx.db();
107        let parent_tipset = ctx
108            .chain_index()
109            .load_required_tipset(&block_template.parents)?;
110
111        let lookback_state = ChainStore::get_lookback_tipset_for_round(
112            ctx.chain_index().shallow_clone(),
113            ctx.chain_config().shallow_clone(),
114            parent_tipset.shallow_clone(),
115            block_template.epoch,
116        )
117        .await
118        .map(|(_, s)| Arc::new(s))?;
119
120        let worker = ctx
121            .state_manager
122            .get_miner_work_addr(*lookback_state, &block_template.miner)?;
123
124        let parent_weight = weight(store, &parent_tipset)?;
125        let heights = &ctx.chain_config().height_infos;
126        let parent_base_fee = compute_base_fee(
127            store,
128            &parent_tipset,
129            heights
130                .get(&Height::Smoke)
131                .context("Missing Smoke height")?
132                .epoch,
133            heights
134                .get(&Height::FireHorse)
135                .context("Missing FireHorse height")?
136                .epoch,
137        )?;
138        let ExecutedTipset {
139            state_root,
140            receipt_root,
141            ..
142        } = ctx
143            .state_manager
144            .compute_tipset_state(
145                parent_tipset,
146                crate::state_manager::NO_CALLBACK,
147                VMTrace::NotTraced,
148            )
149            .await?;
150
151        let network_version = ctx.state_manager.get_network_version(block_template.epoch);
152
153        let mut bls_messages = Vec::new();
154        let mut secpk_messages = Vec::new();
155        let mut bls_msg_cids = Vec::new();
156        let mut secpk_msg_cids = Vec::new();
157        let mut bls_sigs = Vec::new();
158
159        for msg in block_template.messages {
160            match msg.signature().signature_type() {
161                SignatureType::Bls => {
162                    let cid = ctx.db().put_cbor_default(&msg.message)?;
163                    bls_msg_cids.push(cid);
164                    bls_sigs.push(msg.signature);
165                    bls_messages.push(msg.message);
166                }
167                SignatureType::Secp256k1 | SignatureType::Delegated => {
168                    if msg.signature.is_valid_secpk_sig_type(network_version) {
169                        let cid = ctx.db().put_cbor_default(&msg)?;
170                        secpk_msg_cids.push(cid);
171                        secpk_messages.push(msg);
172                    } else {
173                        Err(anyhow::anyhow!(
174                            "unknown sig type: {}",
175                            msg.signature.signature_type()
176                        ))?;
177                    }
178                }
179            }
180        }
181
182        let store = ctx.db();
183        let mut message_array = Amt::<Cid, _>::new(store);
184        for (i, cid) in bls_msg_cids.iter().enumerate() {
185            message_array.set(i as u64, *cid)?;
186        }
187        let bls_msgs_root = message_array.flush()?;
188        let mut message_array = Amt::<Cid, _>::new(store);
189        for (i, cid) in secpk_msg_cids.iter().enumerate() {
190            message_array.set(i as u64, *cid)?;
191        }
192        let secpk_msgs_root = message_array.flush()?;
193
194        let message_meta_cid = store.put_cbor_default(&MessageMeta {
195            bls_messages: bls_msgs_root,
196            secpk_messages: secpk_msgs_root,
197        })?;
198
199        let bls_aggregate = aggregate_from_bls_signatures(bls_sigs).await?;
200
201        let block_header = Arc::new(RawBlockHeader {
202            miner_address: block_template.miner,
203            ticket: block_template.ticket.into(),
204            election_proof: block_template.eproof.into(),
205            beacon_entries: block_template.beacon_values,
206            winning_post_proof: block_template.winning_post_proof,
207            parents: block_template.parents,
208            weight: parent_weight,
209            epoch: block_template.epoch,
210            state_root,
211            message_receipts: receipt_root,
212            messages: message_meta_cid,
213            bls_aggregate: bls_aggregate.into(),
214            timestamp: block_template.timestamp,
215            signature: None,
216            fork_signal: Default::default(),
217            parent_base_fee,
218        });
219
220        let signature = sign_block_header(
221            block_header.shallow_clone(),
222            worker,
223            ctx.keystore.shallow_clone(),
224        )
225        .await?
226        .into();
227
228        let mut block_header = Arc::unwrap_or_clone(block_header);
229        block_header.signature = signature;
230
231        Ok(BlockMessage {
232            header: CachingBlockHeader::from(block_header),
233            bls_messages: bls_msg_cids,
234            secpk_messages: secpk_msg_cids,
235        })
236    }
237}
238
239async fn sign_block_header(
240    block_header: Arc<RawBlockHeader>,
241    worker: Address,
242    keystore: Arc<RwLock<KeyStore>>,
243) -> Result<Signature> {
244    tokio::task::spawn_blocking(move || {
245        sign_block_header_blocking(&block_header, &worker, &keystore)
246    })
247    .await?
248}
249
250// Use [`sign_block_header`] instead, CPU-intensive crypto should be wrapped with `spawn_blocking`
251fn sign_block_header_blocking(
252    block_header: &RawBlockHeader,
253    worker: &Address,
254    keystore: &RwLock<KeyStore>,
255) -> Result<Signature> {
256    let signing_bytes = block_header.signing_bytes();
257
258    let key = {
259        let keystore = keystore.read();
260        let key_info = crate::key_management::try_find(worker, &keystore)?;
261        Key::try_from(key_info)?
262    };
263
264    let sig = crate::key_management::sign(
265        *key.key_info.key_type(),
266        key.key_info.private_key(),
267        &signing_bytes,
268    )?;
269    Ok(sig)
270}
271
272async fn aggregate_from_bls_signatures(bls_sigs: Vec<Signature>) -> anyhow::Result<Signature> {
273    tokio::task::spawn_blocking(move || aggregate_from_bls_signatures_blocking(bls_sigs)).await?
274}
275
276// Use [`aggregate_from_bls_signatures`] instead, CPU-intensive crypto should be wrapped with `spawn_blocking`
277fn aggregate_from_bls_signatures_blocking(bls_sigs: Vec<Signature>) -> anyhow::Result<Signature> {
278    let signatures: Vec<_> = bls_sigs
279        .iter()
280        .map(|sig| anyhow::Ok(bls_signatures::Signature::from_bytes(sig.bytes())?))
281        .try_collect()?;
282
283    if signatures.is_empty() {
284        let sig: bls_signatures::Signature = blstrs::G2Affine::identity().into();
285        let mut raw_signature: [u8; BLS_SIG_LEN] = [0; BLS_SIG_LEN];
286        sig.write_bytes(&mut raw_signature.as_mut())?;
287        Ok(Signature::new_bls(raw_signature.to_vec()))
288    } else {
289        let bls_aggregate =
290            bls_signatures::aggregate(&signatures).context("failed to aggregate signatures")?;
291        Ok(Signature {
292            sig_type: SignatureType::Bls,
293            bytes: bls_aggregate.as_bytes().to_vec(),
294        })
295    }
296}
297
298pub enum MinerGetBaseInfo {}
299impl RpcMethod<3> for MinerGetBaseInfo {
300    const NAME: &'static str = "Filecoin.MinerGetBaseInfo";
301    const PARAM_NAMES: [&'static str; 3] = ["minerAddress", "epoch", "tipsetKey"];
302    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
303    const PERMISSION: Permission = Permission::Read;
304    const DESCRIPTION: &'static str = "Retrieves the Miner Actor at the given address and tipset, returning basic information such as power and mining eligibility.";
305
306    type Params = (Address, i64, ApiTipsetKey);
307    type Ok = Option<MiningBaseInfo>;
308
309    async fn handle(
310        ctx: Ctx,
311        (miner_address, epoch, ApiTipsetKey(tipset_key)): Self::Params,
312        _: &http::Extensions,
313    ) -> Result<Self::Ok, ServerError> {
314        let tipset = ctx
315            .chain_store()
316            .load_required_tipset_or_heaviest(&tipset_key)?;
317
318        Ok(ctx
319            .state_manager
320            .miner_get_base_info(ctx.beacon(), tipset, miner_address, epoch)
321            .await?)
322    }
323}