Skip to content

Commit

Permalink
Provide optional automatic ledger/wallet backups before an upgrade
Browse files Browse the repository at this point in the history
  • Loading branch information
SergiySW committed Aug 5, 2019
1 parent 6d6b2b2 commit a96df1a
Show file tree
Hide file tree
Showing 10 changed files with 204 additions and 11 deletions.
40 changes: 40 additions & 0 deletions nano/core_test/block_store.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1652,6 +1652,46 @@ TEST (mdb_block_store, upgrade_v13_v14)
ASSERT_EQ (error_node_id, MDB_NOTFOUND);
}

TEST (mdb_block_store, upgrade_backup)
{
auto dir (nano::unique_path ());
namespace fs = boost::filesystem;
fs::create_directory (dir);
auto path = dir / dir.leaf ();
/** Returns 'path' if backup file cannot be found */
// clang-format off
auto get_backup_path = [&dir]() {
for (fs::directory_iterator itr (dir); itr != fs::directory_iterator (); ++itr)
{
if (itr->path ().filename ().string ().find ("backup.upgrade.ldb") != std::string::npos)
{
return itr->path ();
}
}
return dir;
};
// clang-format on

{
nano::logger_mt logger;
nano::genesis genesis;
auto error (false);
nano::mdb_store store (error, logger, path);
auto transaction (store.tx_begin_write ());
store.version_put (transaction, 14);
}
ASSERT_EQ (get_backup_path ().string (), dir.string ());

// Now do the upgrade and confirm that backup is saved
nano::logger_mt logger;
auto error (false);
nano::mdb_store store (error, logger, path, nano::txn_tracking_config{}, std::chrono::seconds (5), 128, false, 512, true);
ASSERT_FALSE (error);
auto transaction (store.tx_begin_read ());
ASSERT_LT (14, store.version_get (transaction));
ASSERT_NE (get_backup_path ().string (), dir.string ());
}

// Test various confirmation height values as well as clearing them
TEST (block_store, confirmation_height)
{
Expand Down
52 changes: 52 additions & 0 deletions nano/core_test/node.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -842,6 +842,58 @@ TEST (node_config, v17_values)
ASSERT_EQ (config.conf_height_processor_batch_min_time.count (), 500);
}

TEST (node_config, v17_v18_upgrade)
{
auto path (nano::unique_path ());
nano::jsonconfig tree;
add_required_children_node_config_tree (tree);
tree.put ("version", "17");

auto upgraded (false);
nano::node_config config;
config.logging.init (path);
// These config options should not be present
ASSERT_FALSE (tree.get_optional_child ("backup_before_upgrade"));

config.deserialize_json (upgraded, tree);
// The config options should be added after the upgrade
ASSERT_TRUE (!!tree.get_optional_child ("backup_before_upgrade"));

ASSERT_TRUE (upgraded);
auto version (tree.get<std::string> ("version"));

// Check version is updated
ASSERT_GT (std::stoull (version), 17);
}

TEST (node_config, v18_values)
{
nano::jsonconfig tree;
add_required_children_node_config_tree (tree);

auto path (nano::unique_path ());
auto upgraded (false);
nano::node_config config;
config.logging.init (path);

// Check config is correct
{
tree.put ("backup_before_upgrade", true);
}

config.deserialize_json (upgraded, tree);
ASSERT_FALSE (upgraded);
ASSERT_EQ (config.backup_before_upgrade, true);

// Check config is correct with other values
tree.put ("backup_before_upgrade", false);

upgraded = false;
config.deserialize_json (upgraded, tree);
ASSERT_FALSE (upgraded);
ASSERT_EQ (config.backup_before_upgrade, false);
}

