1use std::sync::Arc;
5
6use crate::blocks::Tipset;
7use crate::chain::block_messages;
8use crate::chain::index::ChainIndex;
9use crate::chain::store::Error;
10use crate::db::EthMappingsStore;
11use crate::interpreter::{
12 fvm2::ForestExternsV2, fvm3::ForestExterns as ForestExternsV3,
13 fvm4::ForestExterns as ForestExternsV4,
14};
15use crate::message::ChainMessage;
16use crate::message::MessageRead as _;
17use crate::networks::{ChainConfig, NetworkChain};
18use crate::shim::actors::{AwardBlockRewardParams, cron, reward};
19use crate::shim::{
20 address::Address,
21 econ::TokenAmount,
22 executor::{ApplyRet, Receipt, StampedEvent},
23 externs::{Rand, RandWrapper},
24 machine::MultiEngine,
25 message::{Message, Message_v3},
26 state_tree::ActorState,
27 version::NetworkVersion,
28};
29use ahash::{HashMap, HashMapExt, HashSet};
30use anyhow::bail;
31use cid::Cid;
32use fvm_ipld_blockstore::Blockstore;
33use fvm_ipld_encoding::{RawBytes, to_vec};
34use fvm_shared2::clock::ChainEpoch;
35use fvm2::{
36 executor::{DefaultExecutor as DefaultExecutor_v2, Executor as Executor_v2},
37 machine::{
38 DefaultMachine as DefaultMachine_v2, Machine as Machine_v2,
39 NetworkConfig as NetworkConfig_v2,
40 },
41};
42use fvm3::{
43 executor::{DefaultExecutor as DefaultExecutor_v3, Executor as Executor_v3},
44 machine::{
45 DefaultMachine as DefaultMachine_v3, Machine as Machine_v3,
46 NetworkConfig as NetworkConfig_v3,
47 },
48};
49use fvm4::{
50 executor::{DefaultExecutor as DefaultExecutor_v4, Executor as Executor_v4},
51 machine::{
52 DefaultMachine as DefaultMachine_v4, Machine as Machine_v4,
53 NetworkConfig as NetworkConfig_v4,
54 },
55};
56use num::Zero;
57use spire_enum::prelude::delegated_enum;
58use std::time::{Duration, Instant};
59
60pub(in crate::interpreter) type ForestMachineV2<DB> =
61 DefaultMachine_v2<Arc<DB>, ForestExternsV2<DB>>;
62pub(in crate::interpreter) type ForestMachineV3<DB> =
63 DefaultMachine_v3<Arc<DB>, ForestExternsV3<DB>>;
64pub(in crate::interpreter) type ForestMachineV4<DB> =
65 DefaultMachine_v4<Arc<DB>, ForestExternsV4<DB>>;
66
67type ForestKernelV2<DB> =
68 fvm2::DefaultKernel<fvm2::call_manager::DefaultCallManager<ForestMachineV2<DB>>>;
69type ForestKernelV3<DB> =
70 fvm3::DefaultKernel<fvm3::call_manager::DefaultCallManager<ForestMachineV3<DB>>>;
71type ForestKernelV4<DB> = fvm4::kernel::filecoin::DefaultFilecoinKernel<
72 fvm4::call_manager::DefaultCallManager<ForestMachineV4<DB>>,
73>;
74
75type ForestExecutorV2<DB> = DefaultExecutor_v2<ForestKernelV2<DB>>;
76type ForestExecutorV3<DB> = DefaultExecutor_v3<ForestKernelV3<DB>>;
77type ForestExecutorV4<DB> = DefaultExecutor_v4<ForestKernelV4<DB>>;
78
79pub type ApplyResult = anyhow::Result<(ApplyRet, Duration)>;
80
81pub type ApplyBlockResult = anyhow::Result<(
82 Vec<Receipt>,
83 Vec<Option<Vec<StampedEvent>>>,
84 Vec<Option<Cid>>,
85)>;
86
87pub const IMPLICIT_MESSAGE_GAS_LIMIT: i64 = i64::MAX / 2;
89
90#[derive(Debug)]
93pub struct BlockMessages {
94 pub miner: Address,
95 pub messages: Vec<ChainMessage>,
96 pub win_count: i64,
97}
98
99impl BlockMessages {
100 pub fn for_tipset(db: &impl Blockstore, ts: &Tipset) -> Result<Vec<BlockMessages>, Error> {
102 let mut applied = HashMap::new();
103 let mut select_msg = |m: ChainMessage| -> Option<ChainMessage> {
104 let entry = applied.entry(m.from()).or_insert_with(|| m.sequence());
107
108 if *entry != m.sequence() {
109 return None;
110 }
111
112 *entry += 1;
113 Some(m)
114 };
115
116 ts.block_headers()
117 .iter()
118 .map(|b| {
119 let (usm, sm) = block_messages(db, b)?;
120
121 let mut messages = Vec::with_capacity(usm.len() + sm.len());
122 messages.extend(usm.into_iter().filter_map(|m| select_msg(m.into())));
123 messages.extend(sm.into_iter().filter_map(|m| select_msg(m.into())));
124
125 Ok(BlockMessages {
126 miner: b.miner_address,
127 messages,
128 win_count: b
129 .election_proof
130 .as_ref()
131 .map(|e| e.win_count)
132 .unwrap_or_default(),
133 })
134 })
135 .collect()
136 }
137}
138
139#[delegated_enum(impl_conversions)]
142pub enum VM<DB: Blockstore + EthMappingsStore + Send + Sync + 'static> {
143 VM2(ForestExecutorV2<DB>),
144 VM3(ForestExecutorV3<DB>),
145 VM4(ForestExecutorV4<DB>),
146}
147
148pub struct ExecutionContext<DB> {
149 pub heaviest_tipset: Tipset,
153 pub state_tree_root: Cid,
155 pub epoch: ChainEpoch,
157 pub rand: Box<dyn Rand>,
159 pub base_fee: TokenAmount,
161 pub circ_supply: TokenAmount,
163 pub chain_config: Arc<ChainConfig>,
165 pub chain_index: ChainIndex<DB>,
167 pub timestamp: u64,
169}
170
171impl<DB> VM<DB>
172where
173 DB: Blockstore + EthMappingsStore + Send + Sync,
174{
175 pub fn new(
176 ExecutionContext {
177 heaviest_tipset,
178 state_tree_root,
179 epoch,
180 rand,
181 base_fee,
182 circ_supply,
183 chain_config,
184 chain_index,
185 timestamp,
186 }: ExecutionContext<DB>,
187 multi_engine: &MultiEngine,
188 enable_tracing: VMTrace,
189 ) -> anyhow::Result<Self> {
190 let network_version = chain_config.network_version(epoch);
191 if network_version >= NetworkVersion::V21 {
192 let mut config = NetworkConfig_v4::new(network_version.into());
193 config.chain_id((chain_config.eth_chain_id).into());
195 if let NetworkChain::Devnet(_) = chain_config.network {
196 config.enable_actor_debugging();
197 }
198
199 let engine = multi_engine.v4.get(&config)?;
200 let mut context = config.for_epoch(epoch, timestamp, state_tree_root);
201 context.set_base_fee(base_fee.into());
202 context.set_circulating_supply(circ_supply.into());
203 context.tracing = enable_tracing.is_traced();
204
205 let fvm: ForestMachineV4<DB> = ForestMachineV4::new(
206 &context,
207 Arc::clone(chain_index.db()),
208 ForestExternsV4::new(
209 RandWrapper::from(rand),
210 heaviest_tipset,
211 epoch,
212 state_tree_root,
213 chain_index,
214 chain_config,
215 ),
216 )?;
217 let exec: ForestExecutorV4<DB> = DefaultExecutor_v4::new(engine, fvm)?;
218 Ok(VM::VM4(exec))
219 } else if network_version >= NetworkVersion::V18 {
220 let mut config = NetworkConfig_v3::new(network_version.into());
221 config.chain_id((chain_config.eth_chain_id).into());
223 if let NetworkChain::Devnet(_) = chain_config.network {
224 config.enable_actor_debugging();
225 }
226
227 let engine = multi_engine.v3.get(&config)?;
228 let mut context = config.for_epoch(epoch, timestamp, state_tree_root);
229 context.set_base_fee(base_fee.into());
230 context.set_circulating_supply(circ_supply.into());
231 context.tracing = enable_tracing.is_traced();
232
233 let fvm: ForestMachineV3<DB> = ForestMachineV3::new(
234 &context,
235 Arc::clone(chain_index.db()),
236 ForestExternsV3::new(
237 RandWrapper::from(rand),
238 heaviest_tipset,
239 epoch,
240 state_tree_root,
241 chain_index,
242 chain_config,
243 ),
244 )?;
245 let exec: ForestExecutorV3<DB> = DefaultExecutor_v3::new(engine, fvm)?;
246 Ok(VM::VM3(exec))
247 } else {
248 let config = NetworkConfig_v2::new(network_version.into());
249 let engine = multi_engine.v2.get(&config)?;
250 let mut context = config.for_epoch(epoch, state_tree_root);
251 context.set_base_fee(base_fee.into());
252 context.set_circulating_supply(circ_supply.into());
253 context.tracing = enable_tracing.is_traced();
254
255 let fvm: ForestMachineV2<DB> = ForestMachineV2::new(
256 &engine,
257 &context,
258 Arc::clone(chain_index.db()),
259 ForestExternsV2::new(
260 RandWrapper::from(rand),
261 heaviest_tipset,
262 epoch,
263 state_tree_root,
264 chain_index,
265 chain_config,
266 ),
267 )?;
268 let exec: ForestExecutorV2<DB> = DefaultExecutor_v2::new(fvm);
269 Ok(VM::VM2(exec))
270 }
271 }
272
273 pub fn flush(&mut self) -> anyhow::Result<Cid> {
275 Ok(delegate_vm!(self.flush()?))
276 }
277
278 pub fn get_actor(&self, addr: &Address) -> anyhow::Result<Option<ActorState>> {
280 match self {
281 VM::VM2(fvm_executor) => Ok(fvm_executor
282 .state_tree()
283 .get_actor(&addr.into())?
284 .map(ActorState::from)),
285 VM::VM3(fvm_executor) => {
286 if let Some(id) = fvm_executor.state_tree().lookup_id(&addr.into())? {
287 Ok(fvm_executor
288 .state_tree()
289 .get_actor(id)?
290 .map(ActorState::from))
291 } else {
292 Ok(None)
293 }
294 }
295 VM::VM4(fvm_executor) => {
296 if let Some(id) = fvm_executor.state_tree().lookup_id(&addr.into())? {
297 Ok(fvm_executor
298 .state_tree()
299 .get_actor(id)?
300 .map(ActorState::from))
301 } else {
302 Ok(None)
303 }
304 }
305 }
306 }
307
308 pub fn run_cron(
309 &mut self,
310 epoch: ChainEpoch,
311 callback: Option<impl FnMut(MessageCallbackCtx<'_>) -> anyhow::Result<()>>,
312 ) -> anyhow::Result<()> {
313 let cron_msg: Message = Message_v3 {
314 from: Address::SYSTEM_ACTOR.into(),
315 to: Address::CRON_ACTOR.into(),
316 sequence: epoch as u64,
318 gas_limit: IMPLICIT_MESSAGE_GAS_LIMIT as u64,
320 method_num: cron::Method::EpochTick as u64,
321 params: Default::default(),
322 value: Default::default(),
323 version: Default::default(),
324 gas_fee_cap: Default::default(),
325 gas_premium: Default::default(),
326 }
327 .into();
328
329 let (ret, duration) = self.apply_implicit_message(&cron_msg)?;
330 if let Some(err) = ret.failure_info() {
331 anyhow::bail!("failed to apply block cron message: {}", err);
332 }
333
334 if let Some(mut callback) = callback {
335 callback(MessageCallbackCtx {
336 cid: cron_msg.cid(),
337 message: &cron_msg.into(),
338 apply_ret: &ret,
339 at: CalledAt::Cron,
340 duration,
341 })?;
342 }
343 Ok(())
344 }
345
346 pub fn apply_block_messages(
349 &mut self,
350 messages: &[BlockMessages],
351 epoch: ChainEpoch,
352 mut callback: Option<impl FnMut(MessageCallbackCtx<'_>) -> anyhow::Result<()>>,
353 ) -> ApplyBlockResult {
354 let mut receipts = Vec::new();
355 let mut events = Vec::new();
356 let mut events_roots: Vec<Option<Cid>> = Vec::new();
357 let mut processed = HashSet::default();
358
359 for block in messages.iter() {
360 let mut penalty = TokenAmount::zero();
361 let mut gas_reward = TokenAmount::zero();
362
363 let mut process_msg = |message: &ChainMessage| -> anyhow::Result<()> {
364 let cid = message.cid();
365 if processed.contains(&cid) {
367 return Ok(());
368 }
369 let (ret, duration) = self.apply_message(message)?;
370
371 if let Some(cb) = &mut callback {
372 cb(MessageCallbackCtx {
373 cid,
374 message,
375 apply_ret: &ret,
376 at: CalledAt::Applied,
377 duration,
378 })?;
379 }
380
381 gas_reward += ret.miner_tip();
383 penalty += ret.penalty();
384 let msg_receipt = ret.msg_receipt();
385 receipts.push(msg_receipt.clone());
386
387 events_roots.push(ret.msg_receipt().events_root());
388 if ret.msg_receipt().events_root().is_some() {
389 events.push(Some(ret.events()));
390 } else {
391 events.push(None);
392 }
393
394 processed.insert(cid);
396 Ok(())
397 };
398
399 for msg in block.messages.iter() {
400 process_msg(msg)?;
401 }
402
403 if let Some(rew_msg) =
405 self.reward_message(epoch, block.miner, block.win_count, penalty, gas_reward)?
406 {
407 let (ret, duration) = self.apply_implicit_message(&rew_msg)?;
408 if let Some(err) = ret.failure_info() {
409 anyhow::bail!(
410 "failed to apply reward message for miner {}: {}",
411 block.miner,
412 err
413 );
414 }
415 if !ret.msg_receipt().exit_code().is_success() {
417 anyhow::bail!(
418 "reward application message failed (exit: {:?})",
419 ret.msg_receipt().exit_code()
420 );
421 }
422
423 if let Some(callback) = &mut callback {
424 callback(MessageCallbackCtx {
425 cid: rew_msg.cid(),
426 message: &rew_msg.into(),
427 apply_ret: &ret,
428 at: CalledAt::Reward,
429 duration,
430 })?
431 }
432 }
433 }
434
435 if let Err(e) = self.run_cron(epoch, callback.as_mut()) {
436 tracing::error!("End of epoch cron failed to run: {}", e);
437 }
438
439 Ok((receipts, events, events_roots))
440 }
441
442 pub fn apply_implicit_message(&mut self, msg: &Message) -> ApplyResult {
444 let start = Instant::now();
445
446 let raw_length = to_vec(msg).expect("encoding error").len();
448
449 let ret = match self {
450 VM::VM2(fvm_executor) => fvm_executor
451 .execute_message(msg.into(), fvm2::executor::ApplyKind::Implicit, raw_length)?
452 .into(),
453 VM::VM3(fvm_executor) => fvm_executor
454 .execute_message(msg.into(), fvm3::executor::ApplyKind::Implicit, raw_length)?
455 .into(),
456 VM::VM4(fvm_executor) => fvm_executor
457 .execute_message(msg.into(), fvm4::executor::ApplyKind::Implicit, raw_length)?
458 .into(),
459 };
460 Ok((ret, start.elapsed()))
461 }
462
463 pub fn apply_message(&mut self, msg: &ChainMessage) -> ApplyResult {
467 let start = Instant::now();
468
469 msg.message().check()?;
471
472 let unsigned = msg.message().clone();
473 let raw_length = to_vec(msg).expect("encoding error").len();
474 let ret: ApplyRet = match self {
475 VM::VM2(fvm_executor) => {
476 let ret = fvm_executor.execute_message(
477 unsigned.into(),
478 fvm2::executor::ApplyKind::Explicit,
479 raw_length,
480 )?;
481
482 if fvm_executor.externs().bail() {
483 bail!("encountered a database lookup error");
484 }
485
486 ret.into()
487 }
488 VM::VM3(fvm_executor) => {
489 let ret = fvm_executor.execute_message(
490 unsigned.into(),
491 fvm3::executor::ApplyKind::Explicit,
492 raw_length,
493 )?;
494
495 if fvm_executor.externs().bail() {
496 bail!("encountered a database lookup error");
497 }
498
499 ret.into()
500 }
501 VM::VM4(fvm_executor) => {
502 let ret = fvm_executor.execute_message(
503 unsigned.into(),
504 fvm4::executor::ApplyKind::Explicit,
505 raw_length,
506 )?;
507
508 if fvm_executor.externs().bail() {
509 bail!("encountered a database lookup error");
510 }
511
512 ret.into()
513 }
514 };
515 let duration = start.elapsed();
516
517 let exit_code = ret.msg_receipt().exit_code();
518
519 if !exit_code.is_success() {
520 tracing::debug!(?exit_code, "VM message execution failure.")
521 }
522
523 Ok((ret, duration))
524 }
525
526 pub(crate) fn reward_message(
527 &self,
528 epoch: ChainEpoch,
529 miner: Address,
530 win_count: i64,
531 penalty: TokenAmount,
532 gas_reward: TokenAmount,
533 ) -> anyhow::Result<Option<Message>> {
534 let params = RawBytes::serialize(AwardBlockRewardParams {
535 miner: miner.into(),
536 penalty: penalty.into(),
537 gas_reward: gas_reward.into(),
538 win_count,
539 })?;
540 let rew_msg = Message_v3 {
541 from: Address::SYSTEM_ACTOR.into(),
542 to: Address::REWARD_ACTOR.into(),
543 method_num: reward::Method::AwardBlockReward as u64,
544 params,
545 sequence: epoch as u64,
547 gas_limit: IMPLICIT_MESSAGE_GAS_LIMIT as u64,
548 value: Default::default(),
549 version: Default::default(),
550 gas_fee_cap: Default::default(),
551 gas_premium: Default::default(),
552 };
553 Ok(Some(rew_msg.into()))
554 }
555}
556
557#[derive(Debug, Clone)]
558pub struct MessageCallbackCtx<'a> {
559 pub cid: Cid,
560 pub message: &'a ChainMessage,
561 pub apply_ret: &'a ApplyRet,
562 pub at: CalledAt,
563 pub duration: Duration,
564}
565
566#[derive(Debug, Clone, Copy)]
567pub enum CalledAt {
568 Applied,
569 Reward,
570 Cron,
571}
572
573impl CalledAt {
574 pub fn apply_kind(&self) -> fvm3::executor::ApplyKind {
576 use fvm3::executor::ApplyKind;
577 match self {
578 CalledAt::Applied => ApplyKind::Explicit,
579 CalledAt::Reward | CalledAt::Cron => ApplyKind::Implicit,
580 }
581 }
582}
583
584#[derive(Default, Clone, Copy)]
587pub enum VMTrace {
588 Traced,
590 #[default]
592 NotTraced,
593}
594
595impl VMTrace {
596 pub fn is_traced(&self) -> bool {
598 matches!(self, VMTrace::Traced)
599 }
600}