dncurrency/nano/store/lmdb/lmdb.cpp
2024-04-14 22:07:39 +02:00

495 lines
19 KiB
C++

#include <nano/lib/numbers.hpp>
#include <nano/lib/stream.hpp>
#include <nano/lib/utility.hpp>
#include <nano/secure/ledger.hpp>
#include <nano/secure/parallel_traversal.hpp>
#include <nano/store/lmdb/iterator.hpp>
#include <nano/store/lmdb/lmdb.hpp>
#include <nano/store/lmdb/wallet_value.hpp>
#include <nano/store/version.hpp>
#include <nano/store/versioning.hpp>
#include <boost/format.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <queue>
nano::store::lmdb::component::component (nano::logger & logger_a, std::filesystem::path const & path_a, nano::ledger_constants & constants, nano::txn_tracking_config const & txn_tracking_config_a, std::chrono::milliseconds block_processor_batch_max_time_a, nano::lmdb_config const & lmdb_config_a, bool backup_before_upgrade_a) :
// clang-format off
nano::store::component{
block_store,
account_store,
pending_store,
online_weight_store,
pruned_store,
peer_store,
confirmation_height_store,
final_vote_store,
version_store,
rep_weight_store,
false // write_queue use_noops
},
// clang-format on
block_store{ *this },
account_store{ *this },
pending_store{ *this },
online_weight_store{ *this },
pruned_store{ *this },
peer_store{ *this },
confirmation_height_store{ *this },
final_vote_store{ *this },
version_store{ *this },
rep_weight_store{ *this },
logger{ logger_a },
env (error, path_a, nano::store::lmdb::env::options::make ().set_config (lmdb_config_a).set_use_no_mem_init (true)),
mdb_txn_tracker (logger_a, txn_tracking_config_a, block_processor_batch_max_time_a),
txn_tracking_enabled (txn_tracking_config_a.enable)
{
if (!error)
{
debug_assert (path_a.filename () == "data.ldb");
auto is_fully_upgraded (false);
auto is_fresh_db (false);
{
auto transaction (tx_begin_read ());
auto err = mdb_dbi_open (env.tx (transaction), "meta", 0, &version_store.meta_handle);
is_fresh_db = err != MDB_SUCCESS;
if (err == MDB_SUCCESS)
{
is_fully_upgraded = (version.get (transaction) == version_current);
mdb_dbi_close (env, version_store.meta_handle);
}
}
// Only open a write lock when upgrades are needed. This is because CLI commands
// open inactive nodes which can otherwise be locked here if there is a long write
// (can be a few minutes with the --fast_bootstrap flag for instance)
if (!is_fully_upgraded)
{
if (!is_fresh_db)
{
logger.info (nano::log::type::lmdb, "Upgrade in progress...");
if (backup_before_upgrade_a)
{
create_backup_file (env, path_a, logger);
}
}
auto needs_vacuuming = false;
{
auto transaction (tx_begin_write ());
open_databases (error, transaction, MDB_CREATE);
if (!error)
{
error |= do_upgrades (transaction, constants, needs_vacuuming);
}
}
if (needs_vacuuming)
{
logger.info (nano::log::type::lmdb, "Ledger vaccum in progress...");
auto vacuum_success = vacuum_after_upgrade (path_a, lmdb_config_a);
if (vacuum_success)
{
logger.info (nano::log::type::lmdb, "Ledger vacuum completed");
}
else
{
logger.error (nano::log::type::lmdb, "Ledger vaccum failed");
logger.error (nano::log::type::lmdb, "(Optional) Please ensure enough disk space is available for a copy of the database and try to vacuum after shutting down the node");
}
}
}
else
{
auto transaction (tx_begin_read ());
open_databases (error, transaction, 0);
}
}
}
bool nano::store::lmdb::component::vacuum_after_upgrade (std::filesystem::path const & path_a, nano::lmdb_config const & lmdb_config_a)
{
// Vacuum the database. This is not a required step and may actually fail if there isn't enough storage space.
auto vacuum_path = path_a.parent_path () / "vacuumed.ldb";
auto vacuum_success = copy_db (vacuum_path);
if (vacuum_success)
{
// Need to close the database to release the file handle
mdb_env_sync (env.environment, true);
mdb_env_close (env.environment);
env.environment = nullptr;
// Replace the ledger file with the vacuumed one
std::filesystem::rename (vacuum_path, path_a);
// Set up the environment again
auto options = nano::store::lmdb::env::options::make ()
.set_config (lmdb_config_a)
.set_use_no_mem_init (true);
env.init (error, path_a, options);
if (!error)
{
auto transaction (tx_begin_read ());
open_databases (error, transaction, 0);
}
}
else
{
// The vacuum file can be in an inconsistent state if there wasn't enough space to create it
std::filesystem::remove (vacuum_path);
}
return vacuum_success;
}
void nano::store::lmdb::component::serialize_mdb_tracker (boost::property_tree::ptree & json, std::chrono::milliseconds min_read_time, std::chrono::milliseconds min_write_time)
{
mdb_txn_tracker.serialize_json (json, min_read_time, min_write_time);
}
void nano::store::lmdb::component::serialize_memory_stats (boost::property_tree::ptree & json)
{
MDB_stat stats;
auto status (mdb_env_stat (env.environment, &stats));
release_assert (status == 0);
json.put ("branch_pages", stats.ms_branch_pages);
json.put ("depth", stats.ms_depth);
json.put ("entries", stats.ms_entries);
json.put ("leaf_pages", stats.ms_leaf_pages);
json.put ("overflow_pages", stats.ms_overflow_pages);
json.put ("page_size", stats.ms_psize);
}
nano::store::write_transaction nano::store::lmdb::component::tx_begin_write (std::vector<nano::tables> const &, std::vector<nano::tables> const &)
{
return env.tx_begin_write (create_txn_callbacks ());
}
nano::store::read_transaction nano::store::lmdb::component::tx_begin_read () const
{
return env.tx_begin_read (create_txn_callbacks ());
}
std::string nano::store::lmdb::component::vendor_get () const
{
return boost::str (boost::format ("LMDB %1%.%2%.%3%") % MDB_VERSION_MAJOR % MDB_VERSION_MINOR % MDB_VERSION_PATCH);
}
nano::store::lmdb::txn_callbacks nano::store::lmdb::component::create_txn_callbacks () const
{
nano::store::lmdb::txn_callbacks mdb_txn_callbacks;
if (txn_tracking_enabled)
{
mdb_txn_callbacks.txn_start = ([&mdb_txn_tracker = mdb_txn_tracker] (store::transaction_impl const * transaction_impl) {
mdb_txn_tracker.add (transaction_impl);
});
mdb_txn_callbacks.txn_end = ([&mdb_txn_tracker = mdb_txn_tracker] (store::transaction_impl const * transaction_impl) {
mdb_txn_tracker.erase (transaction_impl);
});
}
return mdb_txn_callbacks;
}
void nano::store::lmdb::component::open_databases (bool & error_a, store::transaction const & transaction_a, unsigned flags)
{
error_a |= mdb_dbi_open (env.tx (transaction_a), "online_weight", flags, &online_weight_store.online_weight_handle) != 0;
error_a |= mdb_dbi_open (env.tx (transaction_a), "meta", flags, &version_store.meta_handle) != 0;
error_a |= mdb_dbi_open (env.tx (transaction_a), "peers", flags, &peer_store.peers_handle) != 0;
error_a |= mdb_dbi_open (env.tx (transaction_a), "pruned", flags, &pruned_store.pruned_handle) != 0;
error_a |= mdb_dbi_open (env.tx (transaction_a), "confirmation_height", flags, &confirmation_height_store.confirmation_height_handle) != 0;
error_a |= mdb_dbi_open (env.tx (transaction_a), "accounts", flags, &account_store.accounts_v0_handle) != 0;
account_store.accounts_handle = account_store.accounts_v0_handle;
error_a |= mdb_dbi_open (env.tx (transaction_a), "pending", flags, &pending_store.pending_v0_handle) != 0;
pending_store.pending_handle = pending_store.pending_v0_handle;
error_a |= mdb_dbi_open (env.tx (transaction_a), "final_votes", flags, &final_vote_store.final_votes_handle) != 0;
error_a |= mdb_dbi_open (env.tx (transaction_a), "blocks", MDB_CREATE, &block_store.blocks_handle) != 0;
error_a |= mdb_dbi_open (env.tx (transaction_a), "rep_weights", flags, &rep_weight_store.rep_weights_handle) != 0;
}
bool nano::store::lmdb::component::do_upgrades (store::write_transaction & transaction_a, nano::ledger_constants & constants, bool & needs_vacuuming)
{
auto error (false);
auto version_l = version.get (transaction_a);
if (version_l < version_minimum)
{
logger.critical (nano::log::type::lmdb, "The version of the ledger ({}) is lower than the minimum ({}) which is supported for upgrades. Either upgrade a node first or delete the ledger.", version_l, version_minimum);
return true;
}
switch (version_l)
{
case 21:
upgrade_v21_to_v22 (transaction_a);
[[fallthrough]];
case 22:
upgrade_v22_to_v23 (transaction_a);
[[fallthrough]];
case 23:
upgrade_v23_to_v24 (transaction_a);
[[fallthrough]];
case 24:
break;
default:
logger.critical (nano::log::type::lmdb, "The version of the ledger ({}) is too high for this node", version_l);
error = true;
break;
}
return error;
}
void nano::store::lmdb::component::upgrade_v21_to_v22 (store::write_transaction const & transaction_a)
{
logger.info (nano::log::type::lmdb, "Upgrading database from v21 to v22...");
MDB_dbi unchecked_handle{ 0 };
release_assert (!mdb_dbi_open (env.tx (transaction_a), "unchecked", MDB_CREATE, &unchecked_handle));
release_assert (!mdb_drop (env.tx (transaction_a), unchecked_handle, 1)); // del = 1, to delete it from the environment and close the DB handle.
version.put (transaction_a, 22);
logger.info (nano::log::type::lmdb, "Upgrading database from v21 to v22 completed");
}
// Fill rep_weights table with all existing representatives and their vote weight
void nano::store::lmdb::component::upgrade_v22_to_v23 (store::write_transaction const & transaction_a)
{
logger.info (nano::log::type::lmdb, "Upgrading database from v22 to v23...");
auto i{ make_iterator<nano::account, nano::account_info_v22> (transaction_a, tables::accounts) };
auto end{ store::iterator<nano::account, nano::account_info_v22> (nullptr) };
uint64_t processed_accounts = 0;
for (; i != end; ++i)
{
if (!i->second.balance.is_zero ())
{
nano::uint128_t total{ 0 };
nano::store::lmdb::db_val value;
auto status = get (transaction_a, tables::rep_weights, i->second.representative, value);
if (success (status))
{
total = nano::amount{ value }.number ();
}
total += i->second.balance.number ();
status = put (transaction_a, tables::rep_weights, i->second.representative, nano::amount{ total });
release_assert_success (status);
}
processed_accounts++;
if (processed_accounts % 250000 == 0)
{
logger.info (nano::log::type::lmdb, "Processed {} accounts", processed_accounts);
}
}
logger.info (nano::log::type::lmdb, "Processed {} accounts", processed_accounts);
version.put (transaction_a, 23);
logger.info (nano::log::type::lmdb, "Upgrading database from v22 to v23 completed");
}
void nano::store::lmdb::component::upgrade_v23_to_v24 (store::write_transaction const & transaction_a)
{
logger.info (nano::log::type::lmdb, "Upgrading database from v23 to v24...");
MDB_dbi frontiers_handle{ 0 };
release_assert (!mdb_dbi_open (env.tx (transaction_a), "frontiers", MDB_CREATE, &frontiers_handle));
release_assert (!mdb_drop (env.tx (transaction_a), frontiers_handle, 1)); // del = 1, to delete it from the environment and close the DB handle.
version.put (transaction_a, 24);
logger.info (nano::log::type::lmdb, "Upgrading database from v23 to v24 completed");
}
/** Takes a filepath, appends '_backup_<timestamp>' to the end (but before any extension) and saves that file in the same directory */
void nano::store::lmdb::component::create_backup_file (nano::store::lmdb::env & env_a, std::filesystem::path const & filepath_a, nano::logger & logger)
{
auto extension = filepath_a.extension ();
auto filename_without_extension = filepath_a.filename ().replace_extension ("");
auto orig_filepath = filepath_a;
auto & backup_path = orig_filepath.remove_filename ();
auto backup_filename = filename_without_extension;
backup_filename += "_backup_";
backup_filename += std::to_string (std::chrono::system_clock::now ().time_since_epoch ().count ());
backup_filename += extension;
auto backup_filepath = backup_path / backup_filename;
logger.info (nano::log::type::lmdb, "Performing {} backup before database upgrade...", filepath_a.filename ().string ());
auto error (mdb_env_copy (env_a, backup_filepath.string ().c_str ()));
if (error)
{
logger.critical (nano::log::type::lmdb, "Database backup failed");
std::exit (1);
}
else
{
logger.info (nano::log::type::lmdb, "Database backup completed. Backup can be found at: {}", backup_filepath.string ());
}
}
bool nano::store::lmdb::component::exists (store::transaction const & transaction_a, tables table_a, nano::store::lmdb::db_val const & key_a) const
{
nano::store::lmdb::db_val junk;
auto status = get (transaction_a, table_a, key_a, junk);
release_assert (status == MDB_SUCCESS || status == MDB_NOTFOUND);
return (status == MDB_SUCCESS);
}
int nano::store::lmdb::component::get (store::transaction const & transaction_a, tables table_a, nano::store::lmdb::db_val const & key_a, nano::store::lmdb::db_val & value_a) const
{
return mdb_get (env.tx (transaction_a), table_to_dbi (table_a), key_a, value_a);
}
int nano::store::lmdb::component::put (store::write_transaction const & transaction_a, tables table_a, nano::store::lmdb::db_val const & key_a, nano::store::lmdb::db_val const & value_a) const
{
return (mdb_put (env.tx (transaction_a), table_to_dbi (table_a), key_a, value_a, 0));
}
int nano::store::lmdb::component::del (store::write_transaction const & transaction_a, tables table_a, nano::store::lmdb::db_val const & key_a) const
{
return (mdb_del (env.tx (transaction_a), table_to_dbi (table_a), key_a, nullptr));
}
int nano::store::lmdb::component::drop (store::write_transaction const & transaction_a, tables table_a)
{
return clear (transaction_a, table_to_dbi (table_a));
}
int nano::store::lmdb::component::clear (store::write_transaction const & transaction_a, MDB_dbi handle_a)
{
return mdb_drop (env.tx (transaction_a), handle_a, 0);
}
uint64_t nano::store::lmdb::component::count (store::transaction const & transaction_a, tables table_a) const
{
return count (transaction_a, table_to_dbi (table_a));
}
uint64_t nano::store::lmdb::component::count (store::transaction const & transaction_a, MDB_dbi db_a) const
{
MDB_stat stats;
auto status (mdb_stat (env.tx (transaction_a), db_a, &stats));
release_assert_success (status);
return (stats.ms_entries);
}
MDB_dbi nano::store::lmdb::component::table_to_dbi (tables table_a) const
{
switch (table_a)
{
case tables::accounts:
return account_store.accounts_handle;
case tables::blocks:
return block_store.blocks_handle;
case tables::pending:
return pending_store.pending_handle;
case tables::online_weight:
return online_weight_store.online_weight_handle;
case tables::meta:
return version_store.meta_handle;
case tables::peers:
return peer_store.peers_handle;
case tables::pruned:
return pruned_store.pruned_handle;
case tables::confirmation_height:
return confirmation_height_store.confirmation_height_handle;
case tables::final_votes:
return final_vote_store.final_votes_handle;
case tables::rep_weights:
return rep_weight_store.rep_weights_handle;
default:
release_assert (false);
return peer_store.peers_handle;
}
}
bool nano::store::lmdb::component::not_found (int status) const
{
return (status_code_not_found () == status);
}
bool nano::store::lmdb::component::success (int status) const
{
return (MDB_SUCCESS == status);
}
int nano::store::lmdb::component::status_code_not_found () const
{
return MDB_NOTFOUND;
}
std::string nano::store::lmdb::component::error_string (int status) const
{
return mdb_strerror (status);
}
bool nano::store::lmdb::component::copy_db (std::filesystem::path const & destination_file)
{
return !mdb_env_copy2 (env.environment, destination_file.string ().c_str (), MDB_CP_COMPACT);
}
void nano::store::lmdb::component::rebuild_db (store::write_transaction const & transaction_a)
{
// Tables with uint256_union key
std::vector<MDB_dbi> tables = { account_store.accounts_handle, block_store.blocks_handle, pruned_store.pruned_handle, confirmation_height_store.confirmation_height_handle };
for (auto const & table : tables)
{
MDB_dbi temp;
mdb_dbi_open (env.tx (transaction_a), "temp_table", MDB_CREATE, &temp);
// Copy all values to temporary table
for (auto i (store::iterator<nano::uint256_union, nano::store::lmdb::db_val> (std::make_unique<nano::store::lmdb::iterator<nano::uint256_union, nano::store::lmdb::db_val>> (transaction_a, env, table))), n (store::iterator<nano::uint256_union, nano::store::lmdb::db_val> (nullptr)); i != n; ++i)
{
auto s = mdb_put (env.tx (transaction_a), temp, nano::store::lmdb::db_val (i->first), i->second, MDB_APPEND);
release_assert_success (s);
}
release_assert (count (transaction_a, table) == count (transaction_a, temp));
// Clear existing table
mdb_drop (env.tx (transaction_a), table, 0);
// Put values from copy
for (auto i (store::iterator<nano::uint256_union, nano::store::lmdb::db_val> (std::make_unique<nano::store::lmdb::iterator<nano::uint256_union, nano::store::lmdb::db_val>> (transaction_a, env, temp))), n (store::iterator<nano::uint256_union, nano::store::lmdb::db_val> (nullptr)); i != n; ++i)
{
auto s = mdb_put (env.tx (transaction_a), table, nano::store::lmdb::db_val (i->first), i->second, MDB_APPEND);
release_assert_success (s);
}
release_assert (count (transaction_a, table) == count (transaction_a, temp));
// Remove temporary table
mdb_drop (env.tx (transaction_a), temp, 1);
}
// Pending table
{
MDB_dbi temp;
mdb_dbi_open (env.tx (transaction_a), "temp_table", MDB_CREATE, &temp);
// Copy all values to temporary table
for (auto i (store::iterator<nano::pending_key, nano::pending_info> (std::make_unique<nano::store::lmdb::iterator<nano::pending_key, nano::pending_info>> (transaction_a, env, pending_store.pending_handle))), n (store::iterator<nano::pending_key, nano::pending_info> (nullptr)); i != n; ++i)
{
auto s = mdb_put (env.tx (transaction_a), temp, nano::store::lmdb::db_val (i->first), nano::store::lmdb::db_val (i->second), MDB_APPEND);
release_assert_success (s);
}
release_assert (count (transaction_a, pending_store.pending_handle) == count (transaction_a, temp));
mdb_drop (env.tx (transaction_a), pending_store.pending_handle, 0);
// Put values from copy
for (auto i (store::iterator<nano::pending_key, nano::pending_info> (std::make_unique<nano::store::lmdb::iterator<nano::pending_key, nano::pending_info>> (transaction_a, env, temp))), n (store::iterator<nano::pending_key, nano::pending_info> (nullptr)); i != n; ++i)
{
auto s = mdb_put (env.tx (transaction_a), pending_store.pending_handle, nano::store::lmdb::db_val (i->first), nano::store::lmdb::db_val (i->second), MDB_APPEND);
release_assert_success (s);
}
release_assert (count (transaction_a, pending_store.pending_handle) == count (transaction_a, temp));
mdb_drop (env.tx (transaction_a), temp, 1);
}
}
bool nano::store::lmdb::component::init_error () const
{
return error;
}
nano::store::lmdb::component::upgrade_counters::upgrade_counters (uint64_t count_before_v0, uint64_t count_before_v1) :
before_v0 (count_before_v0),
before_v1 (count_before_v1)
{
}
bool nano::store::lmdb::component::upgrade_counters::are_equal () const
{
return (before_v0 == after_v0) && (before_v1 == after_v1);
}
unsigned nano::store::lmdb::component::max_block_write_batch_num () const
{
return std::numeric_limits<unsigned>::max ();
}