// Regression test to ensure that deserializing includes changes node via get_required_child
TEST (node_config, required_child)
{
Expand Down
71 changes: 66 additions & 5 deletions nano/core_test/wallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <fstream>

using namespace std::chrono_literals;
unsigned constexpr nano::wallet_store::version_current;

TEST (wallet, no_key)
{
Expand Down Expand Up @@ -698,7 +699,7 @@ TEST (wallet, version_1_upgrade)
wallet->store.version_put (transaction, 1);
wallet->enter_password (transaction, "1");
ASSERT_TRUE (wallet->store.valid_password (transaction));
ASSERT_EQ (wallet->store.version_current, wallet->store.version (transaction));
ASSERT_EQ (nano::wallet_store::version_current, wallet->store.version (transaction));
nano::raw_key prv;
ASSERT_FALSE (wallet->store.fetch (transaction, key.pub, prv));
ASSERT_EQ (key.prv, prv);
Expand All @@ -710,7 +711,7 @@ TEST (wallet, version_1_upgrade)
wallet->store.version_put (transaction, 1);
wallet->enter_password (transaction, "1");
ASSERT_TRUE (wallet->store.valid_password (transaction));
ASSERT_EQ (wallet->store.version_current, wallet->store.version (transaction));
ASSERT_EQ (nano::wallet_store::version_current, wallet->store.version (transaction));
nano::raw_key prv2;
ASSERT_FALSE (wallet->store.fetch (transaction, key.pub, prv2));
ASSERT_EQ (key.prv, prv2);
Expand Down Expand Up @@ -821,7 +822,7 @@ TEST (wallet, version_2_upgrade)
ASSERT_FALSE (wallet->store.exists (transaction, nano::wallet_store::deterministic_index_special));
ASSERT_FALSE (wallet->store.exists (transaction, nano::wallet_store::seed_special));
wallet->store.attempt_password (transaction, "1");
ASSERT_EQ (wallet->store.version_current, wallet->store.version (transaction));
ASSERT_EQ (nano::wallet_store::version_current, wallet->store.version (transaction));
ASSERT_TRUE (wallet->store.exists (transaction, nano::wallet_store::deterministic_index_special));
ASSERT_TRUE (wallet->store.exists (transaction, nano::wallet_store::seed_special));
ASSERT_FALSE (wallet->deterministic_insert (transaction).is_zero ());
Expand All @@ -835,7 +836,7 @@ TEST (wallet, version_3_upgrade)
wallet->store.rekey (transaction, "1");
wallet->enter_password (transaction, "1");
ASSERT_TRUE (wallet->store.valid_password (transaction));
ASSERT_EQ (wallet->store.version_current, wallet->store.version (transaction));
ASSERT_EQ (nano::wallet_store::version_current, wallet->store.version (transaction));
nano::keypair key;
nano::raw_key seed;
nano::uint256_union seed_ciphertext;
Expand All @@ -853,7 +854,7 @@ TEST (wallet, version_3_upgrade)
wallet->store.version_put (transaction, 3);
wallet->enter_password (transaction, "1");
ASSERT_TRUE (wallet->store.valid_password (transaction));
ASSERT_EQ (wallet->store.version_current, wallet->store.version (transaction));
ASSERT_EQ (nano::wallet_store::version_current, wallet->store.version (transaction));
nano::raw_key prv;
ASSERT_FALSE (wallet->store.fetch (transaction, key.pub, prv));
ASSERT_EQ (key.prv, prv);
Expand All @@ -863,6 +864,66 @@ TEST (wallet, version_3_upgrade)
ASSERT_NE (seed_ciphertext, wallet->store.entry_get_raw (transaction, nano::wallet_store::seed_special).key);
}

TEST (wallet, upgrade_backup)
{
nano::system system (24000, 1);
auto dir (nano::unique_path ());
namespace fs = boost::filesystem;
fs::create_directory (dir);
/** Returns 'path' if backup file cannot be found */
// clang-format off
auto get_backup_path = [&dir]() {
for (fs::directory_iterator itr (dir); itr != fs::directory_iterator (); ++itr)
{
if (itr->path ().filename ().string ().find ("backup.wallets.ldb") != std::string::npos)
{
return itr->path ();
}
}
return dir;
};
// clang-format on

nano::keypair id;
{
nano::node_init init1;
auto node1 (std::make_shared<nano::node> (init1, system.io_ctx, 24001, dir, system.alarm, system.logging, system.work));
ASSERT_FALSE (init1.error ());
auto wallet (node1->wallets.create (id.pub));
ASSERT_NE (nullptr, wallet);
auto transaction (node1->wallets.tx_begin_write ());
wallet->store.version_put (transaction, 3);
}
ASSERT_EQ (get_backup_path ().string (), dir.string ());

// Check with config backup_before_upgrade = false
{
nano::node_init init1;
auto node1 (std::make_shared<nano::node> (init1, system.io_ctx, 24001, dir, system.alarm, system.logging, system.work));
ASSERT_FALSE (init1.error ());
auto wallet (node1->wallets.open (id.pub));
ASSERT_NE (nullptr, wallet);
auto transaction (node1->wallets.tx_begin_write ());
ASSERT_LT (3, wallet->store.version (transaction));
wallet->store.version_put (transaction, 3);
}
ASSERT_EQ (get_backup_path ().string (), dir.string ());

// Now do the upgrade and confirm that backup is saved
{
nano::node_config node_config (24001, system.logging);
node_config.backup_before_upgrade = true;
nano::node_init init1;
auto node1 (std::make_shared<nano::node> (init1, system.io_ctx, nano::unique_path (), system.alarm, node_config, system.work));
ASSERT_FALSE (init1.error ());
auto wallet (node1->wallets.open (id.pub));
ASSERT_NE (nullptr, wallet);
auto transaction (node1->wallets.tx_begin_read ());
ASSERT_LT (3, wallet->store.version (transaction));
}
ASSERT_NE (get_backup_path ().string (), dir.string ());
}

TEST (wallet, no_work)
{
nano::system system (24000, 1);
Expand Down
10 changes: 8 additions & 2 deletions nano/node/lmdb/lmdb.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ void mdb_val::convert_buffer_to_value ()
}
}

