use super::utils::structured;
use super::*;
use crate::interpreter::{ExecutionContext, IMPLICIT_MESSAGE_GAS_LIMIT, VM, VMTrace};
use crate::message::{MessageRead as _, MessageReadWrite as _};
use crate::rpc::state::{ApiInvocResult, MessageGasCost};
use crate::shim::executor::ApplyRet;
use crate::shim::message::{METHOD_SEND, Message};
use crate::state_migration::run_state_migrations;
use std::time::Duration;
use tracing::instrument;
impl StateManager {
#[instrument(skip(self))]
pub fn call_blocking(
&self,
msg: &Message,
tipset: Option<Tipset>,
) -> Result<ApiInvocResult, Error> {
let mut msg = msg.clone();
let chain_config = self.chain_config();
let tipset = if let Some(ts) = tipset {
if ts.epoch() > 0 {
let parent = self
.chain_index()
.load_required_tipset(ts.parents())
.map_err(Error::other)?;
if let Some(epoch) =
chain_config.expensive_fork_between(parent.epoch(), ts.epoch() + 1)
{
return Err(Error::ExpensiveFork { epoch });
}
}
ts
} else {
let mut heaviest_ts = self.heaviest_tipset();
while heaviest_ts.epoch() > 0 {
let parent = self
.chain_index()
.load_required_tipset(heaviest_ts.parents())
.map_err(Error::other)?;
if !chain_config.has_expensive_fork_between(parent.epoch(), heaviest_ts.epoch() + 1)
{
break;
}
heaviest_ts = parent;
}
heaviest_ts
};
let state_cid = *tipset.parent_state();
let state_cid = match run_state_migrations(
tipset.epoch(),
self.chain_config(),
self.db(),
&state_cid,
) {
Ok(Some(new_state)) => new_state,
Ok(None) => state_cid,
Err(e) => return Err(Error::other(e)),
};
let height = tipset.epoch();
let mut vm = VM::new(
ExecutionContext {
heaviest_tipset: tipset.shallow_clone(),
state_tree_root: state_cid,
epoch: height,
rand: Box::new(self.chain_rand(tipset.shallow_clone())),
base_fee: tipset.block_headers().first().parent_base_fee.clone(),
circ_supply: self.genesis_info().get_vm_circulating_supply(
height,
self.db(),
&state_cid,
)?,
chain_config: self.chain_config().shallow_clone(),
chain_index: self.chain_index().shallow_clone(),
timestamp: tipset.min_timestamp(),
},
&self.engine,
VMTrace::Traced,
)?;
let tipset_messages = self
.chain_store()
.messages_for_tipset(&tipset)
.map_err(|err| Error::Other(err.to_string()))?;
let prior_messsages = tipset_messages
.iter()
.filter(|ts_msg| ts_msg.message().from() == msg.from());
for m in prior_messsages {
vm.apply_message(m)?;
}
let state_cid = vm.flush()?;
let state = StateTree::new_from_root(self.db(), &state_cid)?;
let from_actor = state
.get_actor(&msg.from())?
.ok_or_else(|| anyhow::anyhow!("actor not found"))?;
msg.set_sequence(from_actor.sequence);
msg.gas_limit = IMPLICIT_MESSAGE_GAS_LIMIT as u64;
let (apply_ret, duration) = vm.apply_implicit_message(&msg)?;
let msg_cid = msg.cid();
Ok(ApiInvocResult {
msg,
msg_rct: Some(apply_ret.msg_receipt()),
msg_cid,
error: apply_ret.failure_info().unwrap_or_default(),
duration: duration.as_nanos().clamp(0, u128::from(u64::MAX)) as u64,
gas_cost: MessageGasCost::default(),
execution_trace: structured::parse_events(apply_ret.exec_trace()).unwrap_or_default(),
})
}
pub async fn call(
&self,
message: Arc<Message>,
tipset: Option<Tipset>,
) -> Result<ApiInvocResult, Error> {
let this = self.shallow_clone();
tokio::task::spawn_blocking(move || this.call_blocking(&message, tipset)).await?
}
pub async fn apply_on_state_with_gas(
&self,
tipset: Option<&Tipset>,
msg: &Message,
vm_flush: VMFlush,
vm_trace: VMTrace,
sender_validation: SenderValidation,
) -> anyhow::Result<(ApiInvocResult, Option<Cid>)> {
let ts = tipset.map_or_else(|| self.heaviest_tipset(), Tipset::shallow_clone);
let from_protocol = match sender_validation {
SenderValidation::Skip => msg.from.protocol(),
SenderValidation::Enforce => self
.resolve_to_deterministic_address(msg.from, &ts)
.await
.context("could not resolve key")?
.protocol(),
};
let chain_msg = ChainMessage::for_gas_estimation(msg.clone(), from_protocol);
let (apply_ret, duration, state_root) = self
.call_with_gas(
chain_msg,
Default::default(),
Some(ts),
vm_flush,
vm_trace,
sender_validation,
)
.await?;
let msg_rct = Some(apply_ret.msg_receipt());
let error = apply_ret.failure_info().unwrap_or_default();
Ok((
ApiInvocResult {
msg_cid: msg.cid(),
msg: msg.clone(),
msg_rct,
error,
duration: duration.as_nanos().clamp(0, u128::from(u64::MAX)) as u64,
gas_cost: MessageGasCost::default(),
execution_trace: structured::parse_events(apply_ret.into_exec_trace())
.unwrap_or_default(),
},
state_root,
))
}
pub async fn call_with_gas(
&self,
mut message: ChainMessage,
prior_messages: Arc<Vec<ChainMessage>>,
tipset: Option<Tipset>,
vm_flush: VMFlush,
vm_trace: VMTrace,
sender_validation: SenderValidation,
) -> Result<(ApplyRet, Duration, Option<Cid>), Error> {
let ts = tipset.unwrap_or_else(|| self.heaviest_tipset());
let TipsetState { state_root, .. } = self
.load_tipset_state(&ts)
.await
.map_err(|e| Error::Other(format!("Could not load tipset state: {e:#}")))?;
let chain_rand = self.chain_rand(ts.clone());
let epoch = ts.epoch() + 1;
let this = self.shallow_clone();
tokio::task::spawn_blocking(move || {
let (ret, duration, state_cid) = stacker::grow(64 << 20, || -> Result<_, Error> {
let mut vm = VM::new(
ExecutionContext {
heaviest_tipset: ts.clone(),
state_tree_root: state_root,
epoch,
rand: Box::new(chain_rand),
base_fee: ts.block_headers().first().parent_base_fee.clone(),
circ_supply: this.genesis_info().get_vm_circulating_supply(
epoch,
this.chain_index().db(),
&state_root,
)?,
chain_config: this.chain_config().shallow_clone(),
chain_index: this.chain_index().shallow_clone(),
timestamp: ts.min_timestamp(),
},
&this.engine,
vm_trace,
)?;
for msg in prior_messages.iter() {
vm.apply_message(msg)?;
}
let (from_actor, apply) =
sender_for_simulation(&mut vm, message.from(), sender_validation)?;
message.set_sequence(from_actor.sequence);
let (ret, duration) = match apply {
SenderApply::Implicit => vm.apply_implicit_message(message.message())?,
SenderApply::Explicit => vm.apply_message(&message)?,
};
let state_root = match vm_flush {
VMFlush::Flush => Some(vm.flush()?),
VMFlush::Skip => None,
};
Ok((ret, duration, state_root))
})?;
Ok((ret, duration, state_cid))
})
.await?
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
enum SenderApply {
Explicit,
Implicit,
}
fn sender_for_simulation(
vm: &mut VM,
from: Address,
sender_validation: SenderValidation,
) -> Result<(ActorState, SenderApply), Error> {
match (
vm.get_actor(&from)
.map_err(|e| Error::Other(format!("Could not get actor from state: {e:#}")))?,
sender_validation,
) {
(Some(actor), SenderValidation::Enforce) => Ok((actor, SenderApply::Explicit)),
(Some(actor), SenderValidation::Skip) => Ok((actor, SenderApply::Implicit)),
(None, SenderValidation::Enforce) => Err(Error::SenderValidationFailed(format!(
"sender {from} not found on chain"
))),
(None, SenderValidation::Skip) => {
let (create_ret, _) = vm.apply_implicit_message(&placeholder_send(from))?;
let exit_code = create_ret.msg_receipt().exit_code();
if !exit_code.is_success() {
return Err(Error::Other(format!(
"failed to create ephemeral sender placeholder {from} (exit={exit_code}): {}",
create_ret.failure_info().unwrap_or_default()
)));
}
let actor = vm
.get_actor(&from)
.map_err(|e| Error::Other(format!("Could not get placeholder actor: {e:#}")))?
.ok_or_else(|| {
Error::Other(format!(
"ephemeral sender placeholder {from} missing after creation"
))
})?;
Ok((actor, SenderApply::Explicit))
}
}
}
fn placeholder_send(to: Address) -> Message {
Message {
from: Address::SYSTEM_ACTOR,
to,
method_num: METHOD_SEND,
gas_limit: IMPLICIT_MESSAGE_GAS_LIMIT as u64,
..Default::default()
}
}