Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

use sandbox folder for txhashset validation on state sync #2685

Merged
merged 11 commits into from
Mar 28, 2019
49 changes: 38 additions & 11 deletions chain/src/chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@ use crate::util::secp::pedersen::{Commitment, RangeProof};
use crate::util::{Mutex, RwLock, StopState};
use grin_store::Error::NotFoundErr;
use std::collections::HashMap;
use std::env;
use std::fs::File;
use std::path::PathBuf;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use std::time::{Duration, Instant};
Expand Down Expand Up @@ -842,6 +844,11 @@ impl Chain {
}
}

/// Clean the temporary sandbox folder
pub fn clean_txhashset_sandbox(&self) {
txhashset::clean_txhashset_folder(&env::temp_dir());
}

/// Writes a reading view on a txhashset state that's been provided to us.
/// If we're willing to accept that new state, the data stream will be
/// read as a zip file, unzipped and the resulting state files should be
Expand All @@ -863,17 +870,19 @@ impl Chain {

let header = self.get_block_header(&h)?;

{
let mut txhashset_ref = self.txhashset.write();
// Drop file handles in underlying txhashset
txhashset_ref.release_backend_files();
}

// Rewrite hashset
txhashset::zip_write(self.db_root.clone(), txhashset_data, &header)?;
// Write txhashset to sandbox (in the os temporary directory)
let sandbox_dir = env::temp_dir();
txhashset::clean_txhashset_folder(&sandbox_dir);
txhashset::zip_write(sandbox_dir.clone(), txhashset_data.try_clone()?, &header)?;

let mut txhashset =
txhashset::TxHashSet::open(self.db_root.clone(), self.store.clone(), Some(&header))?;
let mut txhashset = txhashset::TxHashSet::open(
sandbox_dir
.to_str()
.expect("invalid sandbox folder")
.to_owned(),
self.store.clone(),
Some(&header),
)?;

// The txhashset.zip contains the output, rangeproof and kernel MMRs.
// We must rebuild the header MMR ourselves based on the headers in our db.
Expand Down Expand Up @@ -925,9 +934,27 @@ impl Chain {

debug!("txhashset_write: finished committing the batch (head etc.)");

// Replace the chain txhashset with the newly built one.
// Sandbox full validation ok, go to overwrite txhashset on db root
{
let mut txhashset_ref = self.txhashset.write();

// Before overwriting, drop file handlers in underlying txhashset
txhashset_ref.release_backend_files();

// Move sandbox to overwrite
txhashset.release_backend_files();
txhashset::txhashset_replace(sandbox_dir, PathBuf::from(self.db_root.clone()))?;

// Re-open on db root dir
txhashset = txhashset::TxHashSet::open(
self.db_root.clone(),
self.store.clone(),
Some(&header),
)?;

self.rebuild_header_mmr(&Tip::from_header(&header), &mut txhashset)?;

// Replace the chain txhashset with the newly built one.
*txhashset_ref = txhashset;
}

Expand Down
37 changes: 35 additions & 2 deletions chain/src/txhashset/txhashset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1450,17 +1450,50 @@ pub fn zip_read(root_dir: String, header: &BlockHeader, rand: Option<u32>) -> Re
/// Extract the txhashset data from a zip file and writes the content into the
/// txhashset storage dir
pub fn zip_write(
root_dir: String,
root_dir: PathBuf,
txhashset_data: File,
header: &BlockHeader,
) -> Result<(), Error> {
let txhashset_path = Path::new(&root_dir).join(TXHASHSET_SUBDIR);
debug!("zip_write on path: {:?}", root_dir);
let txhashset_path = root_dir.clone().join(TXHASHSET_SUBDIR);
fs::create_dir_all(txhashset_path.clone())?;
zip::decompress(txhashset_data, &txhashset_path, expected_file)
.map_err(|ze| ErrorKind::Other(ze.to_string()))?;
check_and_remove_files(&txhashset_path, header)
}

/// Overwrite txhashset folders in "to" folder with "from" folder
pub fn txhashset_replace(from: PathBuf, to: PathBuf) -> Result<(), Error> {
debug!("txhashset_replace: move from {:?} to {:?}", from, to);

// clean the 'to' folder firstly
clean_txhashset_folder(&to);

// rename the 'from' folder as the 'to' folder
if let Err(e) = fs::rename(
from.clone().join(TXHASHSET_SUBDIR),
to.clone().join(TXHASHSET_SUBDIR),
) {
error!("hashset_replace fail on {}. err: {}", TXHASHSET_SUBDIR, e);
Err(ErrorKind::TxHashSetErr(format!("txhashset replacing fail")).into())
} else {
Ok(())
}
}

/// Clean the txhashset folder
pub fn clean_txhashset_folder(root_dir: &PathBuf) {
let txhashset_path = root_dir.clone().join(TXHASHSET_SUBDIR);
if txhashset_path.exists() {
if let Err(e) = fs::remove_dir_all(txhashset_path.clone()) {
warn!(
"clean_txhashset_folder: fail on {:?}. err: {}",
txhashset_path, e
);
}
}
}

fn expected_file(path: &Path) -> bool {
use lazy_static::lazy_static;
use regex::Regex;
Expand Down
14 changes: 12 additions & 2 deletions chain/tests/test_txhashset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,12 @@ fn test_unexpected_zip() {
assert!(txhashset::zip_read(db_root.clone(), &BlockHeader::default(), Some(rand)).is_ok());
let zip_path = Path::new(&db_root).join(format!("txhashset_snapshot_{}.zip", rand));
let zip_file = File::open(&zip_path).unwrap();
assert!(txhashset::zip_write(db_root.clone(), zip_file, &BlockHeader::default()).is_ok());
assert!(txhashset::zip_write(
PathBuf::from(db_root.clone()),
zip_file,
&BlockHeader::default()
)
.is_ok());
// Remove temp txhashset dir
fs::remove_dir_all(Path::new(&db_root).join(format!("txhashset_zip_{}", rand))).unwrap();
// Then add strange files in the original txhashset folder
Expand All @@ -64,7 +69,12 @@ fn test_unexpected_zip() {
fs::remove_dir_all(Path::new(&db_root).join(format!("txhashset_zip_{}", rand))).unwrap();

let zip_file = File::open(zip_path).unwrap();
assert!(txhashset::zip_write(db_root.clone(), zip_file, &BlockHeader::default()).is_ok());
assert!(txhashset::zip_write(
PathBuf::from(db_root.clone()),
zip_file,
&BlockHeader::default()
)
.is_ok());
// Check that the txhashset dir dos not contains the strange files
let txhashset_path = Path::new(&db_root).join("txhashset");
assert!(txhashset_contains_expected_files(
Expand Down
22 changes: 11 additions & 11 deletions servers/src/common/adapters.rs
Original file line number Diff line number Diff line change
Expand Up @@ -330,17 +330,16 @@ impl p2p::ChainAdapter for NetToChainAdapter {
update_time: old_update_time,
downloaded_size: old_downloaded_size,
..
} => {
self.sync_state
.update_txhashset_download(SyncStatus::TxHashsetDownload {
start_time,
prev_update_time: old_update_time,
update_time: Utc::now(),
prev_downloaded_size: old_downloaded_size,
downloaded_size,
total_size,
})
}
} => self
.sync_state
.update_txhashset_download(SyncStatus::TxHashsetDownload {
start_time,
prev_update_time: old_update_time,
update_time: Utc::now(),
prev_downloaded_size: old_downloaded_size,
downloaded_size,
total_size,
}),
_ => false,
}
}
Expand All @@ -360,6 +359,7 @@ impl p2p::ChainAdapter for NetToChainAdapter {
.chain()
.txhashset_write(h, txhashset_data, self.sync_state.as_ref())
{
self.chain().clean_txhashset_sandbox();
error!("Failed to save txhashset archive: {}", e);
let is_good_data = !e.is_bad_data();
self.sync_state.set_sync_error(types::Error::Chain(e));
Expand Down