nano::mdb_store::mdb_store (bool & error_a, nano::logger_mt & logger_a, boost::filesystem::path const & path_a, nano::txn_tracking_config const & txn_tracking_config_a, std::chrono::milliseconds block_processor_batch_max_time_a, int lmdb_max_dbs, bool drop_unchecked, size_t const batch_size) :
nano::mdb_store::mdb_store (bool & error_a, nano::logger_mt & logger_a, boost::filesystem::path const & path_a, nano::txn_tracking_config const & txn_tracking_config_a, std::chrono::milliseconds block_processor_batch_max_time_a, int lmdb_max_dbs, bool drop_unchecked, size_t const batch_size, bool backup_before_upgrade) :
logger (logger_a),
env (error_a, path_a, lmdb_max_dbs, true),
mdb_txn_tracker (logger_a, txn_tracking_config_a, block_processor_batch_max_time_a),
Expand All @@ -60,9 +60,15 @@ txn_tracking_enabled (txn_tracking_config_a.enable)

// 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 --fastbootstrap flag for instance)
// (can be a few minutes with the --fast_bootstrap flag for instance)
if (!is_fully_upgraded)
{
// Backup database before upgrade
if (backup_before_upgrade)
{
auto backup_path = path_a.parent_path () / "backup.upgrade.ldb";
mdb_env_copy (env, backup_path.string ().c_str ());
}
auto transaction (tx_begin_write ());
open_databases (error_a, transaction, MDB_CREATE);
if (!error_a)
Expand Down
2 changes: 1 addition & 1 deletion nano/node/lmdb/lmdb.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class mdb_store : public block_store_partial<MDB_val, mdb_store>
using block_store_partial::block_exists;
using block_store_partial::unchecked_put;

mdb_store (bool &, nano::logger_mt &, boost::filesystem::path const &, nano::txn_tracking_config const & txn_tracking_config_a = nano::txn_tracking_config{}, std::chrono::milliseconds block_processor_batch_max_time_a = std::chrono::milliseconds (5000), int lmdb_max_dbs = 128, bool drop_unchecked = false, size_t batch_size = 512);
mdb_store (bool &, nano::logger_mt &, boost::filesystem::path const &, nano::txn_tracking_config const & txn_tracking_config_a = nano::txn_tracking_config{}, std::chrono::milliseconds block_processor_batch_max_time_a = std::chrono::milliseconds (5000), int lmdb_max_dbs = 128, bool drop_unchecked = false, size_t batch_size = 512, bool backup_before_upgrade = false);
nano::write_transaction tx_begin_write () override;
nano::read_transaction tx_begin_read () override;

Expand Down
2 changes: 1 addition & 1 deletion nano/node/node.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ flags (flags_a),
alarm (alarm_a),
work (work_a),
logger (config_a.logging.min_time_between_log_output),
store_impl (std::make_unique<nano::mdb_store> (init_a.block_store_init, logger, application_path_a / "data.ldb", config_a.diagnostics_config.txn_tracking, config_a.block_processor_batch_max_time, config_a.lmdb_max_dbs, !flags.disable_unchecked_drop, flags.sideband_batch_size)),
store_impl (std::make_unique<nano::mdb_store> (init_a.block_store_init, logger, application_path_a / "data.ldb", config_a.diagnostics_config.txn_tracking, config_a.block_processor_batch_max_time, config_a.lmdb_max_dbs, !flags.disable_unchecked_drop, flags.sideband_batch_size, config_a.backup_before_upgrade)),
store (*store_impl),
wallets_store_impl (std::make_unique<nano::mdb_wallets_store> (init_a.wallets_store_init, application_path_a / "wallets.ldb", config_a.lmdb_max_dbs)),
wallets_store (*wallets_store_impl),
Expand Down
6 changes: 6 additions & 0 deletions nano/node/nodeconfig.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ nano::error nano::node_config::serialize_json (nano::jsonconfig & json) const
json.put ("confirmation_history_size", confirmation_history_size);
json.put ("active_elections_size", active_elections_size);
json.put ("bandwidth_limit", bandwidth_limit);
json.put ("backup_before_upgrade", backup_before_upgrade);

return json.get_error ();
}
Expand Down Expand Up @@ -258,6 +259,10 @@ bool nano::node_config::upgrade_json (unsigned version_a, nano::jsonconfig & jso
json.put ("conf_height_processor_batch_min_time", conf_height_processor_batch_min_time.count ());
}
case 17:
{
json.put ("backup_before_upgrade", backup_before_upgrade);
}
case 18:
break;
default:
throw std::runtime_error ("Unknown node_config version");
Expand Down Expand Up @@ -409,6 +414,7 @@ nano::error nano::node_config::deserialize_json (bool & upgraded_a, nano::jsonco
json.get<size_t> ("confirmation_history_size", confirmation_history_size);
json.get<size_t> ("active_elections_size", active_elections_size);
json.get<size_t> ("bandwidth_limit", bandwidth_limit);
json.get<bool> ("backup_before_upgrade", backup_before_upgrade);

auto conf_height_processor_batch_min_time_l (conf_height_processor_batch_min_time.count ());
json.get ("conf_height_processor_batch_min_time", conf_height_processor_batch_min_time_l);
Expand Down
3 changes: 2 additions & 1 deletion nano/node/nodeconfig.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,10 @@ class node_config
static std::chrono::minutes constexpr wallet_backup_interval = std::chrono::minutes (5);
size_t bandwidth_limit{ 5 * 1024 * 1024 }; // 5Mb/s
std::chrono::milliseconds conf_height_processor_batch_min_time{ 50 };
bool backup_before_upgrade{ false };
static unsigned json_version ()
{
return 17;
return 18;
}
};

