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