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::Message as MessageTrait;
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 =
81 anyhow::Result<(Vec<Receipt>, Vec<Vec<StampedEvent>>, Vec<Option<Cid>>), anyhow::Error>;
82
83pub const IMPLICIT_MESSAGE_GAS_LIMIT: i64 = i64::MAX / 2;
85
86#[derive(Debug)]
89pub struct BlockMessages {
90 pub miner: Address,
91 pub messages: Vec<ChainMessage>,
92 pub win_count: i64,
93}
94
95impl BlockMessages {
96 pub fn for_tipset(db: &impl Blockstore, ts: &Tipset) -> Result<Vec<BlockMessages>, Error> {
98 let mut applied = HashMap::new();
99 let mut select_msg = |m: ChainMessage| -> Option<ChainMessage> {
100 let entry = applied.entry(m.from()).or_insert_with(|| m.sequence());
103
104 if *entry != m.sequence() {
105 return None;
106 }
107
108 *entry += 1;
109 Some(m)
110 };
111
112 ts.block_headers()
113 .iter()
114 .map(|b| {
115 let (usm, sm) = block_messages(db, b)?;
116
117 let mut messages = Vec::with_capacity(usm.len() + sm.len());
118 messages.extend(
119 usm.into_iter()
120 .filter_map(|m| select_msg(ChainMessage::Unsigned(m))),
121 );
122 messages.extend(
123 sm.into_iter()
124 .filter_map(|m| select_msg(ChainMessage::Signed(m))),
125 );
126
127 Ok(BlockMessages {
128 miner: b.miner_address,
129 messages,
130 win_count: b
131 .election_proof
132 .as_ref()
133 .map(|e| e.win_count)
134 .unwrap_or_default(),
135 })
136 })
137 .collect()
138 }
139}
140
141#[delegated_enum(impl_conversions)]
144pub enum VM<DB: Blockstore + Send + Sync + 'static> {
145 VM2(ForestExecutorV2<DB>),
146 VM3(ForestExecutorV3<DB>),
147 VM4(ForestExecutorV4<DB>),
148}
149
150pub struct ExecutionContext<DB> {
151 pub heaviest_tipset: Tipset,
155 pub state_tree_root: Cid,
157 pub epoch: ChainEpoch,
159 pub rand: Box<dyn Rand>,
161 pub base_fee: TokenAmount,
163 pub circ_supply: TokenAmount,
165 pub chain_config: Arc<ChainConfig>,
167 pub chain_index: Arc<ChainIndex<Arc<DB>>>,
169 pub timestamp: u64,
171}
172
173impl<DB> VM<DB>
174where
175 DB: Blockstore + Send + Sync,
176{
177 pub fn new(
178 ExecutionContext {
179 heaviest_tipset,
180 state_tree_root,
181 epoch,
182 rand,
183 base_fee,
184 circ_supply,
185 chain_config,
186 chain_index,
187 timestamp,
188 }: ExecutionContext<DB>,
189 multi_engine: &MultiEngine,
190 enable_tracing: VMTrace,
191 ) -> Result<Self, anyhow::Error> {
192 let network_version = chain_config.network_version(epoch);
193 if network_version >= NetworkVersion::V21 {
194 let mut config = NetworkConfig_v4::new(network_version.into());
195 config.chain_id((chain_config.eth_chain_id).into());
197 if let NetworkChain::Devnet(_) = chain_config.network {
198 config.enable_actor_debugging();
199 }
200
201 let engine = multi_engine.v4.get(&config)?;
202 let mut context = config.for_epoch(epoch, timestamp, state_tree_root);
203 context.set_base_fee(base_fee.into());
204 context.set_circulating_supply(circ_supply.into());
205 context.tracing = enable_tracing.is_traced();
206
207 let fvm: ForestMachineV4<DB> = ForestMachineV4::new(
208 &context,
209 Arc::clone(chain_index.db()),
210 ForestExternsV4::new(
211 RandWrapper::from(rand),
212 heaviest_tipset,
213 epoch,
214 state_tree_root,
215 chain_index,
216 chain_config,
217 ),
218 )?;
219 let exec: ForestExecutorV4<DB> = DefaultExecutor_v4::new(engine, fvm)?;
220 Ok(VM::VM4(exec))
221 } else if network_version >= NetworkVersion::V18 {
222 let mut config = NetworkConfig_v3::new(network_version.into());
223 config.chain_id((chain_config.eth_chain_id).into());
225 if let NetworkChain::Devnet(_) = chain_config.network {
226 config.enable_actor_debugging();
227 }
228
229 let engine = multi_engine.v3.get(&config)?;
230 let mut context = config.for_epoch(epoch, timestamp, state_tree_root);
231 context.set_base_fee(base_fee.into());
232 context.set_circulating_supply(circ_supply.into());
233 context.tracing = enable_tracing.is_traced();
234
235 let fvm: ForestMachineV3<DB> = ForestMachineV3::new(
236 &context,
237 Arc::clone(chain_index.db()),
238 ForestExternsV3::new(
239 RandWrapper::from(rand),
240 heaviest_tipset,
241 epoch,
242 state_tree_root,
243 chain_index,
244 chain_config,
245 ),
246 )?;
247 let exec: ForestExecutorV3<DB> = DefaultExecutor_v3::new(engine, fvm)?;
248 Ok(VM::VM3(exec))
249 } else {
250 let config = NetworkConfig_v2::new(network_version.into());
251 let engine = multi_engine.v2.get(&config)?;
252 let mut context = config.for_epoch(epoch, state_tree_root);
253 context.set_base_fee(base_fee.into());
254 context.set_circulating_supply(circ_supply.into());
255 context.tracing = enable_tracing.is_traced();
256
257 let fvm: ForestMachineV2<DB> = ForestMachineV2::new(
258 &engine,
259 &context,
260 Arc::clone(chain_index.db()),
261 ForestExternsV2::new(
262 RandWrapper::from(rand),
263 heaviest_tipset,
264 epoch,
265 state_tree_root,
266 chain_index,
267 chain_config,
268 ),
269 )?;
270 let exec: ForestExecutorV2<DB> = DefaultExecutor_v2::new(fvm);
271 Ok(VM::VM2(exec))
272 }
273 }
274
275 pub fn flush(&mut self) -> anyhow::Result<Cid> {
277 Ok(delegate_vm!(self.flush()?))
278 }
279
280 pub fn get_actor(&self, addr: &Address) -> Result<Option<ActorState>, anyhow::Error> {
282 match self {
283 VM::VM2(fvm_executor) => Ok(fvm_executor
284 .state_tree()
285 .get_actor(&addr.into())?
286 .map(ActorState::from)),
287 VM::VM3(fvm_executor) => {
288 if let Some(id) = fvm_executor.state_tree().lookup_id(&addr.into())? {
289 Ok(fvm_executor
290 .state_tree()
291 .get_actor(id)?
292 .map(ActorState::from))
293 } else {
294 Ok(None)
295 }
296 }
297 VM::VM4(fvm_executor) => {
298 if let Some(id) = fvm_executor.state_tree().lookup_id(&addr.into())? {
299 Ok(fvm_executor
300 .state_tree()
301 .get_actor(id)?
302 .map(ActorState::from))
303 } else {
304 Ok(None)
305 }
306 }
307 }
308 }
309
310 pub fn run_cron(
311 &mut self,
312 epoch: ChainEpoch,
313 callback: Option<impl FnMut(MessageCallbackCtx<'_>) -> anyhow::Result<()>>,
314 ) -> anyhow::Result<()> {
315 let cron_msg: Message = Message_v3 {
316 from: Address::SYSTEM_ACTOR.into(),
317 to: Address::CRON_ACTOR.into(),
318 sequence: epoch as u64,
320 gas_limit: IMPLICIT_MESSAGE_GAS_LIMIT as u64,
322 method_num: cron::Method::EpochTick as u64,
323 params: Default::default(),
324 value: Default::default(),
325 version: Default::default(),
326 gas_fee_cap: Default::default(),
327 gas_premium: Default::default(),
328 }
329 .into();
330
331 let (ret, duration) = self.apply_implicit_message(&cron_msg)?;
332 if let Some(err) = ret.failure_info() {
333 anyhow::bail!("failed to apply block cron message: {}", err);
334 }
335
336 if let Some(mut callback) = callback {
337 callback(MessageCallbackCtx {
338 cid: cron_msg.cid(),
339 message: &ChainMessage::Unsigned(cron_msg),
340 apply_ret: &ret,
341 at: CalledAt::Cron,
342 duration,
343 })?;
344 }
345 Ok(())
346 }
347
348 pub fn apply_block_messages(
351 &mut self,
352 messages: &[BlockMessages],
353 epoch: ChainEpoch,
354 mut callback: Option<impl FnMut(MessageCallbackCtx<'_>) -> anyhow::Result<()>>,
355 ) -> ApplyBlockResult {
356 let mut receipts = Vec::new();
357 let mut events = Vec::new();
358 let mut events_roots: Vec<Option<Cid>> = Vec::new();
359 let mut processed = HashSet::default();
360
361 for block in messages.iter() {
362 let mut penalty = TokenAmount::zero();
363 let mut gas_reward = TokenAmount::zero();
364
365 let mut process_msg = |message: &ChainMessage| -> Result<(), anyhow::Error> {
366 let cid = message.cid();
367 if processed.contains(&cid) {
369 return Ok(());
370 }
371 let (ret, duration) = self.apply_message(message)?;
372
373 if let Some(cb) = &mut callback {
374 cb(MessageCallbackCtx {
375 cid,
376 message,
377 apply_ret: &ret,
378 at: CalledAt::Applied,
379 duration,
380 })?;
381 }
382
383 gas_reward += ret.miner_tip();
385 penalty += ret.penalty();
386 let msg_receipt = ret.msg_receipt();
387 receipts.push(msg_receipt.clone());
388
389 events_roots.push(ret.msg_receipt().events_root());
390 events.push(ret.events());
391
392 processed.insert(cid);
394 Ok(())
395 };
396
397 for msg in block.messages.iter() {
398 process_msg(msg)?;
399 }
400
401 if let Some(rew_msg) =
403 self.reward_message(epoch, block.miner, block.win_count, penalty, gas_reward)?
404 {
405 let (ret, duration) = self.apply_implicit_message(&rew_msg)?;
406 if let Some(err) = ret.failure_info() {
407 anyhow::bail!(
408 "failed to apply reward message for miner {}: {}",
409 block.miner,
410 err
411 );
412 }
413 if !ret.msg_receipt().exit_code().is_success() {
415 anyhow::bail!(
416 "reward application message failed (exit: {:?})",
417 ret.msg_receipt().exit_code()
418 );
419 }
420
421 if let Some(callback) = &mut callback {
422 callback(MessageCallbackCtx {
423 cid: rew_msg.cid(),
424 message: &ChainMessage::Unsigned(rew_msg),
425 apply_ret: &ret,
426 at: CalledAt::Reward,
427 duration,
428 })?
429 }
430 }
431 }
432
433 if let Err(e) = self.run_cron(epoch, callback.as_mut()) {
434 tracing::error!("End of epoch cron failed to run: {}", e);
435 }
436
437 Ok((receipts, events, events_roots))
438 }
439
440 pub fn apply_implicit_message(&mut self, msg: &Message) -> ApplyResult {
442 let start = Instant::now();
443
444 let raw_length = to_vec(msg).expect("encoding error").len();
446
447 let ret = match self {
448 VM::VM2(fvm_executor) => fvm_executor
449 .execute_message(msg.into(), fvm2::executor::ApplyKind::Implicit, raw_length)?
450 .into(),
451 VM::VM3(fvm_executor) => fvm_executor
452 .execute_message(msg.into(), fvm3::executor::ApplyKind::Implicit, raw_length)?
453 .into(),
454 VM::VM4(fvm_executor) => fvm_executor
455 .execute_message(msg.into(), fvm4::executor::ApplyKind::Implicit, raw_length)?
456 .into(),
457 };
458 Ok((ret, start.elapsed()))
459 }
460
461 pub fn apply_message(&mut self, msg: &ChainMessage) -> ApplyResult {
465 let start = Instant::now();
466
467 msg.message().check()?;
469
470 let unsigned = msg.message().clone();
471 let raw_length = to_vec(msg).expect("encoding error").len();
472 let ret: ApplyRet = match self {
473 VM::VM2(fvm_executor) => {
474 let ret = fvm_executor.execute_message(
475 unsigned.into(),
476 fvm2::executor::ApplyKind::Explicit,
477 raw_length,
478 )?;
479
480 if fvm_executor.externs().bail() {
481 bail!("encountered a database lookup error");
482 }
483
484 ret.into()
485 }
486 VM::VM3(fvm_executor) => {
487 let ret = fvm_executor.execute_message(
488 unsigned.into(),
489 fvm3::executor::ApplyKind::Explicit,
490 raw_length,
491 )?;
492
493 if fvm_executor.externs().bail() {
494 bail!("encountered a database lookup error");
495 }
496
497 ret.into()
498 }
499 VM::VM4(fvm_executor) => {
500 let ret = fvm_executor.execute_message(
501 unsigned.into(),
502 fvm4::executor::ApplyKind::Explicit,
503 raw_length,
504 )?;
505
506 if fvm_executor.externs().bail() {
507 bail!("encountered a database lookup error");
508 }
509
510 ret.into()
511 }
512 };
513 let duration = start.elapsed();
514
515 let exit_code = ret.msg_receipt().exit_code();
516
517 if !exit_code.is_success() {
518 tracing::debug!(?exit_code, "VM message execution failure.")
519 }
520
521 Ok((ret, duration))
522 }
523
524 fn reward_message(
525 &self,
526 epoch: ChainEpoch,
527 miner: Address,
528 win_count: i64,
529 penalty: TokenAmount,
530 gas_reward: TokenAmount,
531 ) -> Result<Option<Message>, anyhow::Error> {
532 let params = RawBytes::serialize(AwardBlockRewardParams {
533 miner: miner.into(),
534 penalty: penalty.into(),
535 gas_reward: gas_reward.into(),
536 win_count,
537 })?;
538 let rew_msg = Message_v3 {
539 from: Address::SYSTEM_ACTOR.into(),
540 to: Address::REWARD_ACTOR.into(),
541 method_num: reward::Method::AwardBlockReward as u64,
542 params,
543 sequence: epoch as u64,
545 gas_limit: IMPLICIT_MESSAGE_GAS_LIMIT as u64,
546 value: Default::default(),
547 version: Default::default(),
548 gas_fee_cap: Default::default(),
549 gas_premium: Default::default(),
550 };
551 Ok(Some(rew_msg.into()))
552 }
553}
554
555#[derive(Debug, Clone)]
556pub struct MessageCallbackCtx<'a> {
557 pub cid: Cid,
558 pub message: &'a ChainMessage,
559 pub apply_ret: &'a ApplyRet,
560 pub at: CalledAt,
561 pub duration: Duration,
562}
563
564#[derive(Debug, Clone, Copy)]
565pub enum CalledAt {
566 Applied,
567 Reward,
568 Cron,
569}
570
571impl CalledAt {
572 pub fn apply_kind(&self) -> fvm3::executor::ApplyKind {
574 use fvm3::executor::ApplyKind;
575 match self {
576 CalledAt::Applied => ApplyKind::Explicit,
577 CalledAt::Reward | CalledAt::Cron => ApplyKind::Implicit,
578 }
579 }
580}
581
582#[derive(Default, Clone, Copy)]
585pub enum VMTrace {
586 Traced,
588 #[default]
590 NotTraced,
591}
592
593impl VMTrace {
594 pub fn is_traced(&self) -> bool {
596 matches!(self, VMTrace::Traced)
597 }
598}