From 42680456b683afd730f8c9ca034cf5d2306e9e8f Mon Sep 17 00:00:00 2001 From: Peter Conrad Date: Wed, 12 Jul 2017 21:13:26 +0200 Subject: [PATCH 01/13] Improved resilience of block database against corruption --- libraries/chain/block_database.cpp | 78 ++++++++----------- .../include/graphene/chain/block_database.hpp | 3 + 2 files changed, 35 insertions(+), 46 deletions(-) diff --git a/libraries/chain/block_database.cpp b/libraries/chain/block_database.cpp index 214459f0d..9c3516248 100644 --- a/libraries/chain/block_database.cpp +++ b/libraries/chain/block_database.cpp @@ -206,34 +206,41 @@ optional block_database::fetch_by_number( uint32_t block_num )cons return optional(); } -optional block_database::last()const -{ +optional block_database::last_index_entry()const { try { index_entry e; + _block_num_to_pos.seekg( 0, _block_num_to_pos.end ); + std::streampos pos = _block_num_to_pos.tellg(); + if( pos < sizeof(index_entry) ) + return optional(); - if( _block_num_to_pos.tellp() < sizeof(index_entry) ) - return optional(); + if( pos % sizeof(index_entry) != 0 ) + pos -= pos % sizeof(index_entry); - _block_num_to_pos.seekg( -sizeof(index_entry), _block_num_to_pos.end ); - _block_num_to_pos.read( (char*)&e, sizeof(e) ); - uint64_t pos = _block_num_to_pos.tellg(); while( e.block_size == 0 && pos > 0 ) { pos -= sizeof(index_entry); _block_num_to_pos.seekg( pos ); _block_num_to_pos.read( (char*)&e, sizeof(e) ); - } - if( e.block_size == 0 ) - return optional(); - - vector data( e.block_size ); - _blocks.seekg( e.block_pos ); - _blocks.read( data.data(), e.block_size ); - auto result = fc::raw::unpack(data); - return result; + if( e.block_size > 0 ) + try + { + vector data( e.block_size ); + _blocks.seekg( e.block_pos ); + _blocks.read( data.data(), e.block_size ); + auto result = fc::raw::unpack(data); + return e; + } + catch (const fc::exception&) + { + } + catch (const std::exception&) + { + } + } } catch (const fc::exception&) { @@ -241,42 +248,21 @@ optional block_database::last()const catch (const std::exception&) { } + return optional(); +} + +optional block_database::last()const +{ + optional entry = last_index_entry(); + if( entry.valid() ) return fetch_by_number( block_header::num_from_id(entry->block_id) ); return optional(); } optional block_database::last_id()const { - try - { - index_entry e; - _block_num_to_pos.seekg( 0, _block_num_to_pos.end ); - - if( _block_num_to_pos.tellp() < sizeof(index_entry) ) - return optional(); - - _block_num_to_pos.seekg( -sizeof(index_entry), _block_num_to_pos.end ); - _block_num_to_pos.read( (char*)&e, sizeof(e) ); - uint64_t pos = _block_num_to_pos.tellg(); - while( e.block_size == 0 && pos > 0 ) - { - pos -= sizeof(index_entry); - _block_num_to_pos.seekg( pos ); - _block_num_to_pos.read( (char*)&e, sizeof(e) ); - } - - if( e.block_size == 0 ) - return optional(); - - return e.block_id; - } - catch (const fc::exception&) - { - } - catch (const std::exception&) - { - } + optional entry = last_index_entry(); + if( entry.valid() ) return entry->block_id; return optional(); } - } } diff --git a/libraries/chain/include/graphene/chain/block_database.hpp b/libraries/chain/include/graphene/chain/block_database.hpp index d1f613c15..2c7ff8128 100644 --- a/libraries/chain/include/graphene/chain/block_database.hpp +++ b/libraries/chain/include/graphene/chain/block_database.hpp @@ -26,6 +26,8 @@ #include namespace graphene { namespace chain { + class index_entry; + class block_database { public: @@ -44,6 +46,7 @@ namespace graphene { namespace chain { optional last()const; optional last_id()const; private: + optional last_index_entry()const; mutable std::fstream _blocks; mutable std::fstream _block_num_to_pos; }; From 17417037c648fd01529b3e894ff3377833cc3b1d Mon Sep 17 00:00:00 2001 From: Peter Conrad Date: Wed, 12 Jul 2017 22:03:57 +0200 Subject: [PATCH 02/13] Moved reindex logic into database / chain_database, make use of additional blocks in block_database Fixed tests wrt db.open --- libraries/app/application.cpp | 65 +++-------------- libraries/chain/db_management.cpp | 69 +++++++++++-------- .../chain/include/graphene/chain/database.hpp | 6 +- libraries/db/object_database.cpp | 7 ++ tests/benchmarks/genesis_allocation.cpp | 6 +- tests/common/database_fixture.cpp | 2 +- tests/generate_empty_blocks/main.cpp | 2 +- tests/tests/block_tests.cpp | 26 +++---- tests/tests/operation_tests2.cpp | 2 +- 9 files changed, 80 insertions(+), 105 deletions(-) diff --git a/libraries/app/application.cpp b/libraries/app/application.cpp index 5e4f9c7e4..1a0b8b656 100644 --- a/libraries/app/application.cpp +++ b/libraries/app/application.cpp @@ -300,7 +300,6 @@ namespace detail { ~application_impl() { - fc::remove_all(_data_dir / "blockchain/dblock"); } void set_dbg_init_key( genesis_state_type& genesis, const std::string& init_key ) @@ -314,8 +313,7 @@ namespace detail { void startup() { try { - bool clean = !fc::exists(_data_dir / "blockchain/dblock"); - fc::create_directories(_data_dir / "blockchain/dblock"); + fc::create_directories(_data_dir / "blockchain"); auto initial_state = [&] { ilog("Initializing database..."); @@ -381,64 +379,17 @@ namespace detail { bool replay = false; std::string replay_reason = "reason not provided"; - // never replay if data dir is empty - if( fc::exists( _data_dir ) && fc::directory_iterator( _data_dir ) != fc::directory_iterator() ) - { - if( _options->count("replay-blockchain") ) - { - replay = true; - replay_reason = "replay-blockchain argument specified"; - } - else if( !clean ) - { - replay = true; - replay_reason = "unclean shutdown detected"; - } - else if( !fc::exists( _data_dir / "db_version" ) ) - { - replay = true; - replay_reason = "db_version file not found"; - } - else - { - std::string version_string; - fc::read_file_contents( _data_dir / "db_version", version_string ); + if( _options->count("replay-blockchain") ) + _chain_db->wipe( _data_dir / "blockchain", false ); - if( version_string != GRAPHENE_CURRENT_DB_VERSION ) - { - replay = true; - replay_reason = "db_version file content mismatch"; - } - } - } - - if( !replay ) + try { - try - { - _chain_db->open( _data_dir / "blockchain", initial_state ); - } - catch( const fc::exception& e ) - { - ilog( "Caught exception ${e} in open()", ("e", e.to_detail_string()) ); - - replay = true; - replay_reason = "exception in open()"; - } + _chain_db->open( _data_dir / "blockchain", initial_state, GRAPHENE_CURRENT_DB_VERSION ); } - - if( replay ) + catch( const fc::exception& e ) { - ilog( "Replaying blockchain due to: ${reason}", ("reason", replay_reason) ); - - fc::remove_all( _data_dir / "db_version" ); - _chain_db->reindex( _data_dir / "blockchain", initial_state() ); - - const auto mode = std::ios::out | std::ios::binary | std::ios::trunc; - std::ofstream db_version( (_data_dir / "db_version").generic_string().c_str(), mode ); - std::string version_string = GRAPHENE_CURRENT_DB_VERSION; - db_version.write( version_string.c_str(), version_string.size() ); - db_version.close(); + elog( "Caught exception ${e} in open(), you might want to force a replay", ("e", e.to_detail_string()) ); + throw; } if( _options->count("force-validate") ) diff --git a/libraries/chain/db_management.cpp b/libraries/chain/db_management.cpp index 68f6fad1f..ca4004e69 100644 --- a/libraries/chain/db_management.cpp +++ b/libraries/chain/db_management.cpp @@ -47,33 +47,39 @@ database::~database() clear_pending(); } -void database::reindex(fc::path data_dir, const genesis_state_type& initial_allocation) +void database::reindex( fc::path data_dir ) { try { - ilog( "reindexing blockchain" ); - wipe(data_dir, false); - open(data_dir, [&initial_allocation]{return initial_allocation;}); - - auto start = fc::time_point::now(); auto last_block = _block_id_to_block.last(); if( !last_block ) { elog( "!no last block" ); edump((last_block)); return; } + if( last_block->block_num() <= head_block_num()) return; + ilog( "reindexing blockchain" ); + auto start = fc::time_point::now(); const auto last_block_num = last_block->block_num(); + uint32_t flush_point = last_block_num - 10000; + uint32_t undo_point = last_block_num - 50; ilog( "Replaying blocks..." ); - // Right now, we leave undo_db enabled when replaying when the bookie plugin is + // Right now, we leave undo_db enabled when replaying when the bookie plugin is // enabled. It depends on new/changed/removed object notifications, and those are // only fired when the undo_db is enabled if (!_slow_replays) _undo_db.disable(); - for( uint32_t i = 1; i <= last_block_num; ++i ) + for( uint32_t i = head_block_num() + 1; i <= last_block_num; ++i ) { - if( i == 1 || - i % 10000 == 0 ) - std::cerr << " " << double(i*100)/last_block_num << "% "<< i << " of " < block = _block_id_to_block.fetch_by_number(i); if( !block.valid() ) { @@ -127,10 +133,29 @@ void database::wipe(const fc::path& data_dir, bool include_blocks) void database::open( const fc::path& data_dir, - std::function genesis_loader) + std::function genesis_loader, + const std::string& db_version) { try { + bool wipe_object_db = false; + if( !fc::exists( data_dir / "db_version" ) ) + wipe_object_db = true; + else + { + std::string version_string; + fc::read_file_contents( data_dir / "db_version", version_string ); + wipe_object_db = ( version_string != db_version ); + } + if( wipe_object_db ) { + ilog("Wiping object_database due to missing or wrong version"); + object_database::wipe( data_dir ); + std::ofstream version_file( (data_dir / "db_version").generic_string().c_str(), + std::ios::out | std::ios::binary | std::ios::trunc ); + version_file.write( db_version.c_str(), db_version.size() ); + version_file.close(); + } + object_database::open(data_dir); _block_id_to_block.open(data_dir / "database" / "block_num_to_block"); @@ -138,15 +163,13 @@ void database::open( if( !find(global_property_id_type()) ) init_genesis(genesis_loader()); - fc::optional last_block = _block_id_to_block.last(); + fc::optional last_block = _block_id_to_block.last_id(); if( last_block.valid() ) { - _fork_db.start_block( *last_block ); - if( last_block->id() != head_block_id() ) - { - FC_ASSERT( head_block_num() == 0, "last block ID does not match current chain state", - ("last_block->id", last_block->id())("head_block_num",head_block_num()) ); - } + FC_ASSERT( *last_block >= head_block_id(), + "last block ID does not match current chain state", + ("last_block->id", last_block)("head_block_id",head_block_num()) ); + reindex( data_dir ); } } FC_CAPTURE_LOG_AND_RETHROW( (data_dir) ) @@ -167,17 +190,9 @@ void database::close(bool rewind) while( head_block_num() > cutoff ) { - // elog("pop"); block_id_type popped_block_id = head_block_id(); pop_block(); _fork_db.remove(popped_block_id); // doesn't throw on missing - try - { - _block_id_to_block.remove(popped_block_id); - } - catch (const fc::key_not_found_exception&) - { - } } } catch ( const fc::exception& e ) diff --git a/libraries/chain/include/graphene/chain/database.hpp b/libraries/chain/include/graphene/chain/database.hpp index 0c6dcb0f1..77699ee4b 100644 --- a/libraries/chain/include/graphene/chain/database.hpp +++ b/libraries/chain/include/graphene/chain/database.hpp @@ -91,10 +91,12 @@ namespace graphene { namespace chain { * * @param data_dir Path to open or create database in * @param genesis_loader A callable object which returns the genesis state to initialize new databases on + * @param db_version a version string that changes when the internal database format and/or logic is modified */ void open( const fc::path& data_dir, - std::function genesis_loader ); + std::function genesis_loader, + const std::string& db_version ); /** * @brief Rebuild object graph from block history and open detabase @@ -102,7 +104,7 @@ namespace graphene { namespace chain { * This method may be called after or instead of @ref database::open, and will rebuild the object graph by * replaying blockchain history. When this method exits successfully, the database will be open. */ - void reindex(fc::path data_dir, const genesis_state_type& initial_allocation = genesis_state_type()); + void reindex(fc::path data_dir); /** * @brief wipe Delete database from disk, and potentially the raw chain as well. diff --git a/libraries/db/object_database.cpp b/libraries/db/object_database.cpp index 29d83ae72..6e1fdea20 100644 --- a/libraries/db/object_database.cpp +++ b/libraries/db/object_database.cpp @@ -71,6 +71,7 @@ index& object_database::get_mutable_index(uint8_t space_id, uint8_t type_id) void object_database::flush() { // ilog("Save object_database in ${d}", ("d", _data_dir)); + fc::create_directories( _data_dir / "object_database" / "lock" ); for( uint32_t space = 0; space < _index.size(); ++space ) { fc::create_directories( _data_dir / "object_database" / fc::to_string(space) ); @@ -79,6 +80,7 @@ void object_database::flush() if( _index[space][type] ) _index[space][type]->save( _data_dir / "object_database" / fc::to_string(space)/fc::to_string(type) ); } + fc::remove_all( _data_dir / "object_database" / "lock" ); } void object_database::wipe(const fc::path& data_dir) @@ -91,6 +93,11 @@ void object_database::wipe(const fc::path& data_dir) void object_database::open(const fc::path& data_dir) { try { + if( fc::exists( _data_dir / "object_database" / "lock" ) ) + { + wlog("Ignoring locked object_database"); + return; + } ilog("Opening object database from ${d} ...", ("d", data_dir)); _data_dir = data_dir; for( uint32_t space = 0; space < _index.size(); ++space ) diff --git a/tests/benchmarks/genesis_allocation.cpp b/tests/benchmarks/genesis_allocation.cpp index 61a3b1b8a..a17a16fa8 100644 --- a/tests/benchmarks/genesis_allocation.cpp +++ b/tests/benchmarks/genesis_allocation.cpp @@ -68,7 +68,7 @@ BOOST_AUTO_TEST_CASE( genesis_and_persistence_bench ) { database db; - db.open(data_dir.path(), [&]{return genesis_state;}); + db.open(data_dir.path(), [&]{return genesis_state;}, "test"); for( int i = 11; i < account_count + 11; ++i) BOOST_CHECK(db.get_balance(account_id_type(i), asset_id_type()).amount == GRAPHENE_MAX_SHARE_SUPPLY / account_count); @@ -81,7 +81,7 @@ BOOST_AUTO_TEST_CASE( genesis_and_persistence_bench ) database db; fc::time_point start_time = fc::time_point::now(); - db.open(data_dir.path(), [&]{return genesis_state;}); + db.open(data_dir.path(), [&]{return genesis_state;}, "test"); ilog("Opened database in ${t} milliseconds.", ("t", (fc::time_point::now() - start_time).count() / 1000)); for( int i = 11; i < account_count + 11; ++i) @@ -116,7 +116,7 @@ BOOST_AUTO_TEST_CASE( genesis_and_persistence_bench ) auto start_time = fc::time_point::now(); wlog( "about to start reindex..." ); - db.reindex(data_dir.path(), genesis_state); + db.open(data_dir.path(), [&]{return genesis_state;}, "force_wipe"); ilog("Replayed database in ${t} milliseconds.", ("t", (fc::time_point::now() - start_time).count() / 1000)); for( int i = 0; i < blocks_to_produce; ++i ) diff --git a/tests/common/database_fixture.cpp b/tests/common/database_fixture.cpp index e6a0b327f..1741c7876 100644 --- a/tests/common/database_fixture.cpp +++ b/tests/common/database_fixture.cpp @@ -355,7 +355,7 @@ void database_fixture::open_database() { if( !data_dir ) { data_dir = fc::temp_directory( graphene::utilities::temp_directory_path() ); - db.open(data_dir->path(), [this]{return genesis_state;}); + db.open(data_dir->path(), [this]{return genesis_state;}, "test"); } } diff --git a/tests/generate_empty_blocks/main.cpp b/tests/generate_empty_blocks/main.cpp index 1b45340d1..b6a2ca955 100644 --- a/tests/generate_empty_blocks/main.cpp +++ b/tests/generate_empty_blocks/main.cpp @@ -124,7 +124,7 @@ int main( int argc, char** argv ) database db; fc::path db_path = data_dir / "db"; - db.open(db_path, [&]() { return genesis; } ); + db.open(db_path, [&]() { return genesis; }, "TEST" ); uint32_t slot = 1; uint32_t missed = 0; diff --git a/tests/tests/block_tests.cpp b/tests/tests/block_tests.cpp index 07609d4b8..5990b12bb 100644 --- a/tests/tests/block_tests.cpp +++ b/tests/tests/block_tests.cpp @@ -138,7 +138,7 @@ BOOST_AUTO_TEST_CASE( generate_empty_blocks ) signed_block cutoff_block; { database db; - db.open(data_dir.path(), make_genesis ); + db.open(data_dir.path(), make_genesis, "TEST" ); b = db.generate_block(db.get_slot_time(1), db.get_scheduled_witness(1), init_account_priv_key, database::skip_nothing); // TODO: Change this test when we correct #406 @@ -162,7 +162,7 @@ BOOST_AUTO_TEST_CASE( generate_empty_blocks ) } { database db; - db.open(data_dir.path(), []{return genesis_state_type();}); + db.open(data_dir.path(), []{return genesis_state_type();}, "TEST"); BOOST_CHECK_EQUAL( db.head_block_num(), cutoff_block.block_num() ); b = cutoff_block; for( uint32_t i = 0; i < 200; ++i ) @@ -187,7 +187,7 @@ BOOST_AUTO_TEST_CASE( undo_block ) fc::temp_directory data_dir( graphene::utilities::temp_directory_path() ); { database db; - db.open(data_dir.path(), make_genesis); + db.open(data_dir.path(), make_genesis, "TEST"); fc::time_point_sec now( GRAPHENE_TESTING_GENESIS_TIMESTAMP ); std::vector< time_point_sec > time_stack; @@ -236,9 +236,9 @@ BOOST_AUTO_TEST_CASE( fork_blocks ) fc::temp_directory data_dir2( graphene::utilities::temp_directory_path() ); database db1; - db1.open(data_dir1.path(), make_genesis); + db1.open(data_dir1.path(), make_genesis, "TEST"); database db2; - db2.open(data_dir2.path(), make_genesis); + db2.open(data_dir2.path(), make_genesis, "TEST"); BOOST_CHECK( db1.get_chain_id() == db2.get_chain_id() ); auto init_account_priv_key = fc::ecc::private_key::regenerate(fc::sha256::hash(string("null_key")) ); @@ -381,7 +381,7 @@ BOOST_AUTO_TEST_CASE( undo_pending ) fc::temp_directory data_dir( graphene::utilities::temp_directory_path() ); { database db; - db.open(data_dir.path(), make_genesis); + db.open(data_dir.path(), make_genesis, "TEST"); auto init_account_priv_key = fc::ecc::private_key::regenerate(fc::sha256::hash(string("null_key")) ); public_key_type init_account_pub_key = init_account_priv_key.get_public_key(); @@ -446,8 +446,8 @@ BOOST_AUTO_TEST_CASE( switch_forks_undo_create ) dir2( graphene::utilities::temp_directory_path() ); database db1, db2; - db1.open(dir1.path(), make_genesis); - db2.open(dir2.path(), make_genesis); + db1.open(dir1.path(), make_genesis, "TEST"); + db2.open(dir2.path(), make_genesis, "TEST"); BOOST_CHECK( db1.get_chain_id() == db2.get_chain_id() ); auto init_account_priv_key = fc::ecc::private_key::regenerate(fc::sha256::hash(string("null_key")) ); @@ -505,8 +505,8 @@ BOOST_AUTO_TEST_CASE( duplicate_transactions ) dir2( graphene::utilities::temp_directory_path() ); database db1, db2; - db1.open(dir1.path(), make_genesis); - db2.open(dir2.path(), make_genesis); + db1.open(dir1.path(), make_genesis, "TEST"); + db2.open(dir2.path(), make_genesis, "TEST"); BOOST_CHECK( db1.get_chain_id() == db2.get_chain_id() ); auto skip_sigs = database::skip_transaction_signatures | database::skip_authority_check; @@ -555,7 +555,7 @@ BOOST_AUTO_TEST_CASE( tapos ) try { fc::temp_directory dir1( graphene::utilities::temp_directory_path() ); database db1; - db1.open(dir1.path(), make_genesis); + db1.open(dir1.path(), make_genesis, "TEST"); const account_object& init1 = *db1.get_index_type().indices().get().find("init1"); @@ -1106,7 +1106,7 @@ BOOST_FIXTURE_TEST_CASE( transaction_invalidated_in_cache, database_fixture ) fc::temp_directory data_dir2( graphene::utilities::temp_directory_path() ); database db2; - db2.open(data_dir2.path(), make_genesis); + db2.open(data_dir2.path(), make_genesis, "TEST"); BOOST_CHECK( db.get_chain_id() == db2.get_chain_id() ); while( db2.head_block_num() < db.head_block_num() ) @@ -1269,7 +1269,7 @@ BOOST_AUTO_TEST_CASE( genesis_reserve_ids ) genesis_state.initial_assets.push_back( usd ); return genesis_state; - } ); + }, "TEST" ); const auto& acct_idx = db.get_index_type().indices().get(); auto acct_itr = acct_idx.find("init0"); diff --git a/tests/tests/operation_tests2.cpp b/tests/tests/operation_tests2.cpp index 98d40207c..3981270d9 100644 --- a/tests/tests/operation_tests2.cpp +++ b/tests/tests/operation_tests2.cpp @@ -1111,7 +1111,7 @@ BOOST_AUTO_TEST_CASE( balance_object_test ) auto _sign = [&]( signed_transaction& tx, const private_key_type& key ) { tx.sign( key, db.get_chain_id() ); }; - db.open(td.path(), [this]{return genesis_state;}); + db.open(td.path(), [this]{return genesis_state;}, "TEST"); const balance_object& balance = balance_id_type()(db); BOOST_CHECK_EQUAL(balance.balance.amount.value, 1); BOOST_CHECK_EQUAL(balance_id_type(1)(db).balance.amount.value, 1); From a0052d4bd35f2a0882a051a44b5b9f656317b9e3 Mon Sep 17 00:00:00 2001 From: Peter Conrad Date: Thu, 13 Jul 2017 20:26:35 +0200 Subject: [PATCH 03/13] Enable undo + fork database for final blocks in a replay Dont remove blocks from block db when popping blocks, handle edge case in replay wrt fork_db, adapted unit tests --- libraries/chain/db_block.cpp | 1 - libraries/chain/db_management.cpp | 40 ++++++++++++++++++------------- tests/tests/block_tests.cpp | 6 ++++- 3 files changed, 29 insertions(+), 18 deletions(-) diff --git a/libraries/chain/db_block.cpp b/libraries/chain/db_block.cpp index 9b2c7f36a..ad4ad1a07 100644 --- a/libraries/chain/db_block.cpp +++ b/libraries/chain/db_block.cpp @@ -506,7 +506,6 @@ void database::pop_block() GRAPHENE_ASSERT( head_block.valid(), pop_empty_chain, "there are no blocks to pop" ); _fork_db.pop_block(); - _block_id_to_block.remove( head_id ); pop_undo(); _popped_tx.insert( _popped_tx.begin(), head_block->transactions.begin(), head_block->transactions.end() ); diff --git a/libraries/chain/db_management.cpp b/libraries/chain/db_management.cpp index ca4004e69..1a60520ad 100644 --- a/libraries/chain/db_management.cpp +++ b/libraries/chain/db_management.cpp @@ -64,11 +64,16 @@ void database::reindex( fc::path data_dir ) uint32_t undo_point = last_block_num - 50; ilog( "Replaying blocks..." ); - // Right now, we leave undo_db enabled when replaying when the bookie plugin is - // enabled. It depends on new/changed/removed object notifications, and those are - // only fired when the undo_db is enabled - if (!_slow_replays) - _undo_db.disable(); + if( head_block_num() >= undo_point ) + _fork_db.start_block( *fetch_block_by_number( head_block_num() ) ); + else + { + // Right now, we leave undo_db enabled when replaying when the bookie plugin is + // enabled. It depends on new/changed/removed object notifications, and those are + // only fired when the undo_db is enabled + if (!_slow_replays) + _undo_db.disable(); + } for( uint32_t i = head_block_num() + 1; i <= last_block_num; ++i ) { if( i % 10000 == 0 ) std::cerr << " " << double(i*100)/last_block_num << "% "< block = _block_id_to_block.fetch_by_number(i); if( !block.valid() ) { @@ -100,21 +103,26 @@ void database::reindex( fc::path data_dir ) wlog( "Dropped ${n} blocks from after the gap", ("n", dropped_count) ); break; } - if (_slow_replays) - push_block(*block, skip_fork_db | - skip_witness_signature | - skip_transaction_signatures | - skip_transaction_dupe_check | - skip_tapos_check | - skip_witness_schedule_check | - skip_authority_check); - else + if( i < undo_point && !_slow_replays) + { apply_block(*block, skip_witness_signature | skip_transaction_signatures | skip_transaction_dupe_check | skip_tapos_check | skip_witness_schedule_check | skip_authority_check); + } + else + { + if (!_slow_replays) + _undo_db.enable(); + push_block(*block, skip_witness_signature | + skip_transaction_signatures | + skip_transaction_dupe_check | + skip_tapos_check | + skip_witness_schedule_check | + skip_authority_check); + } } if (!_slow_replays) _undo_db.enable(); diff --git a/tests/tests/block_tests.cpp b/tests/tests/block_tests.cpp index 5990b12bb..daa0734ba 100644 --- a/tests/tests/block_tests.cpp +++ b/tests/tests/block_tests.cpp @@ -136,6 +136,7 @@ BOOST_AUTO_TEST_CASE( generate_empty_blocks ) // TODO: Don't generate this here auto init_account_priv_key = fc::ecc::private_key::regenerate(fc::sha256::hash(string("null_key")) ); signed_block cutoff_block; + uint32_t last_block; { database db; db.open(data_dir.path(), make_genesis, "TEST" ); @@ -155,6 +156,7 @@ BOOST_AUTO_TEST_CASE( generate_empty_blocks ) if( cutoff_height >= 200 ) { cutoff_block = *(db.fetch_block_by_number( cutoff_height )); + last_block = db.head_block_num(); break; } } @@ -163,7 +165,9 @@ BOOST_AUTO_TEST_CASE( generate_empty_blocks ) { database db; db.open(data_dir.path(), []{return genesis_state_type();}, "TEST"); - BOOST_CHECK_EQUAL( db.head_block_num(), cutoff_block.block_num() ); + BOOST_CHECK_EQUAL( db.head_block_num(), last_block ); + while( db.head_block_num() > cutoff_block.block_num() ) + db.pop_block(); b = cutoff_block; for( uint32_t i = 0; i < 200; ++i ) { From 3bee3f29a23aef02fcee520d5ab5cac6f2c490fc Mon Sep 17 00:00:00 2001 From: Peter Conrad Date: Mon, 31 Jul 2017 14:20:30 +0200 Subject: [PATCH 04/13] Log starting block number of replay --- libraries/chain/db_management.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/chain/db_management.cpp b/libraries/chain/db_management.cpp index 1a60520ad..0f6789101 100644 --- a/libraries/chain/db_management.cpp +++ b/libraries/chain/db_management.cpp @@ -63,7 +63,7 @@ void database::reindex( fc::path data_dir ) uint32_t flush_point = last_block_num - 10000; uint32_t undo_point = last_block_num - 50; - ilog( "Replaying blocks..." ); + ilog( "Replaying blocks, starting at ${next}...", ("next",head_block_num() + 1) ); if( head_block_num() >= undo_point ) _fork_db.start_block( *fetch_block_by_number( head_block_num() ) ); else From b45a6ca14732e73f69ba8feedc69280419be8d1e Mon Sep 17 00:00:00 2001 From: Peter Conrad Date: Mon, 31 Jul 2017 20:24:04 +0200 Subject: [PATCH 05/13] Prevent unsigned integer underflow --- libraries/chain/db_management.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/libraries/chain/db_management.cpp b/libraries/chain/db_management.cpp index 0f6789101..c2ffbf0a7 100644 --- a/libraries/chain/db_management.cpp +++ b/libraries/chain/db_management.cpp @@ -60,12 +60,15 @@ void database::reindex( fc::path data_dir ) ilog( "reindexing blockchain" ); auto start = fc::time_point::now(); const auto last_block_num = last_block->block_num(); - uint32_t flush_point = last_block_num - 10000; - uint32_t undo_point = last_block_num - 50; + uint32_t flush_point = last_block_num < 10000 ? 0 : last_block_num - 10000; + uint32_t undo_point = last_block_num < 50 ? 0 : last_block_num - 50; ilog( "Replaying blocks, starting at ${next}...", ("next",head_block_num() + 1) ); if( head_block_num() >= undo_point ) - _fork_db.start_block( *fetch_block_by_number( head_block_num() ) ); + { + if( head_block_num() > 0 ) + _fork_db.start_block( *fetch_block_by_number( head_block_num() ) ); + } else { // Right now, we leave undo_db enabled when replaying when the bookie plugin is From c8f8f1a44b21f1c1a7835be49c88783fc42e1c86 Mon Sep 17 00:00:00 2001 From: Peter Conrad Date: Thu, 3 Aug 2017 15:41:40 +0200 Subject: [PATCH 06/13] Fixed lock detection --- libraries/db/object_database.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/db/object_database.cpp b/libraries/db/object_database.cpp index 6e1fdea20..9d2516453 100644 --- a/libraries/db/object_database.cpp +++ b/libraries/db/object_database.cpp @@ -93,7 +93,7 @@ void object_database::wipe(const fc::path& data_dir) void object_database::open(const fc::path& data_dir) { try { - if( fc::exists( _data_dir / "object_database" / "lock" ) ) + if( fc::exists( data_dir / "object_database" / "lock" ) ) { wlog("Ignoring locked object_database"); return; From b71f20e06018f8c0d70fc3a559f41581b8ab3d7e Mon Sep 17 00:00:00 2001 From: Peter Conrad Date: Fri, 4 Aug 2017 17:55:13 +0200 Subject: [PATCH 07/13] Dont leave _data_dir empty if db is locked --- libraries/db/object_database.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/db/object_database.cpp b/libraries/db/object_database.cpp index 9d2516453..03ded2764 100644 --- a/libraries/db/object_database.cpp +++ b/libraries/db/object_database.cpp @@ -93,13 +93,13 @@ void object_database::wipe(const fc::path& data_dir) void object_database::open(const fc::path& data_dir) { try { - if( fc::exists( data_dir / "object_database" / "lock" ) ) + _data_dir = data_dir; + if( fc::exists( _data_dir / "object_database" / "lock" ) ) { wlog("Ignoring locked object_database"); return; } ilog("Opening object database from ${d} ...", ("d", data_dir)); - _data_dir = data_dir; for( uint32_t space = 0; space < _index.size(); ++space ) for( uint32_t type = 0; type < _index[space].size(); ++type ) if( _index[space][type] ) From 95a5b57c4feae99eb0bb1fa000140e8fd9701e73 Mon Sep 17 00:00:00 2001 From: Peter Conrad Date: Fri, 4 Aug 2017 20:50:55 +0200 Subject: [PATCH 08/13] Writing the object_database is now almost atomic --- libraries/db/object_database.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/libraries/db/object_database.cpp b/libraries/db/object_database.cpp index 03ded2764..fdde0fed6 100644 --- a/libraries/db/object_database.cpp +++ b/libraries/db/object_database.cpp @@ -71,16 +71,20 @@ index& object_database::get_mutable_index(uint8_t space_id, uint8_t type_id) void object_database::flush() { // ilog("Save object_database in ${d}", ("d", _data_dir)); - fc::create_directories( _data_dir / "object_database" / "lock" ); + fc::create_directories( _data_dir / "object_database.tmp" / "lock" ); for( uint32_t space = 0; space < _index.size(); ++space ) { - fc::create_directories( _data_dir / "object_database" / fc::to_string(space) ); + fc::create_directories( _data_dir / "object_database.tmp" / fc::to_string(space) ); const auto types = _index[space].size(); for( uint32_t type = 0; type < types; ++type ) if( _index[space][type] ) - _index[space][type]->save( _data_dir / "object_database" / fc::to_string(space)/fc::to_string(type) ); + _index[space][type]->save( _data_dir / "object_database.tmp" / fc::to_string(space)/fc::to_string(type) ); } - fc::remove_all( _data_dir / "object_database" / "lock" ); + fc::remove_all( _data_dir / "object_database.tmp" / "lock" ); + if( fc::exists( _data_dir / "object_database" ) ) + fc::rename( _data_dir / "object_database", _data_dir / "object_database.old" ); + fc::rename( _data_dir / "object_database.tmp", _data_dir / "object_database" ); + fc::remove_all( _data_dir / "object_database.old" ); } void object_database::wipe(const fc::path& data_dir) From 0d108fb8ef005af065941571f909b263dd4c0ee3 Mon Sep 17 00:00:00 2001 From: Peter Conrad Date: Fri, 4 Aug 2017 21:44:56 +0200 Subject: [PATCH 09/13] Improved consistency check for block_log --- libraries/chain/block_database.cpp | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/libraries/chain/block_database.cpp b/libraries/chain/block_database.cpp index 9c3516248..87aeed28d 100644 --- a/libraries/chain/block_database.cpp +++ b/libraries/chain/block_database.cpp @@ -216,23 +216,28 @@ optional block_database::last_index_entry()const { if( pos < sizeof(index_entry) ) return optional(); - if( pos % sizeof(index_entry) != 0 ) - pos -= pos % sizeof(index_entry); + pos -= pos % sizeof(index_entry); - while( e.block_size == 0 && pos > 0 ) + _blocks.seekg( 0, _block_num_to_pos.end ); + const std::streampos blocks_size = _blocks.tellg(); + while( pos >= 0 ) { pos -= sizeof(index_entry); _block_num_to_pos.seekg( pos ); _block_num_to_pos.read( (char*)&e, sizeof(e) ); - - if( e.block_size > 0 ) + if( _block_num_to_pos.gcount() == sizeof(e) && e.block_size > 0 + && e.block_pos + e.block_size <= blocks_size ) try { vector data( e.block_size ); _blocks.seekg( e.block_pos ); _blocks.read( data.data(), e.block_size ); - auto result = fc::raw::unpack(data); - return e; + if( _blocks.gcount() == e.block_size ) + { + const signed_block block = fc::raw::unpack(data); + if( block.id() == e.block_id ) + return e; + } } catch (const fc::exception&) { From ab382189fe1e4784d5afc4ef6b107b95d029e5ba Mon Sep 17 00:00:00 2001 From: Peter Conrad Date: Sat, 5 Aug 2017 01:39:42 +0200 Subject: [PATCH 10/13] Cut back block_log index file if inconsistent --- libraries/chain/block_database.cpp | 12 +++++++----- .../chain/include/graphene/chain/block_database.hpp | 1 + 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/libraries/chain/block_database.cpp b/libraries/chain/block_database.cpp index 87aeed28d..3dcdcba42 100644 --- a/libraries/chain/block_database.cpp +++ b/libraries/chain/block_database.cpp @@ -45,14 +45,15 @@ void block_database::open( const fc::path& dbdir ) _block_num_to_pos.exceptions(std::ios_base::failbit | std::ios_base::badbit); _blocks.exceptions(std::ios_base::failbit | std::ios_base::badbit); - if( !fc::exists( dbdir/"index" ) ) + _index_filename = dbdir / "index"; + if( !fc::exists( _index_filename ) ) { - _block_num_to_pos.open( (dbdir/"index").generic_string().c_str(), std::fstream::binary | std::fstream::in | std::fstream::out | std::fstream::trunc); + _block_num_to_pos.open( _index_filename.generic_string().c_str(), std::fstream::binary | std::fstream::in | std::fstream::out | std::fstream::trunc); _blocks.open( (dbdir/"blocks").generic_string().c_str(), std::fstream::binary | std::fstream::in | std::fstream::out | std::fstream::trunc); } else { - _block_num_to_pos.open( (dbdir/"index").generic_string().c_str(), std::fstream::binary | std::fstream::in | std::fstream::out ); + _block_num_to_pos.open( _index_filename.generic_string().c_str(), std::fstream::binary | std::fstream::in | std::fstream::out ); _blocks.open( (dbdir/"blocks").generic_string().c_str(), std::fstream::binary | std::fstream::in | std::fstream::out ); } } FC_CAPTURE_AND_RETHROW( (dbdir) ) } @@ -121,7 +122,7 @@ bool block_database::contains( const block_id_type& id )const index_entry e; auto index_pos = sizeof(e)*block_header::num_from_id(id); _block_num_to_pos.seekg( 0, _block_num_to_pos.end ); - if ( _block_num_to_pos.tellg() <= index_pos ) + if ( _block_num_to_pos.tellg() < index_pos + sizeof(e) ) return false; _block_num_to_pos.seekg( index_pos ); _block_num_to_pos.read( (char*)&e, sizeof(e) ); @@ -220,7 +221,7 @@ optional block_database::last_index_entry()const { _blocks.seekg( 0, _block_num_to_pos.end ); const std::streampos blocks_size = _blocks.tellg(); - while( pos >= 0 ) + while( pos > 0 ) { pos -= sizeof(index_entry); _block_num_to_pos.seekg( pos ); @@ -245,6 +246,7 @@ optional block_database::last_index_entry()const { catch (const std::exception&) { } + fc::resize_file( _index_filename, pos ); } } catch (const fc::exception&) diff --git a/libraries/chain/include/graphene/chain/block_database.hpp b/libraries/chain/include/graphene/chain/block_database.hpp index 2c7ff8128..d902cd1bc 100644 --- a/libraries/chain/include/graphene/chain/block_database.hpp +++ b/libraries/chain/include/graphene/chain/block_database.hpp @@ -47,6 +47,7 @@ namespace graphene { namespace chain { optional last_id()const; private: optional last_index_entry()const; + fc::path _index_filename; mutable std::fstream _blocks; mutable std::fstream _block_num_to_pos; }; From 7d0d61ab43af019ebd8e6cf7032bfe2de93563da Mon Sep 17 00:00:00 2001 From: Peter Conrad Date: Sun, 6 Aug 2017 14:20:04 +0200 Subject: [PATCH 11/13] Fixed undo_database --- libraries/db/undo_database.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/libraries/db/undo_database.cpp b/libraries/db/undo_database.cpp index b37b2c7db..c5f2ef65d 100644 --- a/libraries/db/undo_database.cpp +++ b/libraries/db/undo_database.cpp @@ -118,8 +118,6 @@ void undo_database::undo() _db.insert( std::move(*item.second) ); _stack.pop_back(); - if( _stack.empty() ) - _stack.emplace_back(); enable(); --_active_sessions; } FC_CAPTURE_AND_RETHROW() } @@ -127,6 +125,12 @@ void undo_database::undo() void undo_database::merge() { FC_ASSERT( _active_sessions > 0 ); + if( _active_sessions == 1 && _stack.size() == 1 ) + { + _stack.pop_back(); + --_active_sessions; + return; + } FC_ASSERT( _stack.size() >=2 ); auto& state = _stack.back(); auto& prev_state = _stack[_stack.size()-2]; From 731338f03c6d137164972264bfa279f201c1ce55 Mon Sep 17 00:00:00 2001 From: Peter Conrad Date: Sun, 6 Aug 2017 18:59:31 +0200 Subject: [PATCH 12/13] Added test case for broken merge on empty undo_db --- tests/tests/database_tests.cpp | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/tests/database_tests.cpp b/tests/tests/database_tests.cpp index 5dc35f276..0e2f12956 100644 --- a/tests/tests/database_tests.cpp +++ b/tests/tests/database_tests.cpp @@ -59,3 +59,22 @@ BOOST_AUTO_TEST_CASE( undo_test ) throw; } } + +BOOST_AUTO_TEST_CASE( merge_test ) +{ + try { + database db; + auto ses = db._undo_db.start_undo_session(); + const auto& bal_obj1 = db.create( [&]( account_balance_object& obj ){ + obj.balance = 42; + }); + ses.merge(); + + auto balance = db.get_balance( account_id_type(), asset_id_type() ); + BOOST_CHECK_EQUAL( 42, balance.amount.value ); + } catch ( const fc::exception& e ) + { + edump( (e.to_detail_string()) ); + throw; + } +} From 7b259ba2d3ea55bcdc4a8ab2629b78b6c0d1ec96 Mon Sep 17 00:00:00 2001 From: gladcow Date: Tue, 3 Sep 2019 08:07:46 +0300 Subject: [PATCH 13/13] exclude second undo_db.enable() call in some cases --- libraries/chain/db_management.cpp | 57 ++++++++++++++++++++++++++----- 1 file changed, 48 insertions(+), 9 deletions(-) diff --git a/libraries/chain/db_management.cpp b/libraries/chain/db_management.cpp index c2ffbf0a7..61f23db34 100644 --- a/libraries/chain/db_management.cpp +++ b/libraries/chain/db_management.cpp @@ -47,6 +47,50 @@ database::~database() clear_pending(); } +// Right now, we leave undo_db enabled when replaying when the bookie plugin is +// enabled. It depends on new/changed/removed object notifications, and those are +// only fired when the undo_db is enabled. +// So we use this helper object to disable undo_db only if it is not forbidden +// with _slow_replays flag. +class auto_undo_enabler +{ + const bool _slow_replays; + undo_database& _undo_db; + bool _disabled; +public: + auto_undo_enabler(bool slow_replays, undo_database& undo_db) : + _slow_replays(slow_replays), + _undo_db(undo_db), + _disabled(false) + { + } + + ~auto_undo_enabler() + { + try{ + enable(); + } FC_CAPTURE_AND_LOG(("undo_db enabling crash")) + } + + void enable() + { + if(!_disabled) + return; + _undo_db.enable(); + _disabled = false; + } + + void disable() + { + if(_disabled) + return; + if(_slow_replays) + return; + _undo_db.disable(); + _disabled = true; + } +}; + void database::reindex( fc::path data_dir ) { try { auto last_block = _block_id_to_block.last(); @@ -64,6 +108,7 @@ void database::reindex( fc::path data_dir ) uint32_t undo_point = last_block_num < 50 ? 0 : last_block_num - 50; ilog( "Replaying blocks, starting at ${next}...", ("next",head_block_num() + 1) ); + auto_undo_enabler undo(_slow_replays, _undo_db); if( head_block_num() >= undo_point ) { if( head_block_num() > 0 ) @@ -71,11 +116,7 @@ void database::reindex( fc::path data_dir ) } else { - // Right now, we leave undo_db enabled when replaying when the bookie plugin is - // enabled. It depends on new/changed/removed object notifications, and those are - // only fired when the undo_db is enabled - if (!_slow_replays) - _undo_db.disable(); + undo.disable(); } for( uint32_t i = head_block_num() + 1; i <= last_block_num; ++i ) { @@ -117,8 +158,7 @@ void database::reindex( fc::path data_dir ) } else { - if (!_slow_replays) - _undo_db.enable(); + undo.enable(); push_block(*block, skip_witness_signature | skip_transaction_signatures | skip_transaction_dupe_check | @@ -127,8 +167,7 @@ void database::reindex( fc::path data_dir ) skip_authority_check); } } - if (!_slow_replays) - _undo_db.enable(); + undo.enable(); auto end = fc::time_point::now(); ilog( "Done reindexing, elapsed time: ${t} sec", ("t",double((end-start).count())/1000000.0 ) ); } FC_CAPTURE_AND_RETHROW( (data_dir) ) }