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