Skip to content

Commit 809a1e6

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 abefba3 commit 809a1e6

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
@@ -1138,7 +1138,7 @@ pub(crate) mod tests {
11381138

11391139
#[cfg(test)]
11401140
impl Block {
1141-
fn with_difficulty(mut self, difficulty: Difficulty) -> Self {
1141+
pub(crate) fn with_difficulty(mut self, difficulty: Difficulty) -> Self {
11421142
self.kernel.header.difficulty = difficulty;
11431143
self.unset_digest();
11441144
self

src/models/state/mod.rs

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3981,7 +3981,13 @@ mod tests {
39813981
}
39823982

39833983
mod bootstrap_from_raw_block_files {
3984+
use std::fs::File;
3985+
use std::io::Write;
3986+
39843987
use super::*;
3988+
use crate::tests::shared::mock_genesis_global_state_with_block;
3989+
use crate::tests::shared::test_helper_data_dir;
3990+
use crate::tests::shared::try_fetch_file_from_server;
39853991

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

40854160
// 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
@@ -107,6 +107,7 @@ use crate::models::state::wallet::address::generation_address::GenerationReceivi
107107
use crate::models::state::wallet::expected_utxo::ExpectedUtxo;
108108
use crate::models::state::wallet::expected_utxo::UtxoNotifier;
109109
use crate::models::state::wallet::transaction_output::TxOutputList;
110+
use crate::models::state::wallet::wallet_configuration::WalletConfiguration;
110111
use crate::models::state::wallet::wallet_entropy::WalletEntropy;
111112
use crate::models::state::wallet::wallet_state::WalletState;
112113
use crate::models::state::GlobalState;
@@ -189,17 +190,15 @@ pub(crate) fn get_dummy_peer_connection_data_genesis(
189190
(handshake, socket_address)
190191
}
191192

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

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

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

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

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

0 commit comments

Comments
 (0)