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