Skip to content

Commit 568b952

Browse files
committed
test: Recover state from real main net blocks
Add a test of `GlobalState::bootstrap_from_directory` using real data from main net. The test uses the `blk0.dat` file from a node that has been online since genesis, thus containing reorganizations that some previously untested branches of `GlobalState::bootstrap_from_directory`. This test also serves as a check that the first 113 main net blocks are still considered valid by future versions of the software.
1 parent 65193f9 commit 568b952

File tree

4 files changed

+101
-9
lines changed

4 files changed

+101
-9
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@ target/
77
/makefile-target
88
/makefile-target-opt-level3
99

10-
# git shouldn't see the proof files. Bc big.
10+
# git shouldn't see the proof files, or block files. Bc big.
1111
/test_data/*.proof
12+
/test_data/blk*.dat
1213

1314
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
1415
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html

src/models/blockchain/block/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1156,7 +1156,7 @@ pub(crate) mod tests {
11561156

11571157
#[cfg(test)]
11581158
impl Block {
1159-
fn with_difficulty(mut self, difficulty: Difficulty) -> Self {
1159+
pub(crate) fn with_difficulty(mut self, difficulty: Difficulty) -> Self {
11601160
self.kernel.header.difficulty = difficulty;
11611161
self.unset_digest();
11621162
self

src/models/state/mod.rs

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3993,7 +3993,13 @@ mod tests {
39933993
}
39943994

39953995
mod bootstrap_from_raw_block_files {
3996+
use std::fs::File;
3997+
use std::io::Write;
3998+
39963999
use super::*;
4000+
use crate::tests::shared::mock_genesis_global_state_with_block;
4001+
use crate::tests::shared::test_helper_data_dir;
4002+
use crate::tests::shared::try_fetch_file_from_server;
39974003

39984004
async fn state_with_three_big_mocked_blocks(network: Network) -> GlobalStateLock {
39994005
// Ensure more than one file is used to store blocks.
@@ -4092,6 +4098,75 @@ mod tests {
40924098
"Restored wallet state must agree with original state"
40934099
);
40944100
}
4101+
4102+
#[traced_test]
4103+
#[apply(shared_tokio_runtime)]
4104+
async fn can_restore_from_real_mainnet_data_with_reorganizations() {
4105+
let expected_blk_files = ["blk0.dat"];
4106+
let network = Network::Main;
4107+
4108+
// We need to override difficulty since it's different when the
4109+
// test flag is set. We need the real main net block.
4110+
let mainnets_real_genesis_block =
4111+
Block::genesis(network).with_difficulty(network.genesis_difficulty());
4112+
4113+
// Use at least four MPs per UTXO, otherwise they get unsynced.
4114+
let cli = cli_args::Args {
4115+
network,
4116+
number_of_mps_per_utxo: 4,
4117+
..Default::default()
4118+
};
4119+
let peer_count = 0;
4120+
4121+
let mut state = mock_genesis_global_state_with_block(
4122+
peer_count,
4123+
WalletEntropy::devnet_wallet(),
4124+
cli,
4125+
mainnets_real_genesis_block,
4126+
)
4127+
.await;
4128+
let mut state = state.lock_guard_mut().await;
4129+
4130+
// Are the required blk files present on disk? If not, fetch them
4131+
// from a server.
4132+
let test_data_dir = test_helper_data_dir();
4133+
for blk_file_name in expected_blk_files {
4134+
let mut path = test_data_dir.clone();
4135+
path.push(blk_file_name);
4136+
if File::open(&path).is_err() {
4137+
// Try fetching file from server and write it to disk.
4138+
let (file, _server) = try_fetch_file_from_server(blk_file_name.to_owned())
4139+
.unwrap_or_else(|| {
4140+
panic!("File {blk_file_name} must be available from a server")
4141+
});
4142+
let mut f = File::create_new(&path).unwrap();
4143+
f.write_all(&file).unwrap();
4144+
}
4145+
}
4146+
4147+
let validate_blocks = true;
4148+
state
4149+
.bootstrap_from_directory(&test_data_dir, validate_blocks)
4150+
.await
4151+
.unwrap();
4152+
let restored_block_height = state.chain.light_state().header().height;
4153+
assert_eq!(
4154+
BlockHeight::new(bfe!(113)),
4155+
restored_block_height,
4156+
"Expected block height not reached in state-recovery"
4157+
);
4158+
4159+
let wallet_status = state.get_wallet_status_for_tip().await;
4160+
let balance = state.wallet_state.confirmed_available_balance(
4161+
&wallet_status,
4162+
network.launch_date() + Timestamp::months(7),
4163+
);
4164+
assert_eq!(
4165+
NativeCurrencyAmount::coins(20),
4166+
balance,
4167+
"Expected balance must be available after state-recovery"
4168+
);
4169+
}
40954170
}
40964171

40974172
// note: removed test have_to_specify_change_policy()

src/tests/shared.rs

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ use crate::models::state::wallet::address::generation_address::GenerationReceivi
106106
use crate::models::state::wallet::expected_utxo::ExpectedUtxo;
107107
use crate::models::state::wallet::expected_utxo::UtxoNotifier;
108108
use crate::models::state::wallet::transaction_output::TxOutputList;
109+
use crate::models::state::wallet::wallet_configuration::WalletConfiguration;
109110
use crate::models::state::wallet::wallet_entropy::WalletEntropy;
110111
use crate::models::state::wallet::wallet_state::WalletState;
111112
use crate::models::state::GlobalState;
@@ -188,17 +189,15 @@ pub(crate) fn get_dummy_peer_connection_data_genesis(
188189
(handshake, socket_address)
189190
}
190191

191-
/// Get a global state object for unit test purposes. This global state
192-
/// populated with state from the genesis block, e.g. in the archival mutator
193-
/// set and the wallet.
194-
///
192+
/// Get a global state object for unit test purposes. This global state is
193+
/// populated with state from a caller-defined genesis block.
195194
/// All contained peers represent outgoing connections.
196-
pub(crate) async fn mock_genesis_global_state(
195+
pub(crate) async fn mock_genesis_global_state_with_block(
197196
peer_count: u8,
198197
wallet: WalletEntropy,
199198
cli: cli_args::Args,
199+
genesis_block: Block,
200200
) -> GlobalStateLock {
201-
let genesis_block = Block::genesis(cli.network);
202201
let data_dir: DataDirectory = unit_test_data_directory(cli.network).unwrap();
203202
let archival_state = ArchivalState::new(data_dir.clone(), genesis_block.clone()).await;
204203

@@ -228,7 +227,10 @@ pub(crate) async fn mock_genesis_global_state(
228227
genesis_block.hash(),
229228
);
230229

231-
let wallet_state = WalletState::new_from_wallet_entropy(&data_dir, wallet, &cli).await;
230+
let configuration = WalletConfiguration::new(&data_dir).absorb_options(&cli);
231+
let wallet_state = WalletState::try_new(configuration, wallet, &genesis_block)
232+
.await
233+
.unwrap();
232234

233235
// dummy channel
234236
let (rpc_to_main_tx, mut rpc_to_main_rx) = tokio::sync::mpsc::channel::<RPCServerToMain>(5);
@@ -243,6 +245,20 @@ pub(crate) async fn mock_genesis_global_state(
243245
GlobalStateLock::from_global_state(global_state, rpc_to_main_tx)
244246
}
245247

248+
/// Get a global state object for unit test purposes. This global state is
249+
/// populated with state from the genesis block, e.g. in the archival mutator
250+
/// set and the wallet.
251+
///
252+
/// All contained peers represent outgoing connections.
253+
pub(crate) async fn mock_genesis_global_state(
254+
peer_count: u8,
255+
wallet: WalletEntropy,
256+
cli: cli_args::Args,
257+
) -> GlobalStateLock {
258+
let genesis_block = Block::genesis(cli.network);
259+
mock_genesis_global_state_with_block(peer_count, wallet, cli, genesis_block).await
260+
}
261+
246262
/// A state with a premine UTXO and self-mined blocks. Both composing and
247263
/// guessing was done by the returned entity. Tip has height of
248264
/// `num_blocks_mined`.

0 commit comments

Comments
 (0)