grin/chain/tests/test_pibd_copy.rs
Yeastplume 030bd0e1d9
[DNM] PIBD Task / Issue Tracker (#3695)
* [PIBD_IMPL] Introduce PIBD state into sync workflow (#3685)

* experimental addition of pibd download state for testnet only

* fixes to bitmap number of segments calculation + conversion of bitmap accumulator to bitmap

* attempt to call a test message

* add p2p methods for receiving bitmap segment and applying to desegmenter associated with chain

* fixes to state sync

* add pibd receive messages to network, and basic calls to desegmenter from each (#3686)

* [PIBD_IMPL] PIBD Desegmenter State (#3688)

* add functions to desegmenter to report next desired segments, begin to add state to determine which segments have been requested

* add segmentidentifier type to id requested segments uniquely

* make a call on where to keep track of which PIBD segments have been requested

* move segmenttype definition, add functions to manipulate peer segment list

* remove desegmenter state enum

* change chain desegmenter function to provide rwlock

* trace, warning cleanup

* udpate to test compliation

* [PIBD_IMPL] Bitmap accumulator reconstruction + TxHashset set reconstruction (#3689)

* application of received bitmap segments to local accumulator

* add all required elements to send/receive output segment requests and responses

* testing of output sync

* add special cases to pmmr segment request

* [PIBD_IMPL] PMMR Reassembly from Segments (#3690)

* update pibd copy test to use new desgmenter structure

* begin reconstruction of output pmmr

* clean up hash/leaf insertion logic

* push pruned subtree appears to be working, now also calculates left hand hashes correctly

* factor out ordering of segment/hash order array

* refactor for pmmr application code

* test of chain copy appears to be working

* add rangeproof functions to desegmenter

* add kernel functions, attempt refactor

* small test cleanup, reconstruction of live chain working in manual copy test

* [PIBD_IMPL] PIBD tree sync via network and kill/resume functionality (#3691)

* add functions to determing latest verifiable block height for the given pibd state

* attempting to allow for pibd to resume after killing process

* fix to ensure prune list is properly flushed during pibd sync

* removal of unneeded code

* ignore test for now (fix before full merge)

* [PIBD_IMPL] Finalize PIBD download and move state to chain validation (#3692)

* investigations as to why a slight rewind is needed on startup during PIBD

* move validation code into desegmenter validation thread (for now)

* ensure genesis entries in pmmrs are removed if they're removed in the first segment

* validation all working except for verifying kernel sums

* remove unneeded pmmr rollbacks on resume now root cause was found

* updates to remove unpruned leaves from leaf set when rebuilding pmmr

* remove + 1 to segment traversal iter length

* [PIBD_IMPL] PIBD Stats + Retry on validation errors (#3694)

* start to add stats and reset chain state after errors detected

* add functions to reset prune list when resetting chain pibd state

* debug statement

* remove test function

* [PIBD_IMPL] Update number of simultaneous peer requests for segments (#3696)

* cleanup of segment request list

* allow for more simultaneous requests during state sync

* up number of simultaneous peer requests for segments

* [PIBD_IMPL] Thread simplification + More TUI Updates + Stop State Propagation (#3698)

* change pibd stat display to show progress as a percentage of downloaded leaves

* attempt some inline rp validation

* propagate shutdown state through kernel validation

* change validation loop timing

* simplify validator threading

* add more detailed tracking of kernel history validation to tui, allow stop state during

* adding more stop state + tui progress indication

* remove progressive validate

* test fix

* revert to previous method of applying segments (#3699)

* fix for deadlock issue (#3700)

* update Cargo.lock for next release

* [PIBD_IMPL] Catch-Up functionality + Fixes based on testing (#3702)

* ensure desegmenter attempts to apply correct block after a resumte

* ensure txhashset's committed implementation takes into account output bitmap for summing purposes

* remove check to de-apply outputs during segment application

* return removal of spent outputs during pibd

* remove unneeded status

* remove uneeded change to rewind function

* documentation updates + todo fixes (#3703)

* add pibd abort timeout case (#3704)

* [PIBD_IMPL] BitmapAccumulator Serialization Fix (#3705)

* fix for writing / calculating incorrect length for negative indices

* update capabilities with new version of PIBD hist

* remove incorrect comment

* fix capabilities flag, trace output

* test fix

* Merge DNSSeed scope changes into pibd impl branch (#3708)

* update Cargo.lock for next release

* visibility scope tweaks to aid seed test utilities (#3707)

* move all PIBD-related constants into pibd_params modules (#3711)

* remove potential double read lock during compaction
2022-10-18 09:23:29 +01:00

350 lines
10 KiB
Rust

// Copyright 2021 The Grin Developers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use grin_chain as chain;
use grin_core as core;
use grin_util as util;
#[macro_use]
extern crate log;
use std::path::Path;
use std::sync::Arc;
use std::{fs, io};
use crate::chain::txhashset::BitmapChunk;
use crate::chain::types::{NoopAdapter, Options};
use crate::core::core::{
hash::{Hash, Hashed},
pmmr::segment::{Segment, SegmentIdentifier, SegmentType},
Block, OutputIdentifier, TxKernel,
};
use crate::core::{genesis, global, pow};
use crate::util::secp::pedersen::RangeProof;
use self::chain_test_helper::clean_output_dir;
mod chain_test_helper;
fn copy_dir_all(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> io::Result<()> {
fs::create_dir_all(&dst)?;
for entry in fs::read_dir(src)? {
let entry = entry?;
let ty = entry.file_type()?;
if ty.is_dir() {
copy_dir_all(entry.path(), dst.as_ref().join(entry.file_name()))?;
} else {
fs::copy(entry.path(), dst.as_ref().join(entry.file_name()))?;
}
}
Ok(())
}
// Canned segmenter responder, which will simulate feeding back segments as requested
// by the desegmenter
struct SegmenterResponder {
chain: Arc<chain::Chain>,
}
impl SegmenterResponder {
pub fn new(chain_src_dir: &str, genesis: Block) -> Self {
let dummy_adapter = Arc::new(NoopAdapter {});
debug!(
"Reading SegmenterResponder chain, genesis block: {}",
genesis.hash()
);
// The original chain we're reading from
let res = SegmenterResponder {
chain: Arc::new(
chain::Chain::init(
chain_src_dir.into(),
dummy_adapter.clone(),
genesis,
pow::verify_size,
false,
)
.unwrap(),
),
};
let sh = res.chain.get_header_by_height(0).unwrap();
debug!("Source Genesis - {}", sh.hash());
res
}
pub fn chain(&self) -> Arc<chain::Chain> {
self.chain.clone()
}
pub fn get_bitmap_segment(&self, seg_id: SegmentIdentifier) -> (Segment<BitmapChunk>, Hash) {
let segmenter = self.chain.segmenter().unwrap();
segmenter.bitmap_segment(seg_id).unwrap()
}
pub fn get_output_segment(
&self,
seg_id: SegmentIdentifier,
) -> (Segment<OutputIdentifier>, Hash) {
let segmenter = self.chain.segmenter().unwrap();
segmenter.output_segment(seg_id).unwrap()
}
pub fn get_rangeproof_segment(&self, seg_id: SegmentIdentifier) -> Segment<RangeProof> {
let segmenter = self.chain.segmenter().unwrap();
segmenter.rangeproof_segment(seg_id).unwrap()
}
pub fn get_kernel_segment(&self, seg_id: SegmentIdentifier) -> Segment<TxKernel> {
let segmenter = self.chain.segmenter().unwrap();
segmenter.kernel_segment(seg_id).unwrap()
}
}
// Canned segmenter 'peer', building up its local chain from requested PIBD segments
struct DesegmenterRequestor {
chain: Arc<chain::Chain>,
responder: Arc<SegmenterResponder>,
}
impl DesegmenterRequestor {
pub fn new(chain_src_dir: &str, genesis: Block, responder: Arc<SegmenterResponder>) -> Self {
let dummy_adapter = Arc::new(NoopAdapter {});
debug!(
"Reading DesegmenterRequestor chain, genesis block: {}",
genesis.hash()
);
// The original chain we're reading from
let res = DesegmenterRequestor {
chain: Arc::new(
chain::Chain::init(
chain_src_dir.into(),
dummy_adapter.clone(),
genesis,
pow::verify_size,
false,
)
.unwrap(),
),
responder,
};
let sh = res.chain.get_header_by_height(0).unwrap();
debug!("Dest Genesis - {}", sh.hash());
res
}
/// Copy headers, hopefully bringing the requestor to a state where PIBD is the next step
pub fn copy_headers_from_responder(&mut self) {
let src_chain = self.responder.chain();
let tip = src_chain.header_head().unwrap();
let dest_sync_head = self.chain.header_head().unwrap();
let copy_chunk_size = 1000;
let mut copied_header_index = 1;
let mut src_headers = vec![];
while copied_header_index <= tip.height {
let h = src_chain.get_header_by_height(copied_header_index).unwrap();
src_headers.push(h);
copied_header_index += 1;
if copied_header_index % copy_chunk_size == 0 {
debug!(
"Copying headers to {} of {}",
copied_header_index, tip.height
);
self.chain
.sync_block_headers(&src_headers, dest_sync_head, Options::SKIP_POW)
.unwrap();
src_headers = vec![];
}
}
if !src_headers.is_empty() {
self.chain
.sync_block_headers(&src_headers, dest_sync_head, Options::NONE)
.unwrap();
}
}
// Emulate `continue_pibd` function, which would be called from state sync
// return whether is complete
pub fn continue_pibd(&mut self) -> bool {
let archive_header = self.chain.txhashset_archive_header_header_only().unwrap();
let desegmenter = self.chain.desegmenter(&archive_header).unwrap();
// Apply segments... TODO: figure out how this should be called, might
// need to be a separate thread.
if let Some(mut de) = desegmenter.try_write() {
if let Some(d) = de.as_mut() {
d.apply_next_segments().unwrap();
}
}
let mut next_segment_ids = vec![];
let mut is_complete = false;
if let Some(d) = desegmenter.write().as_mut() {
// Figure out the next segments we need
// (12 is divisible by 3, to try and evenly spread the requests among the 3
// main pmmrs. Bitmaps segments will always be requested first)
next_segment_ids = d.next_desired_segments(12);
is_complete = d.is_complete()
}
debug!("Next segment IDS: {:?}", next_segment_ids);
// For each segment, pick a desirable peer and send message
for seg_id in next_segment_ids.iter() {
// Perform request and response
match seg_id.segment_type {
SegmentType::Bitmap => {
let (seg, output_root) =
self.responder.get_bitmap_segment(seg_id.identifier.clone());
if let Some(d) = desegmenter.write().as_mut() {
d.add_bitmap_segment(seg, output_root).unwrap();
}
}
SegmentType::Output => {
let (seg, bitmap_root) =
self.responder.get_output_segment(seg_id.identifier.clone());
if let Some(d) = desegmenter.write().as_mut() {
d.add_output_segment(seg, Some(bitmap_root)).unwrap();
}
}
SegmentType::RangeProof => {
let seg = self
.responder
.get_rangeproof_segment(seg_id.identifier.clone());
if let Some(d) = desegmenter.write().as_mut() {
d.add_rangeproof_segment(seg).unwrap();
}
}
SegmentType::Kernel => {
let seg = self.responder.get_kernel_segment(seg_id.identifier.clone());
if let Some(d) = desegmenter.write().as_mut() {
d.add_kernel_segment(seg).unwrap();
}
}
};
}
is_complete
}
pub fn check_roots(&self) {
let roots = self.chain.txhashset().read().roots();
let archive_header = self.chain.txhashset_archive_header_header_only().unwrap();
debug!("Archive Header is {:?}", archive_header);
debug!("TXHashset output root is {:?}", roots);
debug!(
"TXHashset merged output root is {:?}",
roots.output_roots.root(&archive_header)
);
assert_eq!(archive_header.range_proof_root, roots.rproof_root);
assert_eq!(archive_header.kernel_root, roots.kernel_root);
assert_eq!(
archive_header.output_root,
roots.output_roots.root(&archive_header)
);
}
}
fn test_pibd_copy_impl(
is_test_chain: bool,
src_root_dir: &str,
dest_root_dir: &str,
dest_template_dir: Option<&str>,
) {
global::set_local_chain_type(global::ChainTypes::Testnet);
let mut genesis = genesis::genesis_test();
if is_test_chain {
global::set_local_chain_type(global::ChainTypes::AutomatedTesting);
genesis = pow::mine_genesis_block().unwrap();
}
// Copy a starting point over for the destination, e.g. a copy of chain
// with all headers pre-applied
if let Some(td) = dest_template_dir {
debug!(
"Copying template dir for destination from {} to {}",
td, dest_root_dir
);
copy_dir_all(td, dest_root_dir).unwrap();
}
let src_responder = Arc::new(SegmenterResponder::new(src_root_dir, genesis.clone()));
let mut dest_requestor =
DesegmenterRequestor::new(dest_root_dir, genesis.clone(), src_responder);
// No template provided so copy headers from source
if dest_template_dir.is_none() {
dest_requestor.copy_headers_from_responder();
if !is_test_chain {
return;
}
}
// Perform until desegmenter reports it's done
while !dest_requestor.continue_pibd() {}
dest_requestor.check_roots();
}
#[test]
#[ignore]
fn test_pibd_copy_sample() {
util::init_test_logger();
// Note there is now a 'test' in grin_wallet_controller/build_chain
// that can be manually tweaked to create a
// small test chain with actual transaction data
// Test on uncompacted and non-compacted chains
let src_root_dir = format!("./tests/test_data/chain_raw");
let dest_root_dir = format!("./tests/test_output/.segment_copy");
clean_output_dir(&dest_root_dir);
test_pibd_copy_impl(true, &src_root_dir, &dest_root_dir, None);
let src_root_dir = format!("./tests/test_data/chain_compacted");
clean_output_dir(&dest_root_dir);
test_pibd_copy_impl(true, &src_root_dir, &dest_root_dir, None);
clean_output_dir(&dest_root_dir);
}
#[test]
#[ignore]
// Note this test is intended to be run manually, as testing the copy of an
// entire live chain is beyond the capability of current CI
// As above, but run on a real instance of a chain pointed where you like
fn test_pibd_copy_real() {
util::init_test_logger();
// If set, just copy headers from source to target template dir and exit
// Used to set up a chain state simulating the start of PIBD to continue manual testing
let copy_headers_to_template = false;
// if testing against a real chain, insert location here
let src_root_dir = format!("/home/yeastplume/Projects/grin-project/servers/floo-1/chain_data");
let dest_template_dir = format!(
"/home/yeastplume/Projects/grin-project/servers/floo-pibd-1/chain_data_headers_only"
);
let dest_root_dir =
format!("/home/yeastplume/Projects/grin-project/servers/floo-pibd-1/chain_data_test_copy");
if copy_headers_to_template {
clean_output_dir(&dest_template_dir);
test_pibd_copy_impl(false, &src_root_dir, &dest_template_dir, None);
} else {
clean_output_dir(&dest_root_dir);
test_pibd_copy_impl(
false,
&src_root_dir,
&dest_root_dir,
Some(&dest_template_dir),
);
}
//clean_output_dir(&dest_root_dir);
}