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