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