Skip to content

Commit

Permalink
feat: forest-tool snapshot compute-state (#3430)
Browse files Browse the repository at this point in the history
Co-authored-by: elmattic <elmattic@users.noreply.github.com>
Co-authored-by: David Himmelstrup <david.himmelstrup@chainsafe.io>
  • Loading branch information
3 people authored Sep 8, 2023
1 parent fc86348 commit e0b5e87
Show file tree
Hide file tree
Showing 21 changed files with 914 additions and 114 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@

### Added

- [#3430](https://github.com/ChainSafe/forest/pull/3430): Add
`forest-tool snapshot compute-state ...` subcommand.
- [#3321](https://github.com/ChainSafe/forest/issues/3321): Support for
multi-threaded car-backed block stores.
- [#3316](https://github.com/ChainSafe/forest/pull/3316): Add
Expand Down
2 changes: 2 additions & 0 deletions src/chain/store/chain_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use std::sync::Arc;
use crate::blocks::{BlockHeader, Tipset, TipsetKeys, TxMeta};
use crate::fil_cns;
use crate::interpreter::BlockMessages;
use crate::interpreter::VMTrace;
use crate::ipld::FrozenCids;
use crate::libp2p_bitswap::{BitswapStoreRead, BitswapStoreReadWrite};
use crate::message::{ChainMessage, Message as MessageTrait, SignedMessage};
Expand Down Expand Up @@ -292,6 +293,7 @@ where
&crate::shim::machine::MultiEngine::default(),
Arc::clone(&heaviest_tipset),
crate::state_manager::NO_CALLBACK,
VMTrace::NotTraced,
)
.map_err(|e| Error::Other(e.to_string()))?;
return Ok((heaviest_tipset, state));
Expand Down
111 changes: 89 additions & 22 deletions src/interpreter/vm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use crate::blocks::Tipset;
use crate::chain::block_messages;
use crate::chain::index::ChainIndex;
use crate::chain::store::Error;
use crate::interpreter::{fvm2::ForestExternsV2, fvm3::ForestExterns as ForestExternsV3};
use crate::message::ChainMessage;
use crate::message::Message as MessageTrait;
use crate::networks::{ChainConfig, NetworkChain};
Expand Down Expand Up @@ -40,11 +41,9 @@ use fvm3::{
};
use fvm_ipld_blockstore::Blockstore;
use fvm_ipld_encoding::{to_vec, RawBytes};
use fvm_shared2::{clock::ChainEpoch, BLOCK_GAS_LIMIT};
use fvm_shared2::clock::ChainEpoch;
use num::Zero;

use crate::interpreter::{fvm2::ForestExternsV2, fvm3::ForestExterns as ForestExternsV3};

pub(in crate::interpreter) type ForestMachineV2<DB> =
DefaultMachine_v2<Arc<DB>, ForestExternsV2<DB>>;
pub(in crate::interpreter) type ForestMachineV3<DB> =
Expand All @@ -57,6 +56,9 @@ type ForestKernelV3<DB> =
type ForestExecutorV2<DB> = DefaultExecutor_v2<ForestKernelV2<DB>>;
type ForestExecutorV3<DB> = DefaultExecutor_v3<ForestKernelV3<DB>>;

/// Comes from <https://github.com/filecoin-project/lotus/blob/v1.23.2/chain/vm/fvm.go#L473>
const IMPLICIT_MESSAGE_GAS_LIMIT: i64 = i64::MAX / 2;

/// Contains all messages to process through the VM as well as miner information
/// for block rewards.
#[derive(Debug)]
Expand Down Expand Up @@ -159,6 +161,7 @@ where
timestamp,
}: ExecutionContext<DB>,
multi_engine: &MultiEngine,
enable_tracing: VMTrace,
) -> Result<Self, anyhow::Error> {
let network_version = chain_config.network_version(epoch);
if network_version >= NetworkVersion::V18 {
Expand All @@ -173,6 +176,8 @@ where
let mut context = config.for_epoch(epoch, timestamp, state_tree_root);
context.set_base_fee(base_fee.into());
context.set_circulating_supply(circ_supply.into());
context.tracing = enable_tracing.is_traced();

let fvm: ForestMachineV3<DB> = ForestMachineV3::new(
&context,
Arc::clone(&chain_index.db),
Expand All @@ -193,6 +198,8 @@ where
let mut context = config.for_epoch(epoch, state_tree_root);
context.set_base_fee(base_fee.into());
context.set_circulating_supply(circ_supply.into());
context.tracing = enable_tracing.is_traced();

let fvm: ForestMachineV2<DB> = ForestMachineV2::new(
&engine,
&context,
Expand Down Expand Up @@ -242,17 +249,15 @@ where
pub fn run_cron(
&mut self,
epoch: ChainEpoch,
callback: Option<
&mut impl FnMut(&Cid, &ChainMessage, &ApplyRet) -> Result<(), anyhow::Error>,
>,
) -> Result<(), anyhow::Error> {
callback: Option<impl FnMut(&MessageCallbackCtx) -> anyhow::Result<()>>,
) -> anyhow::Result<()> {
let cron_msg: Message = Message_v3 {
from: Address::SYSTEM_ACTOR.into(),
to: Address::CRON_ACTOR.into(),
// Epoch as sequence is intentional
sequence: epoch as u64,
// Arbitrarily large gas limit for cron (matching Lotus value)
gas_limit: BLOCK_GAS_LIMIT as u64 * 10000,
gas_limit: IMPLICIT_MESSAGE_GAS_LIMIT as u64,
method_num: cron::Method::EpochTick as u64,
params: Default::default(),
value: Default::default(),
Expand All @@ -267,8 +272,13 @@ where
anyhow::bail!("failed to apply block cron message: {}", err);
}

if let Some(callback) = callback {
callback(&(cron_msg.cid()?), &ChainMessage::Unsigned(cron_msg), &ret)?;
if let Some(mut callback) = callback {
callback(&MessageCallbackCtx {
cid: cron_msg.cid()?,
message: &ChainMessage::Unsigned(cron_msg),
apply_ret: &ret,
at: CalledAt::Cron,
})?;
}
Ok(())
}
Expand All @@ -279,9 +289,9 @@ where
&mut self,
messages: &[BlockMessages],
epoch: ChainEpoch,
mut callback: Option<
impl FnMut(&Cid, &ChainMessage, &ApplyRet) -> Result<(), anyhow::Error>,
>,
// note: we take &MessageCallbackCtx rather than MessageCallbackCtx<'_>
// because I'm not smart enough to make the second one work
mut callback: Option<impl FnMut(&MessageCallbackCtx) -> anyhow::Result<()>>,
) -> Result<Vec<Receipt>, anyhow::Error> {
let mut receipts = Vec::new();
let mut processed = HashSet::<Cid>::default();
Expand All @@ -290,22 +300,28 @@ where
let mut penalty = TokenAmount::zero();
let mut gas_reward = TokenAmount::zero();

let mut process_msg = |msg: &ChainMessage| -> Result<(), anyhow::Error> {
let cid = msg.cid()?;
let mut process_msg = |message: &ChainMessage| -> Result<(), anyhow::Error> {
let cid = message.cid()?;
// Ensure no duplicate processing of a message
if processed.contains(&cid) {
return Ok(());
}
let ret = self.apply_message(msg)?;
let ret = self.apply_message(message)?;

if let Some(cb) = &mut callback {
cb(&cid, msg, &ret)?;
cb(&MessageCallbackCtx {
cid,
message,
apply_ret: &ret,
at: CalledAt::Applied,
})?;
}

// Update totals
gas_reward += ret.miner_tip();
penalty += ret.penalty();
receipts.push(ret.msg_receipt());
let msg_receipt = ret.msg_receipt();
receipts.push(msg_receipt.clone());

// Add processed Cid to set of processed messages
processed.insert(cid);
Expand Down Expand Up @@ -335,20 +351,27 @@ where
ret.msg_receipt().exit_code()
);
}

if let Some(callback) = &mut callback {
callback(&(rew_msg.cid()?), &ChainMessage::Unsigned(rew_msg), &ret)?;
callback(&MessageCallbackCtx {
cid: rew_msg.cid()?,
message: &ChainMessage::Unsigned(rew_msg),
apply_ret: &ret,
at: CalledAt::Reward,
})?
}
}
}

if let Err(e) = self.run_cron(epoch, callback.as_mut()) {
tracing::error!("End of epoch cron failed to run: {}", e);
}

Ok(receipts)
}

/// Applies single message through VM and returns result from execution.
pub fn apply_implicit_message(&mut self, msg: &Message) -> Result<ApplyRet, anyhow::Error> {
pub fn apply_implicit_message(&mut self, msg: &Message) -> anyhow::Result<ApplyRet> {
// raw_length is not used for Implicit messages.
let raw_length = to_vec(msg).expect("encoding error").len();

Expand All @@ -375,7 +398,7 @@ where
/// Applies the state transition for a single message.
/// Returns `ApplyRet` structure which contains the message receipt and some
/// meta data.
pub fn apply_message(&mut self, msg: &ChainMessage) -> Result<ApplyRet, anyhow::Error> {
pub fn apply_message(&mut self, msg: &ChainMessage) -> anyhow::Result<ApplyRet> {
// Basic validity check
msg.message().check()?;

Expand Down Expand Up @@ -440,7 +463,7 @@ where
params,
// Epoch as sequence is intentional
sequence: epoch as u64,
gas_limit: 1 << 30,
gas_limit: IMPLICIT_MESSAGE_GAS_LIMIT as u64,
value: Default::default(),
version: Default::default(),
gas_fee_cap: Default::default(),
Expand All @@ -449,3 +472,47 @@ where
Ok(Some(rew_msg.into()))
}
}

#[derive(Debug, Clone, Copy)]
pub struct MessageCallbackCtx<'a> {
pub cid: Cid,
pub message: &'a ChainMessage,
pub apply_ret: &'a ApplyRet,
pub at: CalledAt,
}

#[derive(Debug, Clone, Copy)]
pub enum CalledAt {
Applied,
Reward,
Cron,
}

impl CalledAt {
/// Was [`VM::apply_message`] or [`VM::apply_implicit_message`] called?
pub fn apply_kind(&self) -> fvm3::executor::ApplyKind {
use fvm3::executor::ApplyKind;
match self {
CalledAt::Applied => ApplyKind::Explicit,
CalledAt::Reward | CalledAt::Cron => ApplyKind::Implicit,
}
}
}

/// Tracing a Filecoin VM has a performance penalty.
/// This controls whether a VM should be traced or not when it is created.
#[derive(Default, Clone, Copy)]
pub enum VMTrace {
/// Collect trace for the given operation
Traced,
/// Do not collect trace
#[default]
NotTraced,
}

impl VMTrace {
/// Should tracing be collected?
pub fn is_traced(&self) -> bool {
matches!(self, VMTrace::Traced)
}
}
2 changes: 1 addition & 1 deletion src/lotus_json/beacon_entry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ impl HasLotusJson for BeaconEntry {
type LotusJson = BeaconEntryLotusJson;

fn snapshots() -> Vec<(serde_json::Value, Self)> {
vec![(json!({"Round": 0, "Data": ""}), BeaconEntry::default())]
vec![(json!({"Round": 0, "Data": null}), BeaconEntry::default())]
}

fn into_lotus_json(self) -> Self::LotusJson {
Expand Down
2 changes: 1 addition & 1 deletion src/lotus_json/election_proof.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ impl HasLotusJson for ElectionProof {
vec![(
json!({
"WinCount": 0,
"VRFProof": ""
"VRFProof": null
}),
ElectionProof::default(),
)]
Expand Down
20 changes: 15 additions & 5 deletions src/lotus_json/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ pub struct MessageLotusJson {
method: LotusJson<u64>,
#[serde(skip_serializing_if = "LotusJson::is_none", default)]
params: LotusJson<Option<RawBytes>>,
// This is a bit of a hack - `Message`s don't really store their CID, but they're
// serialized with it.
// However, getting a message's CID is fallible...
// So we keep this as an `Option`, and ignore it if it fails.
// We also ignore it when serializing from json.
// I wouldn't be surprised if this causes issues with arbitrary tests
#[serde(rename = "CID", skip_serializing_if = "LotusJson::is_none", default)]
cid: LotusJson<Option<Cid>>,
}
Expand All @@ -33,20 +39,24 @@ impl HasLotusJson for Message {
json!({
"From": "f00",
"GasFeeCap": "0",
"GasLimit": 0, // BUG?(aatifsyed)
"GasLimit": 0,
"GasPremium": "0",
"Method": 0,
"Nonce": 0,
"Params": "", // BUG?(aatifsyed)
"Params": null,
"To": "f00",
"Value": "0",
"Version": 0
"Version": 0,
"CID": {
"/": "bafy2bzaced3xdk2uf6azekyxgcttujvy3fzyeqmibtpjf2fxcpfdx2zcx4s3g"
}
}),
Message::default(),
)]
}

fn into_lotus_json(self) -> Self::LotusJson {
let cid = self.cid().ok();
let Self {
version,
from,
Expand All @@ -70,7 +80,7 @@ impl HasLotusJson for Message {
gas_premium: gas_premium.into(),
method: method_num.into(),
params: Some(params).into(),
cid: None.into(),
cid: cid.into(),
}
}

Expand All @@ -86,7 +96,7 @@ impl HasLotusJson for Message {
gas_premium,
method,
params,
cid: _ignored, // BUG?(aatifsyed)
cid: _ignored,
} = lotus_json;
Self {
version: version.into_inner(),
Expand Down
14 changes: 12 additions & 2 deletions src/lotus_json/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,10 @@
//! - Use destructuring to ensure exhaustiveness
//!
//! ### Optional fields
//! Optional fields must have the following annotations:
//! It's not clear if optional fields should be serialized as `null` or not.
//! See e.g `LotusJson<Receipt>`.
//!
//! For now, fields are recommended to have the following annotations:
//! ```rust,ignore
//! # struct Foo {
//! #[serde(skip_serializing_if = "LotusJson::is_none", default)]
Expand Down Expand Up @@ -371,4 +374,11 @@ macro_rules! lotus_json_with_self {
}
}

lotus_json_with_self!(u32, u64, i64, String, chrono::DateTime<chrono::Utc>);
lotus_json_with_self!(
u32,
u64,
i64,
String,
chrono::DateTime<chrono::Utc>,
serde_json::Value,
);
Loading

0 comments on commit e0b5e87

Please sign in to comment.