Skip to content

Commit 323e119

Browse files
authored
test: unit tests for state migrations (#6486)
1 parent 0066ae3 commit 323e119

File tree

15 files changed

+314
-158
lines changed

15 files changed

+314
-158
lines changed

.github/workflows/coverage.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ env:
3939
FOREST_F3_SIDECAR_FFI_BUILD_OPT_OUT: 1
4040
FIL_PROOFS_PARAMETER_CACHE: /var/tmp/filecoin-proof-parameters
4141
RUST_LOG: error
42+
FOREST_ACTOR_BUNDLE_PATH: /var/tmp/forest_actor_bundle.car.zst
4243

4344
jobs:
4445
codecov:
@@ -53,7 +54,7 @@ jobs:
5354
- uses: taiki-e/install-action@nextest
5455
- name: Fetch proof params and RPC test snapshots
5556
run: |
56-
cargo run --bin forest-dev --no-default-features --profile quick -- fetch-rpc-tests
57+
cargo run --bin forest-dev --no-default-features --profile quick -- fetch-test-snapshots --actor-bundle $FOREST_ACTOR_BUNDLE_PATH
5758
ls -ahl $FIL_PROOFS_PARAMETER_CACHE
5859
- name: Generate code coverage
5960
run: mise codecov

.github/workflows/unit-tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ jobs:
7777
uses: taiki-e/install-action@nextest
7878
- name: Fetch proof params and RPC test snapshots
7979
run: |
80-
cargo run --bin forest-dev --no-default-features --profile quick -- fetch-rpc-tests
80+
cargo run --bin forest-dev --no-default-features --profile quick -- fetch-test-snapshots
8181
ls -ahl $FIL_PROOFS_PARAMETER_CACHE
8282
- uses: jdx/mise-action@v3
8383
- run: |

docs/docs/users/reference/cli.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ generate_markdown_section "forest-tool" "index backfill"
157157

158158
generate_markdown_section "forest-dev" ""
159159

160-
generate_markdown_section "forest-dev" "fetch-rpc-tests"
160+
generate_markdown_section "forest-dev" "fetch-test-snapshots"
161161

162162
generate_markdown_section "forest-dev" "state"
163163
generate_markdown_section "forest-dev" "state compute"

src/benchmark_private/tipset_validation.rs

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,29 +19,39 @@ pub fn bench_tipset_validation(c: &mut Criterion) {
1919
let mut group = c.benchmark_group("tipset_validation");
2020

2121
group
22-
.bench_function("calibnet@3111900", |b| {
22+
.bench_function("calibnet@3408952", |b| {
2323
let chain = NetworkChain::Calibnet;
24-
let epoch = 3111900;
25-
let (state_manager, ts) = rt
24+
let epoch = 3408952;
25+
let (state_manager, ts, ts_next) = rt
2626
.block_on(async {
2727
let snapshot = get_state_compute_snapshot(&chain, epoch).await?;
28-
prepare_state_compute(&chain, &snapshot, true).await
28+
prepare_state_compute(&chain, &snapshot).await
2929
})
3030
.unwrap();
31-
b.to_async(&rt)
32-
.iter(|| state_compute(black_box(state_manager.clone()), black_box(ts.clone())))
31+
b.to_async(&rt).iter(|| {
32+
state_compute(
33+
black_box(&state_manager),
34+
black_box(ts.clone()),
35+
black_box(&ts_next),
36+
)
37+
})
3338
})
34-
.bench_function("mainnet@5427431", |b| {
39+
.bench_function("mainnet@5709604", |b| {
3540
let chain = NetworkChain::Mainnet;
36-
let epoch = 5427431;
37-
let (state_manager, ts) = rt
41+
let epoch = 5709604;
42+
let (state_manager, ts, ts_next) = rt
3843
.block_on(async {
3944
let snapshot = get_state_compute_snapshot(&chain, epoch).await?;
40-
prepare_state_compute(&chain, &snapshot, true).await
45+
prepare_state_compute(&chain, &snapshot).await
4146
})
4247
.unwrap();
43-
b.to_async(&rt)
44-
.iter(|| state_compute(black_box(state_manager.clone()), black_box(ts.clone())))
48+
b.to_async(&rt).iter(|| {
49+
state_compute(
50+
black_box(&state_manager),
51+
black_box(ts.clone()),
52+
black_box(&ts_next),
53+
)
54+
})
4555
});
4656

4757
group.finish();

src/blocks/tipset.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,10 @@ impl Tipset {
317317
pub fn parent_state(&self) -> &Cid {
318318
&self.min_ticket_block().state_root
319319
}
320+
/// Returns the message receipt root for the tipset parent.
321+
pub fn parent_message_receipts(&self) -> &Cid {
322+
&self.min_ticket_block().message_receipts
323+
}
320324
/// Returns the tipset's calculated weight
321325
pub fn weight(&self) -> &BigInt {
322326
&self.min_ticket_block().weight

src/chain_sync/tipset_syncer.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use crate::shim::{
1111
address::Address, crypto::verify_bls_aggregate, econ::BLOCK_GAS_LIMIT,
1212
gas::price_list_by_network_version, message::Message, state_tree::StateTree,
1313
};
14+
use crate::state_manager::StateLookupPolicy;
1415
use crate::state_manager::{Error as StateManagerError, StateManager, utils::is_valid_for_sending};
1516
use crate::{
1617
blocks::{Block, CachingBlockHeader, Error as ForestBlockError, FullTipset, Tipset},
@@ -278,7 +279,7 @@ async fn validate_block<DB: Blockstore + Sync + Send + 'static>(
278279
async move {
279280
let header = block.header();
280281
let (state_root, receipt_root) = state_manager
281-
.tipset_state(&base_tipset)
282+
.tipset_state(&base_tipset, StateLookupPolicy::Disabled)
282283
.await
283284
.map_err(|e| {
284285
TipsetSyncerError::Calculation(format!("Failed to calculate state: {e}"))
@@ -439,7 +440,7 @@ async fn check_block_messages<DB: Blockstore + Send + Sync + 'static>(
439440

440441
let mut account_sequences: HashMap<Address, u64> = HashMap::default();
441442
let (state_root, _) = state_manager
442-
.tipset_state(&base_tipset)
443+
.tipset_state(&base_tipset, StateLookupPolicy::Disabled)
443444
.await
444445
.map_err(|e| TipsetSyncerError::Calculation(format!("Could not update state: {e}")))?;
445446
let tree =

src/dev/subcommands/mod.rs

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
mod state_cmd;
55

66
use crate::cli_shared::cli::HELP_MESSAGE;
7+
use crate::networks::generate_actor_bundle;
78
use crate::rpc::Client;
89
use crate::utils::net::{DownloadFileOption, download_file_with_cache};
910
use crate::utils::proofs_api::ensure_proof_params_downloaded;
@@ -30,26 +31,43 @@ pub struct Cli {
3031
/// forest-dev sub-commands
3132
#[derive(clap::Subcommand)]
3233
pub enum Subcommand {
33-
/// Fetch RPC test snapshots to the local cache
34-
FetchRpcTests,
34+
/// Fetch test snapshots to the local cache
35+
FetchTestSnapshots {
36+
// Save actor bundle to
37+
#[arg(long)]
38+
actor_bundle: Option<PathBuf>,
39+
},
3540
#[command(subcommand)]
3641
State(state_cmd::StateCommand),
3742
}
3843

3944
impl Subcommand {
4045
pub async fn run(self, _client: Client) -> anyhow::Result<()> {
4146
match self {
42-
Self::FetchRpcTests => fetch_rpc_tests().await,
47+
Self::FetchTestSnapshots { actor_bundle } => fetch_test_snapshots(actor_bundle).await,
4348
Self::State(cmd) => cmd.run().await,
4449
}
4550
}
4651
}
4752

48-
async fn fetch_rpc_tests() -> anyhow::Result<()> {
53+
async fn fetch_test_snapshots(actor_bundle: Option<PathBuf>) -> anyhow::Result<()> {
54+
// Prepare proof parameter files
4955
crate::utils::proofs_api::maybe_set_proofs_parameter_cache_dir_env(
5056
&crate::Config::default().client.data_dir,
5157
);
5258
ensure_proof_params_downloaded().await?;
59+
60+
// Prepare actor bundles
61+
if let Some(actor_bundle) = actor_bundle {
62+
generate_actor_bundle(&actor_bundle).await?;
63+
println!("Wrote the actors bundle to {}", actor_bundle.display());
64+
}
65+
66+
// Prepare RPC test snapshots
67+
fetch_rpc_tests().await
68+
}
69+
70+
async fn fetch_rpc_tests() -> anyhow::Result<()> {
5371
let tests = include_str!("../../tool/subcommands/api_cmd/test_snapshots.txt")
5472
.lines()
5573
.map(|i| {

src/dev/subcommands/state_cmd.rs

Lines changed: 39 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,7 @@ use crate::{
66
chain::{ChainStore, index::ResolveNullTipset},
77
chain_sync::{load_full_tipset, tipset_syncer::validate_tipset},
88
cli_shared::{chain_path, read_config},
9-
db::{
10-
MemoryDB, SettingsStoreExt,
11-
car::{AnyCar, ManyCar},
12-
db_engine::db_root,
13-
},
9+
db::{SettingsStoreExt, db_engine::db_root},
1410
genesis::read_genesis_header,
1511
interpreter::VMTrace,
1612
networks::{ChainConfig, NetworkChain},
@@ -73,7 +69,7 @@ impl ComputeCommand {
7369
let (_, config) = read_config(None, Some(chain.clone()))?;
7470
db_root(&chain_path(&config))?
7571
};
76-
let db = generate_test_snapshot::load_db(&db_root_path)?;
72+
let db = generate_test_snapshot::load_db(&db_root_path, Some(&chain)).await?;
7773
let chain_config = Arc::new(ChainConfig::from_chain(&chain));
7874
let genesis_header =
7975
read_genesis_header(None, chain_config.genesis_bytes(&db).await?.as_deref(), &db)
@@ -85,18 +81,30 @@ impl ComputeCommand {
8581
chain_config,
8682
genesis_header,
8783
)?);
88-
let ts = {
84+
let (ts, ts_next) = {
8985
// We don't want to track all entries that are visited by `tipset_by_height`
9086
db.pause_tracking();
9187
let ts = chain_store.chain_index().tipset_by_height(
9288
epoch,
9389
chain_store.heaviest_tipset(),
9490
ResolveNullTipset::TakeOlder,
9591
)?;
92+
let ts_next = chain_store.chain_index().tipset_by_height(
93+
epoch + 1,
94+
chain_store.heaviest_tipset(),
95+
ResolveNullTipset::TakeNewer,
96+
)?;
9697
db.resume_tracking();
97-
SettingsStoreExt::write_obj(&db.tracker, crate::db::setting_keys::HEAD_KEY, ts.key())?;
98-
// Only track the desired tipset
99-
Tipset::load_required(&db, ts.key())?
98+
SettingsStoreExt::write_obj(
99+
&db.tracker,
100+
crate::db::setting_keys::HEAD_KEY,
101+
ts_next.key(),
102+
)?;
103+
// Only track the desired tipsets
104+
(
105+
Tipset::load_required(&db, ts.key())?,
106+
Tipset::load_required(&db, ts_next.key())?,
107+
)
100108
};
101109
let epoch = ts.epoch();
102110
let state_manager = Arc::new(StateManager::new(chain_store)?);
@@ -114,6 +122,16 @@ impl ComputeCommand {
114122
"epoch: {epoch}, state_root: {state_root}, receipt_root: {receipt_root}, db_snapshot_size: {}",
115123
human_bytes::human_bytes(db_snapshot.len() as f64)
116124
);
125+
let expected_state_root = *ts_next.parent_state();
126+
let expected_receipt_root = *ts_next.parent_message_receipts();
127+
anyhow::ensure!(
128+
state_root == expected_state_root,
129+
"state root mismatch, state_root: {state_root}, expected_state_root: {expected_state_root}"
130+
);
131+
anyhow::ensure!(
132+
receipt_root == expected_receipt_root,
133+
"receipt root mismatch, receipt_root: {receipt_root}, expected_receipt_root: {expected_receipt_root}"
134+
);
117135
if let Some(export_db_to) = export_db_to {
118136
std::fs::write(export_db_to, db_snapshot)?;
119137
}
@@ -138,39 +156,12 @@ pub struct ReplayComputeCommand {
138156
impl ReplayComputeCommand {
139157
pub async fn run(self) -> anyhow::Result<()> {
140158
let Self { snapshot, chain, n } = self;
141-
let snap_car = AnyCar::try_from(&snapshot)?;
142-
let ts = snap_car.heaviest_tipset()?;
143-
let epoch = ts.epoch();
144-
let db = Arc::new(ManyCar::new(MemoryDB::default()).with_read_only(snap_car)?);
145-
let chain_config = Arc::new(ChainConfig::from_chain(&chain));
146-
let genesis_header =
147-
read_genesis_header(None, chain_config.genesis_bytes(&db).await?.as_deref(), &db)
159+
let (sm, ts, ts_next) =
160+
crate::state_manager::utils::state_compute::prepare_state_compute(&chain, &snapshot)
148161
.await?;
149-
let chain_store = Arc::new(ChainStore::new(
150-
db.clone(),
151-
db.clone(),
152-
db.clone(),
153-
chain_config,
154-
genesis_header,
155-
)?);
156-
let state_manager = Arc::new(StateManager::new(chain_store)?);
157162
for _ in 0..n.get() {
158-
let start = Instant::now();
159-
let StateOutput {
160-
state_root,
161-
receipt_root,
162-
..
163-
} = state_manager
164-
.compute_tipset_state(
165-
ts.clone(),
166-
crate::state_manager::NO_CALLBACK,
167-
VMTrace::NotTraced,
168-
)
163+
crate::state_manager::utils::state_compute::state_compute(&sm, ts.clone(), &ts_next)
169164
.await?;
170-
println!(
171-
"epoch: {epoch}, state_root: {state_root}, receipt_root: {receipt_root}, took {}.",
172-
humantime::format_duration(start.elapsed())
173-
);
174165
}
175166
Ok(())
176167
}
@@ -208,7 +199,7 @@ impl ValidateCommand {
208199
let (_, config) = read_config(None, Some(chain.clone()))?;
209200
db_root(&chain_path(&config))?
210201
};
211-
let db = generate_test_snapshot::load_db(&db_root_path)?;
202+
let db = generate_test_snapshot::load_db(&db_root_path, Some(&chain)).await?;
212203
let chain_config = Arc::new(ChainConfig::from_chain(&chain));
213204
let genesis_header =
214205
read_genesis_header(None, chain_config.genesis_bytes(&db).await?.as_deref(), &db)
@@ -267,27 +258,14 @@ pub struct ReplayValidateCommand {
267258
impl ReplayValidateCommand {
268259
pub async fn run(self) -> anyhow::Result<()> {
269260
let Self { snapshot, chain, n } = self;
270-
let snap_car = AnyCar::try_from(&snapshot)?;
271-
let ts = snap_car.heaviest_tipset()?;
272-
let epoch = ts.epoch();
273-
let db = Arc::new(ManyCar::new(MemoryDB::default()).with_read_only(snap_car)?);
274-
let chain_config = Arc::new(ChainConfig::from_chain(&chain));
275-
let genesis_header =
276-
read_genesis_header(None, chain_config.genesis_bytes(&db).await?.as_deref(), &db)
261+
let (sm, fts) =
262+
crate::state_manager::utils::state_compute::prepare_state_validate(&chain, &snapshot)
277263
.await?;
278-
let chain_store = Arc::new(ChainStore::new(
279-
db.clone(),
280-
db.clone(),
281-
db.clone(),
282-
chain_config,
283-
genesis_header,
284-
)?);
285-
let state_manager = Arc::new(StateManager::new(chain_store)?);
286-
let fts = load_full_tipset(state_manager.chain_store(), ts.key())?;
264+
let epoch = fts.epoch();
287265
for _ in 0..n.get() {
288266
let fts = fts.clone();
289267
let start = Instant::now();
290-
validate_tipset(&state_manager, fts, None).await?;
268+
validate_tipset(&sm, fts, None).await?;
291269
println!(
292270
"epoch: {epoch}, took {}.",
293271
humantime::format_duration(start.elapsed())
@@ -298,5 +276,7 @@ impl ReplayValidateCommand {
298276
}
299277

300278
fn disable_tipset_cache() {
301-
unsafe { std::env::set_var("FOREST_TIPSET_CACHE_DISABLED", "1") };
279+
unsafe {
280+
std::env::set_var("FOREST_TIPSET_CACHE_DISABLED", "1");
281+
}
302282
}

0 commit comments

Comments
 (0)