From 1135ba8caa2e19fede8b35e20cee3d255969748e Mon Sep 17 00:00:00 2001 From: Wesley Shillingford Date: Wed, 8 May 2019 10:37:57 +0100 Subject: [PATCH] Stop node when using an incompatible ledger (#1964) * Stop node when using an incompatible database version * Give test correct comment --- nano/core_test/block_store.cpp | 23 ++ nano/node/lmdb.cpp | 15 +- nano/node/lmdb.hpp | 2 +- nano/node/node.cpp | 401 +++++++++++++++++---------------- nano/node/node.hpp | 1 - nano/node/wallet.cpp | 2 +- nano/node/wallet.hpp | 2 +- 7 files changed, 237 insertions(+), 209 deletions(-) diff --git a/nano/core_test/block_store.cpp b/nano/core_test/block_store.cpp index b39bd33f..0e16bc41 100644 --- a/nano/core_test/block_store.cpp +++ b/nano/core_test/block_store.cpp @@ -1660,6 +1660,29 @@ TEST (block_store, upgrade_confirmation_height_many) } } +// Ledger versions are not forward compatible +TEST (block_store, incompatible_version) +{ + auto path (nano::unique_path ()); + nano::logger_mt logger; + { + auto error (false); + nano::mdb_store store (error, logger, path); + ASSERT_FALSE (error); + + // Put version to an unreachable number so that it should always be incompatible + auto transaction (store.tx_begin_write ()); + store.version_put (transaction, std::numeric_limits::max ()); + } + + // Now try and read it, should give an error + { + auto error (false); + nano::mdb_store store (error, logger, path); + ASSERT_TRUE (error); + } +} + namespace { // These functions take the latest account_info and create a legacy one so that upgrade tests can be emulated more easily. diff --git a/nano/node/lmdb.cpp b/nano/node/lmdb.cpp index caadd1e7..fdfbba1c 100644 --- a/nano/node/lmdb.cpp +++ b/nano/node/lmdb.cpp @@ -843,8 +843,8 @@ env (error_a, path_a, lmdb_max_dbs) } if (!error_a) { - do_upgrades (transaction, batch_size); - if (drop_unchecked) + error_a |= do_upgrades (transaction, batch_size); + if (!error_a && drop_unchecked) { unchecked_clear (transaction); } @@ -959,9 +959,11 @@ nano::store_iterator nano::mdb_store::peers_ return result; } -void nano::mdb_store::do_upgrades (nano::write_transaction & transaction_a, size_t batch_size) +bool nano::mdb_store::do_upgrades (nano::write_transaction & transaction_a, size_t batch_size) { - switch (version_get (transaction_a)) + auto error (false); + auto version_l = version_get (transaction_a); + switch (version_l) { case 1: upgrade_v1_to_v2 (transaction_a); @@ -992,8 +994,11 @@ void nano::mdb_store::do_upgrades (nano::write_transaction & transaction_a, size case 14: break; default: - assert (false); + logger.always_log (boost::str (boost::format ("The version of the ledger (%1%) is too high for this node") % version_l)); + error = true; + break; } + return error; } void nano::mdb_store::upgrade_v1_to_v2 (nano::transaction const & transaction_a) diff --git a/nano/node/lmdb.hpp b/nano/node/lmdb.hpp index 8f79b911..5a5e6abb 100644 --- a/nano/node/lmdb.hpp +++ b/nano/node/lmdb.hpp @@ -404,7 +404,7 @@ private: boost::optional block_raw_get_by_type (nano::transaction const &, nano::block_hash const &, nano::block_type &) const; void block_raw_put (nano::transaction const &, MDB_dbi, nano::block_hash const &, MDB_val); void clear (MDB_dbi); - void do_upgrades (nano::write_transaction &, size_t); + bool do_upgrades (nano::write_transaction &, size_t); void upgrade_v1_to_v2 (nano::transaction const &); void upgrade_v2_to_v3 (nano::transaction const &); void upgrade_v3_to_v4 (nano::transaction const &); diff --git a/nano/node/node.cpp b/nano/node/node.cpp index 5a6d1d6f..619d0c1a 100644 --- a/nano/node/node.cpp +++ b/nano/node/node.cpp @@ -648,7 +648,7 @@ std::unique_ptr collect_seq_con_info (alarm & alarm, con bool nano::node_init::error () const { - return block_store_init || wallet_init || wallets_store_init; + return block_store_init || wallets_store_init; } nano::vote_processor::vote_processor (nano::node & node_a) : @@ -1047,7 +1047,7 @@ block_processor_thread ([this]() { this->block_processor.process_blocks (); }), online_reps (*this, config.online_weight_minimum.number ()), -wallets (init_a.wallet_init, *this), +wallets (init_a.wallets_store_init, *this), stats (config.stat_config), vote_uniquer (block_uniquer), active (*this, delay_frontier_confirmation_height_updating), @@ -1055,183 +1055,184 @@ confirmation_height_processor (pending_confirmation_height, store, ledger.stats, payment_observer_processor (observers.blocks), startup_time (std::chrono::steady_clock::now ()) { - if (config.websocket_config.enabled) - { - auto endpoint_l (nano::tcp_endpoint (config.websocket_config.address, config.websocket_config.port)); - websocket_server = std::make_shared (*this, endpoint_l); - this->websocket_server->run (); - } - - wallets.observer = [this](bool active) { - observers.wallet.notify (active); - }; - network.channel_observer = [this](std::shared_ptr channel_a) { - observers.endpoint.notify (channel_a); - }; - network.disconnect_observer = [this]() { - observers.disconnect.notify (); - }; - if (!config.callback_address.empty ()) - { - observers.blocks.add ([this](std::shared_ptr block_a, nano::account const & account_a, nano::amount const & amount_a, bool is_state_send_a) { - if (this->block_arrival.recent (block_a->hash ())) - { - auto node_l (shared_from_this ()); - background ([node_l, block_a, account_a, amount_a, is_state_send_a]() { - boost::property_tree::ptree event; - event.add ("account", account_a.to_account ()); - event.add ("hash", block_a->hash ().to_string ()); - std::string block_text; - block_a->serialize_json (block_text); - event.add ("block", block_text); - event.add ("amount", amount_a.to_string_dec ()); - if (is_state_send_a) - { - event.add ("is_send", is_state_send_a); - event.add ("subtype", "send"); - } - // Subtype field - else if (block_a->type () == nano::block_type::state) - { - if (block_a->link ().is_zero ()) - { - event.add ("subtype", "change"); - } - else if (amount_a == 0 && !node_l->ledger.epoch_link.is_zero () && node_l->ledger.is_epoch_link (block_a->link ())) - { - event.add ("subtype", "epoch"); - } - else - { - event.add ("subtype", "receive"); - } - } - std::stringstream ostream; - boost::property_tree::write_json (ostream, event); - ostream.flush (); - auto body (std::make_shared (ostream.str ())); - auto address (node_l->config.callback_address); - auto port (node_l->config.callback_port); - auto target (std::make_shared (node_l->config.callback_target)); - auto resolver (std::make_shared (node_l->io_ctx)); - resolver->async_resolve (boost::asio::ip::tcp::resolver::query (address, std::to_string (port)), [node_l, address, port, target, body, resolver](boost::system::error_code const & ec, boost::asio::ip::tcp::resolver::iterator i_a) { - if (!ec) - { - node_l->do_rpc_callback (i_a, address, port, target, body, resolver); - } - else - { - if (node_l->config.logging.callback_logging ()) - { - node_l->logger.always_log (boost::str (boost::format ("Error resolving callback: %1%:%2%: %3%") % address % port % ec.message ())); - } - node_l->stats.inc (nano::stat::type::error, nano::stat::detail::http_callback, nano::stat::dir::out); - } - }); - }); - } - }); - } - if (websocket_server) - { - observers.blocks.add ([this](std::shared_ptr block_a, nano::account const & account_a, nano::amount const & amount_a, bool is_state_send_a) { - if (this->websocket_server->any_subscribers (nano::websocket::topic::confirmation)) - { - if (this->block_arrival.recent (block_a->hash ())) - { - std::string subtype; - if (is_state_send_a) - { - subtype = "send"; - } - else if (block_a->type () == nano::block_type::state) - { - if (block_a->link ().is_zero ()) - { - subtype = "change"; - } - else if (amount_a == 0 && !this->ledger.epoch_link.is_zero () && this->ledger.is_epoch_link (block_a->link ())) - { - subtype = "epoch"; - } - else - { - subtype = "receive"; - } - } - nano::websocket::message_builder builder; - auto msg (builder.block_confirmed (block_a, account_a, amount_a, subtype)); - this->websocket_server->broadcast (msg); - } - } - }); - } - observers.endpoint.add ([this](std::shared_ptr channel_a) { - this->network.send_keepalive (*channel_a); - }); - observers.vote.add ([this](nano::transaction const & transaction, std::shared_ptr vote_a, std::shared_ptr channel_a) { - this->gap_cache.vote (vote_a); - this->online_reps.observe (vote_a->account); - nano::uint128_t rep_weight; - nano::uint128_t min_rep_weight; - { - rep_weight = ledger.weight (transaction, vote_a->account); - min_rep_weight = online_reps.online_stake () / 1000; - } - if (rep_weight > min_rep_weight) - { - bool rep_crawler_exists (false); - for (auto hash : *vote_a) - { - if (this->rep_crawler.exists (hash)) - { - rep_crawler_exists = true; - break; - } - } - if (rep_crawler_exists) - { - // We see a valid non-replay vote for a block we requested, this node is probably a representative - if (this->rep_crawler.response (channel_a, vote_a->account, rep_weight)) - { - logger.try_log (boost::str (boost::format ("Found a representative at %1%") % channel_a->to_string ())); - // Rebroadcasting all active votes to new representative - auto blocks (this->active.list_blocks (true)); - for (auto i (blocks.begin ()), n (blocks.end ()); i != n; ++i) - { - if (*i != nullptr) - { - nano::confirm_req req (*i); - channel_a->send (req); - } - } - } - } - } - }); - if (this->websocket_server) - { - observers.vote.add ([this](nano::transaction const & transaction, std::shared_ptr vote_a, std::shared_ptr channel_a) { - if (this->websocket_server->any_subscribers (nano::websocket::topic::vote)) - { - nano::websocket::message_builder builder; - auto msg (builder.vote_received (vote_a)); - this->websocket_server->broadcast (msg); - } - }); - } - if (NANO_VERSION_PATCH == 0) - { - logger.always_log ("Node starting, version: ", NANO_MAJOR_MINOR_VERSION); - } - else - { - logger.always_log ("Node starting, version: ", NANO_MAJOR_MINOR_RC_VERSION); - } - - logger.always_log (boost::str (boost::format ("Work pool running %1% threads") % work.threads.size ())); if (!init_a.error ()) { + if (config.websocket_config.enabled) + { + auto endpoint_l (nano::tcp_endpoint (config.websocket_config.address, config.websocket_config.port)); + websocket_server = std::make_shared (*this, endpoint_l); + this->websocket_server->run (); + } + + wallets.observer = [this](bool active) { + observers.wallet.notify (active); + }; + network.channel_observer = [this](std::shared_ptr channel_a) { + observers.endpoint.notify (channel_a); + }; + network.disconnect_observer = [this]() { + observers.disconnect.notify (); + }; + if (!config.callback_address.empty ()) + { + observers.blocks.add ([this](std::shared_ptr block_a, nano::account const & account_a, nano::amount const & amount_a, bool is_state_send_a) { + if (this->block_arrival.recent (block_a->hash ())) + { + auto node_l (shared_from_this ()); + background ([node_l, block_a, account_a, amount_a, is_state_send_a]() { + boost::property_tree::ptree event; + event.add ("account", account_a.to_account ()); + event.add ("hash", block_a->hash ().to_string ()); + std::string block_text; + block_a->serialize_json (block_text); + event.add ("block", block_text); + event.add ("amount", amount_a.to_string_dec ()); + if (is_state_send_a) + { + event.add ("is_send", is_state_send_a); + event.add ("subtype", "send"); + } + // Subtype field + else if (block_a->type () == nano::block_type::state) + { + if (block_a->link ().is_zero ()) + { + event.add ("subtype", "change"); + } + else if (amount_a == 0 && !node_l->ledger.epoch_link.is_zero () && node_l->ledger.is_epoch_link (block_a->link ())) + { + event.add ("subtype", "epoch"); + } + else + { + event.add ("subtype", "receive"); + } + } + std::stringstream ostream; + boost::property_tree::write_json (ostream, event); + ostream.flush (); + auto body (std::make_shared (ostream.str ())); + auto address (node_l->config.callback_address); + auto port (node_l->config.callback_port); + auto target (std::make_shared (node_l->config.callback_target)); + auto resolver (std::make_shared (node_l->io_ctx)); + resolver->async_resolve (boost::asio::ip::tcp::resolver::query (address, std::to_string (port)), [node_l, address, port, target, body, resolver](boost::system::error_code const & ec, boost::asio::ip::tcp::resolver::iterator i_a) { + if (!ec) + { + node_l->do_rpc_callback (i_a, address, port, target, body, resolver); + } + else + { + if (node_l->config.logging.callback_logging ()) + { + node_l->logger.always_log (boost::str (boost::format ("Error resolving callback: %1%:%2%: %3%") % address % port % ec.message ())); + } + node_l->stats.inc (nano::stat::type::error, nano::stat::detail::http_callback, nano::stat::dir::out); + } + }); + }); + } + }); + } + if (websocket_server) + { + observers.blocks.add ([this](std::shared_ptr block_a, nano::account const & account_a, nano::amount const & amount_a, bool is_state_send_a) { + if (this->websocket_server->any_subscribers (nano::websocket::topic::confirmation)) + { + if (this->block_arrival.recent (block_a->hash ())) + { + std::string subtype; + if (is_state_send_a) + { + subtype = "send"; + } + else if (block_a->type () == nano::block_type::state) + { + if (block_a->link ().is_zero ()) + { + subtype = "change"; + } + else if (amount_a == 0 && !this->ledger.epoch_link.is_zero () && this->ledger.is_epoch_link (block_a->link ())) + { + subtype = "epoch"; + } + else + { + subtype = "receive"; + } + } + nano::websocket::message_builder builder; + auto msg (builder.block_confirmed (block_a, account_a, amount_a, subtype)); + this->websocket_server->broadcast (msg); + } + } + }); + } + observers.endpoint.add ([this](std::shared_ptr channel_a) { + this->network.send_keepalive (*channel_a); + }); + observers.vote.add ([this](nano::transaction const & transaction, std::shared_ptr vote_a, std::shared_ptr channel_a) { + this->gap_cache.vote (vote_a); + this->online_reps.observe (vote_a->account); + nano::uint128_t rep_weight; + nano::uint128_t min_rep_weight; + { + rep_weight = ledger.weight (transaction, vote_a->account); + min_rep_weight = online_reps.online_stake () / 1000; + } + if (rep_weight > min_rep_weight) + { + bool rep_crawler_exists (false); + for (auto hash : *vote_a) + { + if (this->rep_crawler.exists (hash)) + { + rep_crawler_exists = true; + break; + } + } + if (rep_crawler_exists) + { + // We see a valid non-replay vote for a block we requested, this node is probably a representative + if (this->rep_crawler.response (channel_a, vote_a->account, rep_weight)) + { + logger.try_log (boost::str (boost::format ("Found a representative at %1%") % channel_a->to_string ())); + // Rebroadcasting all active votes to new representative + auto blocks (this->active.list_blocks (true)); + for (auto i (blocks.begin ()), n (blocks.end ()); i != n; ++i) + { + if (*i != nullptr) + { + nano::confirm_req req (*i); + channel_a->send (req); + } + } + } + } + } + }); + if (this->websocket_server) + { + observers.vote.add ([this](nano::transaction const & transaction, std::shared_ptr vote_a, std::shared_ptr channel_a) { + if (this->websocket_server->any_subscribers (nano::websocket::topic::vote)) + { + nano::websocket::message_builder builder; + auto msg (builder.vote_received (vote_a)); + this->websocket_server->broadcast (msg); + } + }); + } + if (NANO_VERSION_PATCH == 0) + { + logger.always_log ("Node starting, version: ", NANO_MAJOR_MINOR_VERSION); + } + else + { + logger.always_log ("Node starting, version: ", NANO_MAJOR_MINOR_RC_VERSION); + } + + logger.always_log (boost::str (boost::format ("Work pool running %1% threads") % work.threads.size ())); + if (config.logging.node_lifetime_tracing ()) { logger.always_log ("Constructing node"); @@ -1251,35 +1252,35 @@ startup_time (std::chrono::steady_clock::now ()) node_id = nano::keypair (); logger.always_log ("Node ID: ", node_id.pub.to_account ()); - } - const uint8_t * weight_buffer = network_params.network.is_live_network () ? nano_bootstrap_weights_live : nano_bootstrap_weights_beta; - size_t weight_size = network_params.network.is_live_network () ? nano_bootstrap_weights_live_size : nano_bootstrap_weights_beta_size; - if (network_params.network.is_live_network () || network_params.network.is_beta_network ()) - { - nano::bufferstream weight_stream ((const uint8_t *)weight_buffer, weight_size); - nano::uint128_union block_height; - if (!nano::try_read (weight_stream, block_height)) + const uint8_t * weight_buffer = network_params.network.is_live_network () ? nano_bootstrap_weights_live : nano_bootstrap_weights_beta; + size_t weight_size = network_params.network.is_live_network () ? nano_bootstrap_weights_live_size : nano_bootstrap_weights_beta_size; + if (network_params.network.is_live_network () || network_params.network.is_beta_network ()) { - auto max_blocks = (uint64_t)block_height.number (); - auto transaction (store.tx_begin_read ()); - if (ledger.store.block_count (transaction).sum () < max_blocks) + nano::bufferstream weight_stream ((const uint8_t *)weight_buffer, weight_size); + nano::uint128_union block_height; + if (!nano::try_read (weight_stream, block_height)) { - ledger.bootstrap_weight_max_blocks = max_blocks; - while (true) + auto max_blocks = (uint64_t)block_height.number (); + auto transaction (store.tx_begin_read ()); + if (ledger.store.block_count (transaction).sum () < max_blocks) { - nano::account account; - if (nano::try_read (weight_stream, account.bytes)) + ledger.bootstrap_weight_max_blocks = max_blocks; + while (true) { - break; + nano::account account; + if (nano::try_read (weight_stream, account.bytes)) + { + break; + } + nano::amount weight; + if (nano::try_read (weight_stream, weight.bytes)) + { + break; + } + logger.always_log ("Using bootstrap rep weight: ", account.to_account (), " -> ", weight.format_balance (Mxrb_ratio, 0, true), " XRB"); + ledger.bootstrap_weights[account] = weight.number (); } - nano::amount weight; - if (nano::try_read (weight_stream, weight.bytes)) - { - break; - } - logger.always_log ("Using bootstrap rep weight: ", account.to_account (), " -> ", weight.format_balance (Mxrb_ratio, 0, true), " XRB"); - ledger.bootstrap_weights[account] = weight.number (); } } } diff --git a/nano/node/node.hpp b/nano/node/node.hpp index 22a2206d..510c6d5f 100644 --- a/nano/node/node.hpp +++ b/nano/node/node.hpp @@ -287,7 +287,6 @@ public: bool error () const; bool block_store_init{ false }; bool wallets_store_init{ false }; - bool wallet_init{ false }; }; class vote_processor final diff --git a/nano/node/wallet.cpp b/nano/node/wallet.cpp index b89b23e8..2d9153e5 100644 --- a/nano/node/wallet.cpp +++ b/nano/node/wallet.cpp @@ -1516,7 +1516,7 @@ void nano::wallets::do_wallet_actions () } } -nano::wallets::wallets (bool & error_a, nano::node & node_a) : +nano::wallets::wallets (bool error_a, nano::node & node_a) : observer ([](bool) {}), node (node_a), env (boost::polymorphic_downcast (node_a.wallets_store_impl.get ())->environment), diff --git a/nano/node/wallet.hpp b/nano/node/wallet.hpp index 2c45574b..8891bba6 100644 --- a/nano/node/wallet.hpp +++ b/nano/node/wallet.hpp @@ -182,7 +182,7 @@ public: class wallets final { public: - wallets (bool &, nano::node &); + wallets (bool, nano::node &); ~wallets (); std::shared_ptr open (nano::uint256_union const &); std::shared_ptr create (nano::uint256_union const &);