Expand Down
26 changes: 26 additions & 0 deletions nano/node/wallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1607,6 +1607,24 @@ thread ([this]() {
}
}
}
// Backup before upgrade wallets
bool backup_required (false);
if (node.config.backup_before_upgrade)
{
auto transaction (tx_begin_read ());
for (auto & item : items)
{
if (item.second->store.version (transaction) != nano::wallet_store::version_current)
{
backup_required = true;
break;
}
}
}
if (backup_required)
{
backup_before_upgrade ();
}
for (auto & item : items)
{
item.second->enter_initial_password ();
Expand Down Expand Up @@ -1936,6 +1954,14 @@ void nano::wallets::move_table (std::string const & name_a, MDB_txn * tx_source,
assert (!error6);
}

void nano::wallets::backup_before_upgrade ()
{
const char *store_path;
mdb_env_get_path (env, &store_path);
auto backup_path = boost::filesystem::path (store_path).parent_path () / "backup.wallets.ldb";
mdb_env_copy (env, backup_path.string ().c_str ());
}

nano::uint128_t const nano::wallets::generate_priority = std::numeric_limits<nano::uint128_t>::max ();
nano::uint128_t const nano::wallets::high_priority = std::numeric_limits<nano::uint128_t>::max () - 1;

Expand Down
3 changes: 2 additions & 1 deletion nano/node/wallet.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ class wallet_store final
static unsigned const version_2 = 2;
static unsigned const version_3 = 3;
static unsigned const version_4 = 4;
unsigned const version_current = version_4;
static unsigned constexpr version_current = version_4;
static nano::uint256_union const version_special;
static nano::uint256_union const wallet_key_special;
static nano::uint256_union const salt_special;
Expand Down Expand Up @@ -204,6 +204,7 @@ class wallets final
void ongoing_compute_reps ();
void split_if_needed (nano::transaction &, nano::block_store &);
void move_table (std::string const &, MDB_txn *, MDB_txn *);
void backup_before_upgrade ();
nano::network_params network_params;
std::function<void(bool)> observer;
std::unordered_map<nano::uint256_union, std::shared_ptr<nano::wallet>> items;
Expand Down

0 comments on commit a96df1a

Please sign in to comment.