diff --git a/Makefile.am b/Makefile.am index 12a3acda..b1b5ccb7 100644 --- a/Makefile.am +++ b/Makefile.am @@ -56,6 +56,7 @@ src_libbitcoin_node_la_SOURCES = \ src/protocols/protocol_header_in_70012.cpp \ src/protocols/protocol_header_out_31800.cpp \ src/protocols/protocol_header_out_70012.cpp \ + src/protocols/protocol_observer.cpp \ src/protocols/protocol_transaction_in.cpp \ src/protocols/protocol_transaction_out.cpp \ src/sessions/session.cpp \ @@ -153,6 +154,7 @@ include_bitcoin_node_protocols_HEADERS = \ include/bitcoin/node/protocols/protocol_header_in_70012.hpp \ include/bitcoin/node/protocols/protocol_header_out_31800.hpp \ include/bitcoin/node/protocols/protocol_header_out_70012.hpp \ + include/bitcoin/node/protocols/protocol_observer.hpp \ include/bitcoin/node/protocols/protocol_transaction_in.hpp \ include/bitcoin/node/protocols/protocol_transaction_out.hpp \ include/bitcoin/node/protocols/protocols.hpp diff --git a/builds/cmake/CMakeLists.txt b/builds/cmake/CMakeLists.txt index be44db31..858df754 100644 --- a/builds/cmake/CMakeLists.txt +++ b/builds/cmake/CMakeLists.txt @@ -289,6 +289,7 @@ add_library( ${CANONICAL_LIB_NAME} "../../src/protocols/protocol_header_in_70012.cpp" "../../src/protocols/protocol_header_out_31800.cpp" "../../src/protocols/protocol_header_out_70012.cpp" + "../../src/protocols/protocol_observer.cpp" "../../src/protocols/protocol_transaction_in.cpp" "../../src/protocols/protocol_transaction_out.cpp" "../../src/sessions/session.cpp" diff --git a/builds/msvc/vs2022/libbitcoin-node/libbitcoin-node.vcxproj b/builds/msvc/vs2022/libbitcoin-node/libbitcoin-node.vcxproj index c1cf45d0..0c750f9f 100644 --- a/builds/msvc/vs2022/libbitcoin-node/libbitcoin-node.vcxproj +++ b/builds/msvc/vs2022/libbitcoin-node/libbitcoin-node.vcxproj @@ -93,6 +93,7 @@ + @@ -126,6 +127,7 @@ + diff --git a/builds/msvc/vs2022/libbitcoin-node/libbitcoin-node.vcxproj.filters b/builds/msvc/vs2022/libbitcoin-node/libbitcoin-node.vcxproj.filters index 2ffaf9da..8525813e 100644 --- a/builds/msvc/vs2022/libbitcoin-node/libbitcoin-node.vcxproj.filters +++ b/builds/msvc/vs2022/libbitcoin-node/libbitcoin-node.vcxproj.filters @@ -108,6 +108,9 @@ src\protocols + + src\protocols + src\protocols @@ -203,6 +206,9 @@ include\bitcoin\node\protocols + + include\bitcoin\node\protocols + include\bitcoin\node\protocols diff --git a/console/executor.cpp b/console/executor.cpp index fe68a2ba..eed5096b 100644 --- a/console/executor.cpp +++ b/console/executor.cpp @@ -45,6 +45,7 @@ using namespace std::placeholders; // "c" avoids conflict with network "quit" messages. const std::string executor::name_{ "bn" }; const std::string executor::close_{ "c" }; +const std::string executor::backup_{ "b" }; const std::unordered_map executor::defined_ { { levels::application, true }, @@ -93,10 +94,16 @@ const std::unordered_map executor::events_ { database::event_t::close_file, "close_file" }, { database::event_t::create_table, "create_table" }, { database::event_t::verify_table, "verify_table" }, - { database::event_t::close_table, "close_table" } + { database::event_t::close_table, "close_table" }, + { database::event_t::wait_lock, "wait_lock" }, + { database::event_t::flush_table, "flush_table" }, + { database::event_t::backup_table, "backup_table" }, + { database::event_t::dump_table, "dump_table" }, + { database::event_t::restore_table, "restore_table" } }; const std::unordered_map executor::tables_ { + { database::table_t::store, "store" }, { database::table_t::header_table, "header_table" }, { database::table_t::header_head, "header_head" }, { database::table_t::header_body, "header_body" }, @@ -1603,7 +1610,7 @@ void executor::subscribe_capture() { const auto token = system::trim_copy(line); - // Close (this isn't a toggle). + // Close (not a toggle). if (token == close_) { logger("CONSOLE: Close"); @@ -1611,6 +1618,26 @@ void executor::subscribe_capture() return false; } + // Backup (not a toggle). + if (token == backup_) + { + logger(BN_NODE_BACKUP_STARTED); + node_->pause(); + + const auto error = store_.snapshot([&](auto event, auto table) + { + logger(format(BN_BACKUP) % events_.at(event) % tables_.at(table)); + }); + + if (error) + logger(format(BN_NODE_BACKUP_FAIL) % error.message()); + else + logger(format(BN_NODE_BACKUP_COMPLETE)); + + node_->resume(); + return !error; + } + if (!keys_.contains(token)) { logger("CONSOLE: '" + line + "'"); diff --git a/console/executor.hpp b/console/executor.hpp index da63aba4..0016ed33 100644 --- a/console/executor.hpp +++ b/console/executor.hpp @@ -85,6 +85,7 @@ class executor static const std::string name_; static const std::string close_; + static const std::string backup_; static const std::unordered_map defined_; static const std::unordered_map display_; static const std::unordered_map keys_; diff --git a/console/localize.hpp b/console/localize.hpp index 10ccc4c3..3f0eaa5a 100644 --- a/console/localize.hpp +++ b/console/localize.hpp @@ -132,6 +132,8 @@ namespace node { "open::%1%(%2%)" #define BN_CLOSE \ "close::%1%(%2%)" +#define BN_BACKUP \ + "backup::%1%(%2%)" #define BN_NODE_INTERRUPT \ "Press CTRL-C to stop the node." @@ -141,6 +143,12 @@ namespace node { "Please wait while the network is starting..." #define BN_NODE_START_FAIL \ "Node failed to start with error, %1%." +#define BN_NODE_BACKUP_STARTED \ + "Node backup started." +#define BN_NODE_BACKUP_FAIL \ + "Node failed to backup with error, %1%." +#define BN_NODE_BACKUP_COMPLETE \ + "Node backup complete." #define BN_NODE_STARTED \ "Node is started." #define BN_NODE_RUNNING \ diff --git a/include/bitcoin/node.hpp b/include/bitcoin/node.hpp index ad995182..805f4d15 100644 --- a/include/bitcoin/node.hpp +++ b/include/bitcoin/node.hpp @@ -46,6 +46,7 @@ #include #include #include +#include #include #include #include diff --git a/include/bitcoin/node/chasers/chaser.hpp b/include/bitcoin/node/chasers/chaser.hpp index dd885f28..cfce568b 100644 --- a/include/bitcoin/node/chasers/chaser.hpp +++ b/include/bitcoin/node/chasers/chaser.hpp @@ -67,6 +67,14 @@ class BCN_API chaser /// Issued by 'session_outbound' and handled by 'block_in_31800'. stall, + /// Channels (all) are directed to pause reading. + /// Iissued by 'full_node' and handled by 'protocol'. + pause, + + /// Channels (all) are directed to resume reading. + /// Iissued by 'full_node' and handled by 'protocol'. + resume, + /// A block has been downloaded, checked and stored (height_t). /// Issued by 'block_in_31800' and handled by 'connect'. checked, diff --git a/include/bitcoin/node/full_node.hpp b/include/bitcoin/node/full_node.hpp index 783bbd8a..40c9affc 100644 --- a/include/bitcoin/node/full_node.hpp +++ b/include/bitcoin/node/full_node.hpp @@ -29,6 +29,7 @@ namespace libbitcoin { namespace node { + // Thread safe. class BCN_API full_node : public network::p2p { @@ -81,6 +82,12 @@ class BCN_API full_node /// Methods. /// ----------------------------------------------------------------------- + /// Pause the node. + virtual void pause() NOEXCEPT; + + /// Resume the node. + virtual void resume() NOEXCEPT; + /// The candidate chain is current. virtual bool is_current() const NOEXCEPT; diff --git a/include/bitcoin/node/protocols/protocol.hpp b/include/bitcoin/node/protocols/protocol.hpp index fa23270f..b6a08473 100644 --- a/include/bitcoin/node/protocols/protocol.hpp +++ b/include/bitcoin/node/protocols/protocol.hpp @@ -32,7 +32,7 @@ namespace libbitcoin { namespace node { -/// Abstract base for node protocols. +/// Abstract base for node protocols, thread safe. class BCN_API protocol : public network::protocol { diff --git a/include/bitcoin/node/protocols/protocol_block_in_31800.hpp b/include/bitcoin/node/protocols/protocol_block_in_31800.hpp index 897635d8..a13c9cb0 100644 --- a/include/bitcoin/node/protocols/protocol_block_in_31800.hpp +++ b/include/bitcoin/node/protocols/protocol_block_in_31800.hpp @@ -19,7 +19,6 @@ #ifndef LIBBITCOIN_NODE_PROTOCOLS_PROTOCOL_BLOCK_IN_31800_HPP #define LIBBITCOIN_NODE_PROTOCOLS_PROTOCOL_BLOCK_IN_31800_HPP -#include #include #include #include @@ -72,6 +71,8 @@ class BCN_API protocol_block_in_31800 chaser::chase event_, chaser::link value) NOEXCEPT; virtual void do_get_downloads(chaser::count_t count) NOEXCEPT; virtual void do_split(chaser::channel_t channel) NOEXCEPT; + void do_pause(chaser::channel_t channel) NOEXCEPT; + void do_resume(chaser::channel_t channel) NOEXCEPT; /// Accept incoming block message. virtual bool handle_receive_block(const code& ec, diff --git a/include/bitcoin/node/protocols/protocol_observer.hpp b/include/bitcoin/node/protocols/protocol_observer.hpp new file mode 100644 index 00000000..4cca0f7e --- /dev/null +++ b/include/bitcoin/node/protocols/protocol_observer.hpp @@ -0,0 +1,56 @@ +/** + * Copyright (c) 2011-2023 libbitcoin developers (see AUTHORS) + * + * This file is part of libbitcoin. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +#ifndef LIBBITCOIN_NODE_PROTOCOLS_PROTOCOL_OBSERVER_HPP +#define LIBBITCOIN_NODE_PROTOCOLS_PROTOCOL_OBSERVER_HPP + +#include +#include +#include + +namespace libbitcoin { +namespace node { + +class BCN_API protocol_observer + : public node::protocol, + protected network::tracker +{ +public: + typedef std::shared_ptr ptr; + + template + protocol_observer(Session& session, + const channel_ptr& channel) NOEXCEPT + : node::protocol(session, channel), + network::tracker(session.log) + { + } + + void start() NOEXCEPT override; + + virtual void handle_event(const code& ec, + chaser::chase event_, chaser::link value) NOEXCEPT; + + void do_pause(chaser::channel_t channel) NOEXCEPT; + void do_resume(chaser::channel_t channel) NOEXCEPT; +}; + +} // namespace node +} // namespace libbitcoin + +#endif diff --git a/include/bitcoin/node/protocols/protocols.hpp b/include/bitcoin/node/protocols/protocols.hpp index 68dc5b78..e3d3d29f 100644 --- a/include/bitcoin/node/protocols/protocols.hpp +++ b/include/bitcoin/node/protocols/protocols.hpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include diff --git a/include/bitcoin/node/sessions/attach.hpp b/include/bitcoin/node/sessions/attach.hpp index f77c062e..b49f98f4 100644 --- a/include/bitcoin/node/sessions/attach.hpp +++ b/include/bitcoin/node/sessions/attach.hpp @@ -98,6 +98,7 @@ class attach channel->attach(self)->start(); channel->attach(self)->start(); channel->attach(self)->start(); + channel->attach(self)->start(); } }; diff --git a/src/full_node.cpp b/src/full_node.cpp index c6cff813..950b083f 100644 --- a/src/full_node.cpp +++ b/src/full_node.cpp @@ -173,6 +173,16 @@ void full_node::do_notify(const code& ec, chaser::chase event_, // Methods. // ---------------------------------------------------------------------------- +void full_node::pause() NOEXCEPT +{ + notify(error::success, chaser::chase::pause, {}); +} + +void full_node::resume() NOEXCEPT +{ + notify(error::success, chaser::chase::resume, {}); +} + bool full_node::is_current() const NOEXCEPT { if (is_zero(config_.node.currency_window_minutes)) @@ -207,7 +217,6 @@ chaser::event_subscriber& full_node::event_subscriber() NOEXCEPT return event_subscriber_; } -// protected const configuration& full_node::config() const NOEXCEPT { return config_; diff --git a/src/protocols/protocol_block_in_31800.cpp b/src/protocols/protocol_block_in_31800.cpp index 6a1e5ff8..d674100a 100644 --- a/src/protocols/protocol_block_in_31800.cpp +++ b/src/protocols/protocol_block_in_31800.cpp @@ -201,39 +201,60 @@ void protocol_block_in_31800::stopping(const code& ec) NOEXCEPT void protocol_block_in_31800::handle_event(const code&, chaser::chase event_, chaser::link value) NOEXCEPT { + constexpr auto minimum_for_stall_divide = 2_size; + if (stopped()) return; - // There are count blocks to download at/above the given header. if (event_ == chaser::chase::download) { + // There are count blocks to download at/above the given header. if (is_current()) { BC_ASSERT(std::holds_alternative(value)); POST(do_get_downloads, std::get(value)); } } - - // If value identifies this channel, split work and stop. else if (event_ == chaser::chase::split) { BC_ASSERT(std::holds_alternative(value)); const auto channel = std::get(value); + // If value identifies this channel, split work and stop. if (channel == identifier()) { POST(do_split, channel); } } - - // If this channel has work, split it and stop. else if (event_ == chaser::chase::stall) { - if (!map_->empty()) + // If this channel has divisible work, split it and stop. + if (map_->size() >= minimum_for_stall_divide) { POST(do_split, chaser::count_t{}); } } + else if (event_ == chaser::chase::pause) + { + // Pause local timers due to channel pause. + POST(do_pause, chaser::channel_t{}); + } + else if (event_ == chaser::chase::resume) + { + // Resume local timers due to channel resume. + POST(do_resume, chaser::channel_t{}); + } +} + +void protocol_block_in_31800::do_pause(chaser::channel_t) NOEXCEPT +{ + pause_performance(); +} + +void protocol_block_in_31800::do_resume(chaser::channel_t) NOEXCEPT +{ + if (!map_->empty()) + start_performance(); } void protocol_block_in_31800::do_get_downloads(chaser::count_t) NOEXCEPT diff --git a/src/protocols/protocol_observer.cpp b/src/protocols/protocol_observer.cpp new file mode 100644 index 00000000..215103d9 --- /dev/null +++ b/src/protocols/protocol_observer.cpp @@ -0,0 +1,73 @@ +/** + * Copyright (c) 2011-2023 libbitcoin developers (see AUTHORS) + * + * This file is part of libbitcoin. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +#include + +#include +#include +#include +#include + +namespace libbitcoin { +namespace node { + +#define CLASS protocol_observer + +using namespace std::placeholders; + +void protocol_observer::start() NOEXCEPT +{ + BC_ASSERT(stranded()); + + if (started()) + return; + + // Events subscription is asynchronous. + async_subscribe_events(BIND(handle_event, _1, _2, _3)); + + protocol::start(); +} + +void protocol_observer::handle_event(const code&, + chaser::chase event_, chaser::link) NOEXCEPT +{ + if (stopped()) + return; + + if (event_ == chaser::chase::pause) + { + POST(do_pause, chaser::channel_t{}); + } + else if (event_ == chaser::chase::resume) + { + POST(do_resume, chaser::channel_t{}); + } +} + +void protocol_observer::do_pause(chaser::channel_t) NOEXCEPT +{ + pause(); +} + +void protocol_observer::do_resume(chaser::channel_t) NOEXCEPT +{ + resume(); +} + +} // namespace node +} // namespace libbitcoin diff --git a/src/sessions/session_outbound.cpp b/src/sessions/session_outbound.cpp index 82006773..08e3903e 100644 --- a/src/sessions/session_outbound.cpp +++ b/src/sessions/session_outbound.cpp @@ -130,7 +130,7 @@ void session_outbound::do_performance(uint64_t channel, uint64_t speed, BC_ASSERT(stranded()); // Three elements are required to measure deviation, don't drop to two. - constexpr auto mimimum_for_deviation = 3_size; + constexpr auto minimum_for_standard_deviation = 3_size; if (speed == max_uint64) { @@ -150,7 +150,7 @@ void session_outbound::do_performance(uint64_t channel, uint64_t speed, speeds_[channel] = static_cast(speed); const auto count = speeds_.size(); - if (count <= mimimum_for_deviation) + if (count <= minimum_for_standard_deviation) { handler(error::success); return;