From d67bddd72eefefd0b396dbb970fd16786c931059 Mon Sep 17 00:00:00 2001 From: isabelsavannah Date: Tue, 22 Oct 2019 10:44:33 -0700 Subject: [PATCH] KEP-1593: Replace dynamic peering with simpler approach (#362) * KEP-1593: Replace dynamic peering with simpler approach --- CMakeLists.txt | 2 +- bootstrap/CMakeLists.txt | 12 - bootstrap/bootstrap_peers.cpp | 180 ----- bootstrap/bootstrap_peers.hpp | 49 -- bootstrap/bootstrap_peers_base.hpp | 70 -- bootstrap/test/CMakeLists.txt | 4 - bootstrap/test/bootstrap_test.cpp | 160 ---- crud/crud.cpp | 3 +- crud/test/CMakeLists.txt | 2 +- crud/test/crud_test.cpp | 63 +- mocks/CMakeLists.txt | 2 + mocks/mock_pbft_base.hpp | 10 +- .../mock_peers_beacon_base.hpp | 32 +- mocks/mock_utils_interface.hpp | 21 + mocks/smart_mock_io.cpp | 6 + mocks/smart_mock_io.hpp | 2 + mocks/smart_mock_peers_beacon.cpp | 63 ++ .../smart_mock_peers_beacon.hpp | 16 +- options/simple_options.cpp | 6 + options/simple_options.hpp | 2 + pbft/CMakeLists.txt | 6 - pbft/operations/pbft_memory_operation.cpp | 28 +- pbft/operations/pbft_memory_operation.hpp | 13 +- pbft/operations/pbft_operation.hpp | 19 +- pbft/operations/pbft_operation_manager.cpp | 16 +- pbft/operations/pbft_operation_manager.hpp | 11 +- pbft/operations/pbft_persistent_operation.cpp | 29 +- pbft/operations/pbft_persistent_operation.hpp | 8 +- pbft/operations/test/CMakeLists.txt | 2 +- .../test/pbft_operation_manager_test.cpp | 49 +- .../test/pbft_operation_test_common.cpp | 20 +- .../test/pbft_persistent_operation_test.cpp | 61 +- pbft/pbft.cpp | 567 +++---------- pbft/pbft.hpp | 39 +- pbft/pbft_base.hpp | 10 +- pbft/pbft_checkpoint_manager.cpp | 8 +- pbft/pbft_checkpoint_manager.hpp | 6 +- pbft/pbft_config_store.cpp | 245 ------ pbft/pbft_config_store.hpp | 99 --- pbft/pbft_configuration.cpp | 270 ------- pbft/pbft_configuration.hpp | 77 -- pbft/test/CMakeLists.txt | 8 +- pbft/test/database_pbft_service_test.cpp | 12 +- pbft/test/pbft_catchup_test.cpp | 8 +- pbft/test/pbft_checkpoint_tests.cpp | 17 +- pbft/test/pbft_config_store_test.cpp | 165 ---- pbft/test/pbft_configuration_test.cpp | 137 ---- pbft/test/pbft_join_leave_test.cpp | 746 ------------------ pbft/test/pbft_newview_test.cpp | 9 +- pbft/test/pbft_peer_change_test.cpp | 252 ++++++ pbft/test/pbft_proto_test.cpp | 4 +- pbft/test/pbft_test.cpp | 6 +- pbft/test/pbft_test_common.cpp | 14 +- pbft/test/pbft_test_common.hpp | 9 +- pbft/test/pbft_viewchange_test.cpp | 6 +- peers_beacon/CMakeLists.txt | 13 + {bootstrap => peers_beacon}/peer_address.hpp | 19 +- peers_beacon/peers_beacon.cpp | 313 ++++++++ peers_beacon/peers_beacon.hpp | 72 ++ peers_beacon/peers_beacon_base.hpp | 41 + peers_beacon/test/CMakeLists.txt | 4 + peers_beacon/test/peers_beacon_tests.cpp | 237 ++++++ swarm/CMakeLists.txt | 2 +- swarm/main.cpp | 56 +- utils/CMakeLists.txt | 7 +- utils/blacklist.cpp | 6 +- utils/esr_peer_info.cpp | 65 +- utils/http_req.cpp | 22 +- utils/make_endpoint.hpp | 2 +- utils/test/utils_test.cpp | 26 +- utils/utils_interface.hpp | 31 + utils/utils_interface_base.hpp | 36 + 72 files changed, 1609 insertions(+), 3024 deletions(-) delete mode 100644 bootstrap/CMakeLists.txt delete mode 100644 bootstrap/bootstrap_peers.cpp delete mode 100644 bootstrap/bootstrap_peers.hpp delete mode 100644 bootstrap/bootstrap_peers_base.hpp delete mode 100644 bootstrap/test/CMakeLists.txt delete mode 100644 bootstrap/test/bootstrap_test.cpp rename utils/esr_peer_info.hpp => mocks/mock_peers_beacon_base.hpp (53%) create mode 100644 mocks/mock_utils_interface.hpp create mode 100644 mocks/smart_mock_peers_beacon.cpp rename utils/http_req.hpp => mocks/smart_mock_peers_beacon.hpp (60%) delete mode 100644 pbft/pbft_config_store.cpp delete mode 100644 pbft/pbft_config_store.hpp delete mode 100644 pbft/pbft_configuration.cpp delete mode 100644 pbft/pbft_configuration.hpp delete mode 100644 pbft/test/pbft_config_store_test.cpp delete mode 100644 pbft/test/pbft_configuration_test.cpp delete mode 100644 pbft/test/pbft_join_leave_test.cpp create mode 100644 pbft/test/pbft_peer_change_test.cpp create mode 100644 peers_beacon/CMakeLists.txt rename {bootstrap => peers_beacon}/peer_address.hpp (77%) create mode 100644 peers_beacon/peers_beacon.cpp create mode 100644 peers_beacon/peers_beacon.hpp create mode 100644 peers_beacon/peers_beacon_base.hpp create mode 100644 peers_beacon/test/CMakeLists.txt create mode 100644 peers_beacon/test/peers_beacon_tests.cpp create mode 100644 utils/utils_interface.hpp create mode 100644 utils/utils_interface_base.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index ec57f499..cbc3d15d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -83,7 +83,7 @@ add_subdirectory(proto) set(BLUZELLE_STD_INCLUDES ${Boost_INCLUDE_DIRS} ${GTEST_INCLUDE_DIR} ${JSONCPP_INCLUDE_DIRS} ${OPENSSL_INCLUDE_DIR} ${PROTO_INCLUDE_DIRS} ${ROCKSDB_INCLUDE_DIRS}) -add_subdirectory(bootstrap) +add_subdirectory(peers_beacon) add_subdirectory(crud) add_subdirectory(node) add_subdirectory(options) diff --git a/bootstrap/CMakeLists.txt b/bootstrap/CMakeLists.txt deleted file mode 100644 index d53eb625..00000000 --- a/bootstrap/CMakeLists.txt +++ /dev/null @@ -1,12 +0,0 @@ -add_library(bootstrap STATIC - peer_address.hpp - bootstrap_peers_base.hpp - bootstrap_peers.cpp - bootstrap_peers.hpp - ) - -target_link_libraries(bootstrap utils) -target_include_directories(bootstrap PRIVATE ${BLUZELLE_STD_INCLUDES}) -add_dependencies(bootstrap boost openssl) - -add_subdirectory(test) diff --git a/bootstrap/bootstrap_peers.cpp b/bootstrap/bootstrap_peers.cpp deleted file mode 100644 index ab4dd5fd..00000000 --- a/bootstrap/bootstrap_peers.cpp +++ /dev/null @@ -1,180 +0,0 @@ -// Copyright (C) 2018 Bluzelle -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License, version 3, -// as published by the Free Software Foundation. -// -// 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 -#include -#include -#include -#include -#include - -using namespace bzn; - - -bool -bootstrap_peers::fetch_peers_from_file(const std::string& filename) -{ - std::ifstream file(filename); - if (file.fail()) - { - LOG(error) << "Failed to read bootstrap peers file " << filename; - return false; - } - - LOG(info) << "Reading peers from " << filename; - - return ingest_json(file); -} - - -bool -bootstrap_peers::fetch_peers_from_url(const std::string& url) -{ - std::string peers = bzn::utils::http::sync_req(url); - - LOG(info) << "Downloaded peer list from " << url; - - std::stringstream stream; - stream << peers; - - return ingest_json(stream); -} - - -bool -bootstrap_peers::fetch_peers_from_esr_contract(const std::string& esr_url, const std::string& esr_address, const bzn::uuid_t& swarm_id) -{ - auto peer_ids = bzn::utils::esr::get_peer_ids(swarm_id, esr_address, esr_url); - for (const auto& peer_id : peer_ids) - { - bzn::peer_address_t peer_info{bzn::utils::esr::get_peer_info(swarm_id, peer_id, esr_address, esr_url)}; - if (peer_info.host.empty() - || peer_info.port == 0 - //|| peer_info.name.empty() // is it important that a peer have a name? - || peer_info.uuid.empty() - ) - { - LOG(warning) << "Invalid peer information found in esr contract, ignoring info for peer: " << peer_id << " in swarm: " << swarm_id; - } - else - { - this->peer_addresses.emplace(peer_info); - } - } - return true; -} - - -const bzn::peers_list_t& -bootstrap_peers::get_peers() const -{ - return this->peer_addresses; -} - - -size_t -bootstrap_peers::initialize_peer_list(const Json::Value& root, bzn::peers_list_t& /*peer_addresses*/) -{ - size_t valid_addresses_read = 0; - // Expect the read json to be an array of peer objects - for (const auto& peer : root) - { - std::string host; - std::string name; - std::string uuid; - uint16_t port; - - try - { - host = peer["host"].asString(); - port = peer["port"].asUInt(); - uuid = peer.isMember("uuid") ? peer["uuid"].asString() : "unknown"; - name = peer.isMember("name") ? peer["name"].asString() : "unknown"; - } - catch(std::exception& e) - { - LOG(warning) << "Ignoring malformed peer specification " << peer; - continue; - } - - // port wasn't actually a 16 bit uint - if (peer["port"].asUInt() != port) - { - LOG(warning) << "Ignoring peer with bad port " << peer; - continue; - } - - // peer didn't contain everything we need - if (host.empty() || port == 0) - { - LOG(warning) << "Ignoring underspecified peer (needs host and port) " << peer; - continue; - } - - if (this->is_peer_validation_enabled()) - { - // At this point we cannot validate uuids as we do not have - // signatures for all of them, so we simply ignore blacklisted - // uuids - if (bzn::utils::blacklist::is_blacklisted(uuid)) - { - LOG(warning) << "Ignoring blacklisted node with uuid: [" << uuid << "]"; - continue; - } - } - - this->peer_addresses.emplace(host, port, name, uuid); - - LOG(trace) << "Found peer " << host << ":" << port << " (" << name << ")"; - - valid_addresses_read++; - } - return valid_addresses_read; -} - - -bool -bootstrap_peers::ingest_json(std::istream& peers) -{ - size_t addresses_before = this->peer_addresses.size(); - Json::Value root; - - try - { - peers >> root; - } - catch (const std::exception& e) - { - LOG(error) << "Failed to parse peer JSON (" << e.what() << ")"; - return false; - } - - size_t valid_addresses_read = this->initialize_peer_list(root, this->peer_addresses); - - size_t new_addresses = this->peer_addresses.size() - addresses_before; - size_t duplicate_addresses = new_addresses - valid_addresses_read; - - LOG(info) << "Found " << new_addresses << " new peers"; - - if (duplicate_addresses > 0) - { - LOG(info) << "Ignored " << duplicate_addresses << " duplicate addresses"; - } - - return new_addresses > 0; -} diff --git a/bootstrap/bootstrap_peers.hpp b/bootstrap/bootstrap_peers.hpp deleted file mode 100644 index cc7355ca..00000000 --- a/bootstrap/bootstrap_peers.hpp +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (C) 2018 Bluzelle -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License, version 3, -// as published by the Free Software Foundation. -// -// 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 . - -#pragma once - -#include -#include - - -namespace bzn -{ - - class bootstrap_peers final : public bzn::bootstrap_peers_base - { - public: - explicit bootstrap_peers(bool peer_validation_enabled=false) : peer_validation_enabled(peer_validation_enabled) {} - - bool fetch_peers_from_file(const std::string& filename) override; - - bool fetch_peers_from_url(const std::string& url) override; - - bool fetch_peers_from_esr_contract(const std::string& esr_url, const std::string& esr_address, const bzn::uuid_t& swarm_id) override; - - const bzn::peers_list_t& get_peers() const override; - - private: - bool ingest_json(std::istream& peers); - - bzn::peers_list_t peer_addresses; - - size_t initialize_peer_list(const Json::Value& root, bzn::peers_list_t& peer_addresses); - - bool is_peer_validation_enabled() { return this->peer_validation_enabled; } - - bool peer_validation_enabled{false}; - }; - -} // namespace bzn diff --git a/bootstrap/bootstrap_peers_base.hpp b/bootstrap/bootstrap_peers_base.hpp deleted file mode 100644 index a719971b..00000000 --- a/bootstrap/bootstrap_peers_base.hpp +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (C) 2018 Bluzelle -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License, version 3, -// as published by the Free Software Foundation. -// -// 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 . - -#pragma once - -#include -#include -#include - - -namespace std -{ - template<> struct hash - { - size_t operator()(const bzn::peer_address_t& x) const noexcept - { - return std::hash()(std::string{x.host} + std::to_string(x.port)); - } - }; -} - -namespace bzn -{ - using peers_list_t = std::unordered_set; - - class bootstrap_peers_base - { - public: - - /** - * Read and parse peers specified in a local file (existing peers will - * not be overwritten). - * @return if at least one peer fetched successfully - */ - virtual bool fetch_peers_from_file(const std::string& filename) = 0; - - /** - * Fetch and parse peers specified in json from some url (existing - * peers will not be overwritten). - * @return if at least one peer fetched successfully - */ - virtual bool fetch_peers_from_url(const std::string& url) = 0; - - /** - * Given a swarm id, fetch the peer info for the peers in that swarm - * @param esr_url a string containing the url of the Etherium contract server - * @param esr_address a string containing the Etherium address of the get peer info contract - * @param swarm_id a string containing the unique identifier of the swarm containing the peers of interest - * @return true - note that it is possible that the contract does not return any peers - */ - virtual bool fetch_peers_from_esr_contract(const std::string& esr_url, const std::string& esr_address, const bzn::uuid_t& swarm_id) = 0; - - /** - * @return a reference to the initial set of peers - */ - virtual const peers_list_t& get_peers() const = 0; - }; - -} // namespace bzn diff --git a/bootstrap/test/CMakeLists.txt b/bootstrap/test/CMakeLists.txt deleted file mode 100644 index 4b0e2ce1..00000000 --- a/bootstrap/test/CMakeLists.txt +++ /dev/null @@ -1,4 +0,0 @@ -set(test_srcs bootstrap_test.cpp) -set(test_libs bootstrap) - -add_gmock_test(bootstrap) diff --git a/bootstrap/test/bootstrap_test.cpp b/bootstrap/test/bootstrap_test.cpp deleted file mode 100644 index 2abec397..00000000 --- a/bootstrap/test/bootstrap_test.cpp +++ /dev/null @@ -1,160 +0,0 @@ -// Copyright (C) 2018 Bluzelle -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License, version 3, -// as published by the Free Software Foundation. -// -// 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 -{ - const std::string invalid_json = "[{\"Some key\": 124}, }}[} oh noes I broke it"; - const std::string no_peers = "[]"; - const std::string valid_peers = "[{\"name\": \"peer1\", \"host\": \"peer1.com\", \"port\": 12345, \"http_port\" : 8080}, {\"host\": \"nonamepeer.com\", \"port\": 54321}]"; - const std::string duplicate_peers = "[{\"name\": \"peer1\", \"host\": \"peer1.com\", \"port\": 12345, \"http_port\" : 8080}, {\"name\": \"peer1\", \"host\": \"peer1.com\", \"port\": 12345}]"; - const std::string underspecified_peer = "[{\"name\": \"peer1\", \"port\": 1024}]"; - const std::string bad_port = "[{\"name\": \"peer1\", \"host\": \"127.0.0.1\", \"port\": 70000}]"; - const std::string test_peers_filename = "peers.json"; - - const std::string sample_peers_url = "pastebin.com/raw/KvbcVhfZ"; - const std::string sample_peers_url_with_protocol = "http://www.pastebin.com/raw/KvbcVhfZ"; -} - -using namespace ::testing; - - -class bootstrap_file_test : public Test -{ -public: - bzn::bootstrap_peers bootstrap_peers; - - void set_peers_data(const std::string& peers_data) - { - std::ofstream ofile(test_peers_filename); - ofile << peers_data; - ofile.close(); - } - - ~bootstrap_file_test() - { - unlink(test_peers_filename.c_str()); - } -}; - - -TEST_F(bootstrap_file_test, test_invalid_json) -{ - set_peers_data(invalid_json); - ASSERT_FALSE(bootstrap_peers.fetch_peers_from_file(test_peers_filename)); - ASSERT_TRUE(bootstrap_peers.get_peers().empty()); -} - - -TEST_F(bootstrap_file_test, test_no_peers) -{ - set_peers_data(no_peers); - ASSERT_FALSE(bootstrap_peers.fetch_peers_from_file(test_peers_filename)); - ASSERT_TRUE(bootstrap_peers.get_peers().empty()); -} - - -TEST_F(bootstrap_file_test, test_underspecified_peer) -{ - set_peers_data(underspecified_peer); - ASSERT_FALSE(bootstrap_peers.fetch_peers_from_file(test_peers_filename)); - ASSERT_TRUE(bootstrap_peers.get_peers().empty()); -} - - -TEST_F(bootstrap_file_test, test_bad_port) -{ - set_peers_data(bad_port); - ASSERT_FALSE(bootstrap_peers.fetch_peers_from_file(test_peers_filename)); - ASSERT_TRUE(bootstrap_peers.get_peers().empty()); -} - - -TEST_F(bootstrap_file_test, test_valid_peers) -{ - set_peers_data(valid_peers); - ASSERT_TRUE(bootstrap_peers.fetch_peers_from_file(test_peers_filename)); - ASSERT_EQ(bootstrap_peers.get_peers().size(), 2U); - - bool seen_peer1 = false; - bool seen_peer2 = false; - - for (const bzn::peer_address_t& p : bootstrap_peers.get_peers()) - { - if (p.port == 12345) seen_peer1 = true; - if (p.port == 54321) seen_peer2 = true; - } - - ASSERT_TRUE(seen_peer1 && seen_peer2); -} - - -TEST_F(bootstrap_file_test, test_unnamed_peers) -{ - set_peers_data(valid_peers); - ASSERT_TRUE(bootstrap_peers.fetch_peers_from_file(test_peers_filename)); - ASSERT_EQ(bootstrap_peers.get_peers().size(), 2U); - - bool seen_name1 = false; - bool seen_name2 = false; - - for (const bzn::peer_address_t& p : bootstrap_peers.get_peers()) - { - if (p.name == "peer1") seen_name1 = true; - if (p.name == "unknown") seen_name2 = true; - } - - ASSERT_TRUE(seen_name1 && seen_name2); -} - - -TEST_F(bootstrap_file_test, test_duplicate_peers) -{ - set_peers_data(duplicate_peers); - ASSERT_TRUE(bootstrap_peers.fetch_peers_from_file(test_peers_filename)); - ASSERT_EQ(bootstrap_peers.get_peers().size(), 1U); -} - -// flaky tests... - -TEST(bootstrap_net_test, DISABLED_test_fetch_data) -{ - bzn::bootstrap_peers bootstrap_peers; - // I don't like this test dependancy, but mocking out the http stuff - // would mean mocking out all the meaningful code that this touches - ASSERT_TRUE(bootstrap_peers.fetch_peers_from_url(sample_peers_url)); - ASSERT_EQ(bootstrap_peers.get_peers().size(), 1U); -} - - -TEST(bootstrap_net_test, DISABLED_test_fetch_data_with_protocol) -{ - bzn::bootstrap_peers bootstrap_peers; - ASSERT_TRUE(bootstrap_peers.fetch_peers_from_url(sample_peers_url_with_protocol)); - ASSERT_EQ(bootstrap_peers.get_peers().size(), 1U); -} - - -TEST(bootstrap_net_test, DISABLED_test_fetch_peers_from_solidity) -{ - bzn::bootstrap_peers bootstrap_peers; - bzn::uuid_t swarm_id{"BluzelleSwarm"}; - ASSERT_TRUE(bootstrap_peers.fetch_peers_from_esr_contract(bzn::utils::ROPSTEN_URL, bzn::utils::DEFAULT_SWARM_INFO_ESR_ADDRESS, swarm_id)); - ASSERT_EQ(bootstrap_peers.get_peers().size(), 7U); -} diff --git a/crud/crud.cpp b/crud/crud.cpp index 685d4e80..1261d1d6 100644 --- a/crud/crud.cpp +++ b/crud/crud.cpp @@ -981,7 +981,8 @@ crud::is_caller_a_writer(const bzn::caller_id_t& caller_id, const Json::Value& p } // A node may be issuing an operation such as delete for key expiration... - for (const auto& peer_uuid : *this->pbft->current_peers_ptr()) + // TODO: this may need to compare against all recent peers, not just current ones + for (const auto& peer_uuid : *this->pbft->peers()->current()) { if (peer_uuid.uuid == boost::trim_copy(caller_id)) { diff --git a/crud/test/CMakeLists.txt b/crud/test/CMakeLists.txt index f7be00bc..f18f7052 100644 --- a/crud/test/CMakeLists.txt +++ b/crud/test/CMakeLists.txt @@ -1,4 +1,4 @@ set(test_srcs crud_test.cpp subscription_manager_test.cpp) -set(test_libs crud pbft pbft_operations node storage bootstrap proto ${Protobuf_LIBRARIES}) +set(test_libs crud pbft pbft_operations node storage peers_beacon proto smart_mocks ${Protobuf_LIBRARIES}) add_gmock_test(crud) diff --git a/crud/test/crud_test.cpp b/crud/test/crud_test.cpp index 2cc74fae..feb72422 100644 --- a/crud/test/crud_test.cpp +++ b/crud/test/crud_test.cpp @@ -27,6 +27,7 @@ #include #include #include +#include using namespace ::testing; @@ -143,7 +144,7 @@ namespace auto mock_pbft = std::make_shared(); EXPECT_CALL(*mock_pbft, - current_peers_ptr()).WillRepeatedly(Return(std::make_shared>())); + peers()).WillRepeatedly(Return(bzn::static_empty_peers_beacon())); crud->start(mock_pbft); return crud; } @@ -441,7 +442,7 @@ TEST(crud, test_that_create_sends_proper_response) auto mock_pbft = std::make_shared(); - EXPECT_CALL(*mock_pbft, current_peers_ptr()).WillRepeatedly(Return(std::make_shared>())); + EXPECT_CALL(*mock_pbft, peers()).WillRepeatedly(Return(bzn::static_empty_peers_beacon())); crud->start(mock_pbft); @@ -547,7 +548,7 @@ TEST(crud, test_that_point_of_contact_create_sends_proper_response) auto mock_pbft = std::make_shared(); - EXPECT_CALL(*mock_pbft, current_peers_ptr()).WillRepeatedly(Return(std::make_shared>())); + EXPECT_CALL(*mock_pbft, peers()).WillRepeatedly(Return(bzn::static_empty_peers_beacon())); crud->start(mock_pbft); @@ -686,7 +687,7 @@ TEST(crud, test_that_read_sends_proper_response) auto mock_pbft = std::make_shared(); - EXPECT_CALL(*mock_pbft, current_peers_ptr()).WillRepeatedly(Return(std::make_shared>())); + EXPECT_CALL(*mock_pbft, peers()).WillRepeatedly(Return(bzn::static_empty_peers_beacon())); crud->start(mock_pbft); @@ -805,7 +806,7 @@ TEST(crud, test_that_point_of_contact_read_sends_proper_response) auto mock_pbft = std::make_shared(); - EXPECT_CALL(*mock_pbft, current_peers_ptr()).WillRepeatedly(Return(std::make_shared>())); + EXPECT_CALL(*mock_pbft, peers()).WillRepeatedly(Return(bzn::static_empty_peers_beacon())); crud->start(mock_pbft); @@ -921,7 +922,7 @@ TEST(crud, test_that_update_sends_proper_response) auto mock_pbft = std::make_shared(); - EXPECT_CALL(*mock_pbft, current_peers_ptr()).WillRepeatedly(Return(std::make_shared>())); + EXPECT_CALL(*mock_pbft, peers()).WillRepeatedly(Return(bzn::static_empty_peers_beacon())); EXPECT_CALL(*mock_subscription_manager, start()); @@ -1006,7 +1007,7 @@ TEST(crud, test_that_point_of_contact_update_sends_proper_response) auto mock_pbft = std::make_shared(); - EXPECT_CALL(*mock_pbft, current_peers_ptr()).WillRepeatedly(Return(std::make_shared>())); + EXPECT_CALL(*mock_pbft, peers()).WillRepeatedly(Return(bzn::static_empty_peers_beacon())); EXPECT_CALL(*mock_subscription_manager, start()); @@ -1094,7 +1095,7 @@ TEST(crud, test_that_delete_sends_proper_response) auto mock_pbft = std::make_shared(); - EXPECT_CALL(*mock_pbft, current_peers_ptr()).WillRepeatedly(Return(std::make_shared>())); + EXPECT_CALL(*mock_pbft, peers()).WillRepeatedly(Return(bzn::static_empty_peers_beacon())); EXPECT_CALL(*mock_subscription_manager, start()); @@ -1156,7 +1157,7 @@ TEST(crud, test_that_point_of_contact_delete_sends_proper_response) auto mock_pbft = std::make_shared(); - EXPECT_CALL(*mock_pbft, current_peers_ptr()).WillRepeatedly(Return(std::make_shared>())); + EXPECT_CALL(*mock_pbft, peers()).WillRepeatedly(Return(bzn::static_empty_peers_beacon())); EXPECT_CALL(*mock_subscription_manager, start()); @@ -1235,7 +1236,7 @@ TEST(crud, test_that_has_sends_proper_response) auto mock_pbft = std::make_shared(); - EXPECT_CALL(*mock_pbft, current_peers_ptr()).WillRepeatedly(Return(std::make_shared>())); + EXPECT_CALL(*mock_pbft, peers()).WillRepeatedly(Return(bzn::static_empty_peers_beacon())); crud->start(mock_pbft); @@ -1316,7 +1317,7 @@ TEST(crud, test_that_point_of_contact_has_sends_proper_response) auto mock_pbft = std::make_shared(); - EXPECT_CALL(*mock_pbft, current_peers_ptr()).WillRepeatedly(Return(std::make_shared>())); + EXPECT_CALL(*mock_pbft, peers()).WillRepeatedly(Return(bzn::static_empty_peers_beacon())); crud->start(mock_pbft); @@ -1396,7 +1397,7 @@ TEST(crud, test_that_keys_sends_proper_response) auto mock_pbft = std::make_shared(); - EXPECT_CALL(*mock_pbft, current_peers_ptr()).WillRepeatedly(Return(std::make_shared>())); + EXPECT_CALL(*mock_pbft, peers()).WillRepeatedly(Return(bzn::static_empty_peers_beacon())); crud->start(mock_pbft); @@ -1473,7 +1474,7 @@ TEST(crud, test_that_point_of_contact_keys_sends_proper_response) auto mock_pbft = std::make_shared(); - EXPECT_CALL(*mock_pbft, current_peers_ptr()).WillRepeatedly(Return(std::make_shared>())); + EXPECT_CALL(*mock_pbft, peers()).WillRepeatedly(Return(bzn::static_empty_peers_beacon())); crud->start(mock_pbft); @@ -1563,7 +1564,7 @@ TEST(crud, test_that_size_sends_proper_response) auto mock_pbft = std::make_shared(); - EXPECT_CALL(*mock_pbft, current_peers_ptr()).WillRepeatedly(Return(std::make_shared>())); + EXPECT_CALL(*mock_pbft, peers()).WillRepeatedly(Return(bzn::static_empty_peers_beacon())); crud->start(mock_pbft); @@ -1629,7 +1630,7 @@ TEST(crud, test_that_point_of_contact_size_sends_proper_response) auto mock_pbft = std::make_shared(); - EXPECT_CALL(*mock_pbft, current_peers_ptr()).WillRepeatedly(Return(std::make_shared>())); + EXPECT_CALL(*mock_pbft, peers()).WillRepeatedly(Return(bzn::static_empty_peers_beacon())); crud->start(mock_pbft); @@ -1710,7 +1711,7 @@ TEST(crud, test_that_subscribe_request_calls_subscription_manager) auto mock_pbft = std::make_shared(); - EXPECT_CALL(*mock_pbft, current_peers_ptr()).WillRepeatedly(Return(std::make_shared>())); + EXPECT_CALL(*mock_pbft, peers()).WillRepeatedly(Return(bzn::static_empty_peers_beacon())); crud->start(mock_pbft); @@ -1753,7 +1754,7 @@ TEST(crud, test_that_unsubscribe_request_calls_subscription_manager) auto mock_pbft = std::make_shared(); - EXPECT_CALL(*mock_pbft, current_peers_ptr()).WillRepeatedly(Return(std::make_shared>())); + EXPECT_CALL(*mock_pbft, peers()).WillRepeatedly(Return(bzn::static_empty_peers_beacon())); crud->start(mock_pbft); @@ -1794,7 +1795,7 @@ TEST(crud, test_that_create_db_request_sends_proper_response) auto mock_pbft = std::make_shared(); - EXPECT_CALL(*mock_pbft, current_peers_ptr()).WillRepeatedly(Return(std::make_shared>())); + EXPECT_CALL(*mock_pbft, peers()).WillRepeatedly(Return(bzn::static_empty_peers_beacon())); crud->start(mock_pbft); @@ -1833,7 +1834,7 @@ TEST(crud, test_that_point_of_contact_create_db_request_sends_proper_response) auto mock_pbft = std::make_shared(); - EXPECT_CALL(*mock_pbft, current_peers_ptr()).WillRepeatedly(Return(std::make_shared>())); + EXPECT_CALL(*mock_pbft, peers()).WillRepeatedly(Return(bzn::static_empty_peers_beacon())); crud->start(mock_pbft); @@ -1888,7 +1889,7 @@ TEST(crud, test_that_has_db_request_sends_proper_response) auto mock_pbft = std::make_shared(); - EXPECT_CALL(*mock_pbft, current_peers_ptr()).WillRepeatedly(Return(std::make_shared>())); + EXPECT_CALL(*mock_pbft, peers()).WillRepeatedly(Return(bzn::static_empty_peers_beacon())); crud->start(mock_pbft); @@ -1945,7 +1946,7 @@ TEST(crud, test_that_point_of_contact_has_db_request_sends_proper_response) auto mock_pbft = std::make_shared(); - EXPECT_CALL(*mock_pbft, current_peers_ptr()).WillRepeatedly(Return(std::make_shared>())); + EXPECT_CALL(*mock_pbft, peers()).WillRepeatedly(Return(bzn::static_empty_peers_beacon())); crud->start(mock_pbft); @@ -2010,7 +2011,7 @@ TEST(crud, test_that_delete_db_sends_proper_response) auto mock_pbft = std::make_shared(); - EXPECT_CALL(*mock_pbft, current_peers_ptr()).WillOnce(Return(std::make_shared>())); + EXPECT_CALL(*mock_pbft, peers()).WillRepeatedly(Return(bzn::static_empty_peers_beacon())); crud->start(mock_pbft); @@ -2077,7 +2078,7 @@ TEST(crud, test_that_point_of_contact_delete_db_sends_proper_response) auto mock_pbft = std::make_shared(); - EXPECT_CALL(*mock_pbft, current_peers_ptr()).WillRepeatedly(Return(std::make_shared>())); + EXPECT_CALL(*mock_pbft, peers()).WillRepeatedly(Return(bzn::static_empty_peers_beacon())); crud->start(mock_pbft); @@ -2470,7 +2471,7 @@ TEST(crud, test_that_key_with_expire_set_is_deleted_by_timer_callback) auto mock_pbft = std::make_shared(); - EXPECT_CALL(*mock_pbft, current_peers_ptr()).WillRepeatedly(Return(std::make_shared>())); + EXPECT_CALL(*mock_pbft, peers()).WillRepeatedly(Return(bzn::static_empty_peers_beacon())); bzn::uuid_t node_uuid{"node-uuid"}; EXPECT_CALL(*mock_pbft, get_uuid()).WillOnce(ReturnRef(node_uuid)); @@ -2533,7 +2534,7 @@ TEST(crud, test_that_key_with_expiration_can_be_made_persistent) auto mock_pbft = std::make_shared(); - EXPECT_CALL(*mock_pbft, current_peers_ptr()).WillRepeatedly(Return(std::make_shared>())); + EXPECT_CALL(*mock_pbft, peers()).WillRepeatedly(Return(bzn::static_empty_peers_beacon())); crud->start(mock_pbft); @@ -2591,7 +2592,7 @@ TEST(crud, test_that_create_db_uses_bluzelle_key_to_validate) auto mock_pbft = std::make_shared(); - EXPECT_CALL(*mock_pbft, current_peers_ptr()).WillRepeatedly(Return(std::make_shared>())); + EXPECT_CALL(*mock_pbft, peers()).WillRepeatedly(Return(bzn::static_empty_peers_beacon())); crud->start(mock_pbft); @@ -2626,7 +2627,7 @@ TEST(crud, test_that_create_db_with_incorrect_bluzelle_key_fails_to_validate) auto mock_pbft = std::make_shared(); - EXPECT_CALL(*mock_pbft, current_peers_ptr()).WillRepeatedly(Return(std::make_shared>())); + EXPECT_CALL(*mock_pbft, peers()).WillRepeatedly(Return(bzn::static_empty_peers_beacon())); crud->start(mock_pbft); @@ -2661,7 +2662,7 @@ TEST(crud, test_that_delete_db_uses_bluzelle_key_to_validate) auto mock_pbft = std::make_shared(); - EXPECT_CALL(*mock_pbft, current_peers_ptr()).WillRepeatedly(Return(std::make_shared>())); + EXPECT_CALL(*mock_pbft, peers()).WillRepeatedly(Return(bzn::static_empty_peers_beacon())); crud->start(mock_pbft); @@ -2717,7 +2718,7 @@ TEST(crud, test_that_delete_db_with_incorrect_bluzelle_key_fails_to_validate) auto mock_pbft = std::make_shared(); - EXPECT_CALL(*mock_pbft, current_peers_ptr()).WillRepeatedly(Return(std::make_shared>())); + EXPECT_CALL(*mock_pbft, peers()).WillRepeatedly(Return(bzn::static_empty_peers_beacon())); crud->start(mock_pbft); @@ -2788,7 +2789,7 @@ TEST(crud, test_that_create_and_updates_which_exceed_db_limit_send_proper_respon auto mock_pbft = std::make_shared(); auto crud = std::make_shared(mock_io_context, std::make_shared(), mock_subscription_manager, nullptr); - EXPECT_CALL(*mock_pbft, current_peers_ptr()).WillRepeatedly(Return(std::make_shared>())); + EXPECT_CALL(*mock_pbft, peers()).WillRepeatedly(Return(bzn::static_empty_peers_beacon())); crud->start(mock_pbft); @@ -3272,7 +3273,7 @@ TEST(crud, test_that_expire_send_proper_response) auto mock_pbft = std::make_shared(); - EXPECT_CALL(*mock_pbft, current_peers_ptr()).WillRepeatedly(Return(std::make_shared>())); + EXPECT_CALL(*mock_pbft, peers()).WillRepeatedly(Return(bzn::static_empty_peers_beacon())); crud->start(mock_pbft); diff --git a/mocks/CMakeLists.txt b/mocks/CMakeLists.txt index 76e9aa52..78ea023b 100644 --- a/mocks/CMakeLists.txt +++ b/mocks/CMakeLists.txt @@ -3,6 +3,8 @@ add_library(smart_mocks STATIC smart_mock_node.cpp smart_mock_io.hpp smart_mock_io.cpp + smart_mock_peers_beacon.hpp + smart_mock_peers_beacon.cpp ) add_dependencies(smart_mocks boost proto googletest) diff --git a/mocks/mock_pbft_base.hpp b/mocks/mock_pbft_base.hpp index a68fa6a2..5f885f7b 100644 --- a/mocks/mock_pbft_base.hpp +++ b/mocks/mock_pbft_base.hpp @@ -32,16 +32,18 @@ class mock_pbft_base : public pbft_base { void(const bzn_envelope& msg, std::shared_ptr session)); MOCK_CONST_METHOD0(is_primary, bool()); - MOCK_CONST_METHOD1(get_primary, - const peer_address_t&(std::optional view)); + MOCK_CONST_METHOD0(get_current_primary, + std::optional()); + MOCK_CONST_METHOD1(predict_primary, + std::optional(uint64_t view)); MOCK_CONST_METHOD0(get_uuid, const bzn::uuid_t&()); MOCK_METHOD0(handle_failure, void()); MOCK_CONST_METHOD1(get_peer_by_uuid, const peer_address_t&(const std::string& uuid)); - MOCK_CONST_METHOD0(current_peers_ptr, - std::shared_ptr>()); + MOCK_CONST_METHOD0(peers, + std::shared_ptr()); }; } // namespace bzn diff --git a/utils/esr_peer_info.hpp b/mocks/mock_peers_beacon_base.hpp similarity index 53% rename from utils/esr_peer_info.hpp rename to mocks/mock_peers_beacon_base.hpp index 737c3540..3052591e 100644 --- a/utils/esr_peer_info.hpp +++ b/mocks/mock_peers_beacon_base.hpp @@ -14,11 +14,31 @@ #pragma once -#include -#include //for bzn::peer_address_t +#include +#include + + +namespace bzn { + + class mock_peers_beacon_base : public peers_beacon_base + { + public: + MOCK_METHOD0(start, + void()); + + MOCK_CONST_METHOD0(current, + std::shared_ptr()); + + MOCK_CONST_METHOD0(ordered, + std::shared_ptr()); + + MOCK_METHOD1(refresh, + bool(bool first_run)); + + bool refresh() + { + return this->refresh(false); + } + }; -namespace bzn::utils::esr -{ - std::vector get_peer_ids(const bzn::uuid_t& swarm_id, const std::string& esr_address, const std::string& url); - bzn::peer_address_t get_peer_info(const bzn::uuid_t& swarm_id, const std::string& peer_id, const std::string& esr_address, const std::string& url); } diff --git a/mocks/mock_utils_interface.hpp b/mocks/mock_utils_interface.hpp new file mode 100644 index 00000000..32a3261d --- /dev/null +++ b/mocks/mock_utils_interface.hpp @@ -0,0 +1,21 @@ +#pragma once +#include + +namespace bzn { + + class mock_utils_interface_base : public utils_interface_base { + public: + MOCK_CONST_METHOD3(get_peer_ids, + std::vector(const bzn::uuid_t& swarm_id, const std::string& esr_address, const std::string& url)); + MOCK_CONST_METHOD4(get_peer_info, + bzn::peer_address_t(const bzn::uuid_t& swarm_id, const std::string& peer_id, const std::string& esr_address, const std::string& url)); + MOCK_CONST_METHOD2(sync_req, + std::string(const std::string& url, const std::string& post)); + + std::string sync_req(const std::string& url) const + { + return this->sync_req(url, ""); + } + }; + +} // namespace bzn diff --git a/mocks/smart_mock_io.cpp b/mocks/smart_mock_io.cpp index 3e782bcf..49cc9aac 100644 --- a/mocks/smart_mock_io.cpp +++ b/mocks/smart_mock_io.cpp @@ -220,3 +220,9 @@ bzn::asio::smart_mock_io::yield_until_clear() sleep_time *= 2; } } + +void +bzn::asio::smart_mock_io::trigger_timer(unsigned int timer_id) +{ + this->timer_callbacks.at(timer_id)(boost::system::error_code{}); +} diff --git a/mocks/smart_mock_io.hpp b/mocks/smart_mock_io.hpp index c30007c4..66edf5af 100644 --- a/mocks/smart_mock_io.hpp +++ b/mocks/smart_mock_io.hpp @@ -27,6 +27,8 @@ namespace bzn::asio void yield_until_clear(); + void trigger_timer(unsigned int timer_id); + std::shared_ptr real_io_context = std::make_shared(); std::shared_ptr websocket = std::make_shared(); diff --git a/mocks/smart_mock_peers_beacon.cpp b/mocks/smart_mock_peers_beacon.cpp new file mode 100644 index 00000000..22019bb1 --- /dev/null +++ b/mocks/smart_mock_peers_beacon.cpp @@ -0,0 +1,63 @@ +// Copyright (C) 2019 Bluzelle +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License, version 3, +// as published by the Free Software Foundation. +// +// 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 + +#include + +using namespace ::testing; + +std::shared_ptr +bzn::static_peers_beacon_for(bzn::peers_list_t peers) +{ + auto res = std::make_shared(); + auto list = std::make_shared(peers); + + EXPECT_CALL(*res, start()).Times(AtMost(1)); + + EXPECT_CALL(*res, current()).WillRepeatedly(Return(list)); + + auto ordered = std::make_shared(); + std::for_each(peers.begin(), peers.end(), + [&](const auto& peer) + { + ordered->push_back(peer); + }); + + std::sort(ordered->begin(), ordered->end(), + [](const auto& peer1, const auto& peer2) + { + return peer1.uuid.compare(peer2.uuid) < 0; + }); + + EXPECT_CALL(*res, ordered()).WillRepeatedly(Return(ordered)); + + EXPECT_CALL(*res, refresh(_)).Times(AnyNumber()); + + return res; +} + +std::shared_ptr +bzn::static_peers_beacon_for(std::vector peers) +{ + bzn::peers_list_t list; + std::for_each(peers.begin(), peers.end(), + [&](const auto& peer){list.insert(peer);} + ); + + return static_peers_beacon_for(list); +} + +std::shared_ptr +bzn::static_empty_peers_beacon() +{ + return static_peers_beacon_for(std::vector{}); +} diff --git a/utils/http_req.hpp b/mocks/smart_mock_peers_beacon.hpp similarity index 60% rename from utils/http_req.hpp rename to mocks/smart_mock_peers_beacon.hpp index 688c94e3..65d4e7ad 100644 --- a/utils/http_req.hpp +++ b/mocks/smart_mock_peers_beacon.hpp @@ -1,4 +1,4 @@ - // Copyright (C) 2018 Bluzelle +// Copyright (C) 2019 Bluzelle // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License, version 3, @@ -10,16 +10,14 @@ // 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 . #pragma once -#include +#include - -namespace bzn::utils::http +namespace bzn { - // Performs an HTTP GET or POST and returns the body of the HTTP response - std::string sync_req(const std::string& url, const std::string& post = ""); - -} // namespace bzn::http + std::shared_ptr static_peers_beacon_for(peers_list_t peers); + std::shared_ptr static_peers_beacon_for(std::vector peers); + std::shared_ptr static_empty_peers_beacon(); +} diff --git a/options/simple_options.cpp b/options/simple_options.cpp index 7da92dba..132ad312 100644 --- a/options/simple_options.cpp +++ b/options/simple_options.cpp @@ -61,6 +61,9 @@ simple_options::build_options() (BOOTSTRAP_PEERS_URL.c_str(), po::value(), "url for bootstrap peers list") + (PEERS_REFRESH_INTERVAL_SECONDS.c_str(), + po::value()->default_value(600), + "interval at which to check for updates to the peers list") (LISTENER_ADDRESS.c_str(), po::value()->required(), "listener address for consensus node") @@ -100,6 +103,9 @@ simple_options::build_options() (SWARM_INFO_ESR_URL.c_str(), po::value()->default_value(bzn::utils::ROPSTEN_URL), "url of ESR Swarm Info contract server") + (IGNORE_ESR.c_str(), + po::value()->default_value(false), + "do not use esr as a peer source") (STACK.c_str(), po::value()->required(), "software stack used by swarm"); diff --git a/options/simple_options.hpp b/options/simple_options.hpp index 20f426fd..d506af23 100644 --- a/options/simple_options.hpp +++ b/options/simple_options.hpp @@ -24,6 +24,7 @@ namespace bzn::option_names { const std::string AUDIT_MEM_SIZE = "audit_mem_size"; const std::string AUDIT_ENABLED = "audit_enabled"; + const std::string PEERS_REFRESH_INTERVAL_SECONDS = "peers_refresh_interval_seconds"; const std::string BOOTSTRAP_PEERS_FILE = "bootstrap_file"; const std::string BOOTSTRAP_PEERS_URL = "bootstrap_url"; const std::string DEBUG_LOGGING = "debug_logging"; @@ -67,6 +68,7 @@ namespace bzn::option_names const std::string SWARM_INFO_ESR_ADDRESS = "swarm_info_esr_address"; const std::string SWARM_INFO_ESR_URL = "swarm_info_esr_url"; + const std::string IGNORE_ESR = "ignore_esr"; const std::string STACK = "stack"; diff --git a/pbft/CMakeLists.txt b/pbft/CMakeLists.txt index 0e21625a..a79ee1f8 100644 --- a/pbft/CMakeLists.txt +++ b/pbft/CMakeLists.txt @@ -2,18 +2,12 @@ add_library(pbft STATIC pbft_base.hpp pbft.hpp pbft.cpp - pbft_configuration.hpp - pbft_configuration.cpp dummy_pbft_service.cpp dummy_pbft_service.hpp pbft_service_base.hpp pbft_failure_detector.cpp pbft_failure_detector.hpp pbft_failure_detector_base.hpp - pbft_configuration.hpp - pbft_configuration.cpp - pbft_config_store.hpp - pbft_config_store.cpp pbft_checkpoint_manager.cpp pbft_checkpoint_manager.hpp database_pbft_service.cpp diff --git a/pbft/operations/pbft_memory_operation.cpp b/pbft/operations/pbft_memory_operation.cpp index 4b924caf..18df5208 100644 --- a/pbft/operations/pbft_memory_operation.cpp +++ b/pbft/operations/pbft_memory_operation.cpp @@ -15,12 +15,12 @@ #include #include #include +#include using namespace bzn; -pbft_memory_operation::pbft_memory_operation(uint64_t view, uint64_t sequence, const bzn::hash_t& request_hash, std::shared_ptr> peers) +pbft_memory_operation::pbft_memory_operation(uint64_t view, uint64_t sequence, const bzn::hash_t& request_hash) : pbft_operation(view, sequence, request_hash) - , peers(std::move(peers)) { } @@ -135,39 +135,47 @@ pbft_memory_operation::is_preprepared() const return this->preprepare_seen; } -size_t -pbft_memory_operation::faulty_nodes_bound() const +bool +pbft_memory_operation::is_ready_for_commit(const std::shared_ptr& peers) const +{ + // TODO: may have to count based only on uuids that are still in the peers list + return this->has_request() && this->is_preprepared() && this->prepares_seen.size() >= pbft::honest_majority_size(peers->current()->size()); +} + +bool +pbft_memory_operation::is_ready_for_execute(const std::shared_ptr& peers) const { - return (this->peers->size() - 1) / 3; + // TODO: may have to count based only on uuids that are still in the peers list + return this->is_prepared() && this->commits_seen.size() >= pbft::honest_majority_size(peers->current()->size()); } bool pbft_memory_operation::is_prepared() const { - return this->has_request() && this->is_preprepared() && this->prepares_seen.size() > 2 * this->faulty_nodes_bound(); + return this->stage != pbft_operation_stage::prepare; } bool pbft_memory_operation::is_committed() const { - return this->is_prepared() && this->commits_seen.size() > 2 * this->faulty_nodes_bound(); + return this->stage == pbft_operation_stage::execute; } void -pbft_memory_operation::advance_operation_stage(bzn::pbft_operation_stage new_stage) +pbft_memory_operation::advance_operation_stage(bzn::pbft_operation_stage new_stage, const std::shared_ptr& peers) { switch(new_stage) { case pbft_operation_stage::prepare : throw std::runtime_error("cannot advance to initial stage"); case pbft_operation_stage::commit : - if (!this->is_preprepared() || this->stage != pbft_operation_stage::prepare) + if (!this->is_ready_for_commit(peers) || this->stage != pbft_operation_stage::prepare) { throw std::runtime_error("illegal move to commit phase"); } break; case pbft_operation_stage::execute : - if (!this->is_committed() || this->stage != pbft_operation_stage::commit) + if (!this->is_ready_for_execute(peers) || this->stage != pbft_operation_stage::commit) { throw std::runtime_error("illegal move to execute phase"); } diff --git a/pbft/operations/pbft_memory_operation.hpp b/pbft/operations/pbft_memory_operation.hpp index 2ed7ea72..c0d00854 100644 --- a/pbft/operations/pbft_memory_operation.hpp +++ b/pbft/operations/pbft_memory_operation.hpp @@ -15,7 +15,7 @@ #pragma once #include -#include +#include namespace bzn { @@ -23,7 +23,7 @@ namespace bzn { public: - pbft_memory_operation(uint64_t view, uint64_t sequence, const bzn::hash_t& request_hash, std::shared_ptr> peers); + pbft_memory_operation(uint64_t view, uint64_t sequence, const bzn::hash_t& request_hash); pbft_operation_stage get_stage() const override; @@ -33,7 +33,10 @@ namespace bzn bool is_prepared() const override; bool is_committed() const override; - void advance_operation_stage(pbft_operation_stage new_stage) override; + bool is_ready_for_commit(const std::shared_ptr& peers) const override; + bool is_ready_for_execute(const std::shared_ptr& peers) const override; + + void advance_operation_stage(pbft_operation_stage new_stage, const std::shared_ptr& peers) override; void record_request(const bzn_envelope& encoded_request) override; bool has_request() const override; @@ -48,8 +51,6 @@ namespace bzn std::map get_prepares() const override; private: - size_t faulty_nodes_bound() const; - bzn_envelope preprepare_message; std::map prepare_messages; @@ -65,6 +66,6 @@ namespace bzn bool request_saved = false; - const std::shared_ptr> peers; + const std::shared_ptr peers; }; } diff --git a/pbft/operations/pbft_operation.hpp b/pbft/operations/pbft_operation.hpp index d786389c..3f2a61b8 100644 --- a/pbft/operations/pbft_operation.hpp +++ b/pbft/operations/pbft_operation.hpp @@ -17,6 +17,7 @@ #include #include #include +#include namespace bzn { @@ -78,20 +79,30 @@ namespace bzn virtual void record_pbft_msg(const pbft_msg& msg, const bzn_envelope& encoded_msg) = 0; /** - * @return have we seen a preprepare for this operation + * @return has this operation moved to the prepare phase */ virtual bool is_preprepared() const = 0; /** - * @return is this operation prepared (as defined in the pbft paper) at this node + * @return has this operation moved to the commit phase */ virtual bool is_prepared() const = 0; /** - * @return is this operation committed-local (as defined in the pbft paper) at this node + * @return has this operation moved to the execute phase */ virtual bool is_committed() const = 0; + /** + * @return is this operation ready to move to the commit phase + */ + virtual bool is_ready_for_commit(const std::shared_ptr& peers) const = 0; + + /** + * @return is this operation ready to move to the execute phase + */ + virtual bool is_ready_for_execute(const std::shared_ptr& peers) const = 0; + /** * record the request that this operation is for. caller is responsible for checking that the request's hash * actually matches this operation's hash. @@ -103,7 +114,7 @@ namespace bzn * advance the operation to the next stage, checking that doing so is legal * @param new_stage */ - virtual void advance_operation_stage(pbft_operation_stage new_stage) = 0; + virtual void advance_operation_stage(pbft_operation_stage new_stage, const std::shared_ptr& peers) = 0; /** * @return do we know the full request associated with this operation? diff --git a/pbft/operations/pbft_operation_manager.cpp b/pbft/operations/pbft_operation_manager.cpp index 025d46d8..5de8829a 100644 --- a/pbft/operations/pbft_operation_manager.cpp +++ b/pbft/operations/pbft_operation_manager.cpp @@ -20,8 +20,9 @@ using namespace bzn; -pbft_operation_manager::pbft_operation_manager(std::optional> storage) - : storage(storage) +pbft_operation_manager::pbft_operation_manager(std::shared_ptr peers, std::optional> storage) + : peers(peers) + , storage(storage) { if (!storage) { @@ -30,8 +31,7 @@ pbft_operation_manager::pbft_operation_manager(std::optional -pbft_operation_manager::find_or_construct(uint64_t view, uint64_t sequence, const bzn::hash_t &request_hash, - std::shared_ptr> peers_list) +pbft_operation_manager::find_or_construct(uint64_t view, uint64_t sequence, const bzn::hash_t &request_hash) { std::lock_guard lock(this->pbft_lock); @@ -45,11 +45,11 @@ pbft_operation_manager::find_or_construct(uint64_t view, uint64_t sequence, cons std::shared_ptr op; if (this->storage) { - op = std::make_shared(view, sequence, request_hash, *(this->storage), peers_list->size()); + op = std::make_shared(view, sequence, request_hash, *(this->storage)); } else { - op = std::make_shared(view, sequence, request_hash, peers_list); + op = std::make_shared(view, sequence, request_hash); } bool added; @@ -63,9 +63,9 @@ pbft_operation_manager::find_or_construct(uint64_t view, uint64_t sequence, cons } std::shared_ptr -pbft_operation_manager::find_or_construct(const pbft_msg& msg, std::shared_ptr> peers_list) +pbft_operation_manager::find_or_construct(const pbft_msg& msg) { - return this->find_or_construct(msg.view(), msg.sequence(), msg.request_hash(), peers_list); + return this->find_or_construct(msg.view(), msg.sequence(), msg.request_hash()); } void diff --git a/pbft/operations/pbft_operation_manager.hpp b/pbft/operations/pbft_operation_manager.hpp index 1e17d0dd..9922ddd9 100644 --- a/pbft/operations/pbft_operation_manager.hpp +++ b/pbft/operations/pbft_operation_manager.hpp @@ -18,24 +18,24 @@ #include #include #include -#include +#include +#include namespace bzn { class pbft_operation_manager { public: - pbft_operation_manager(std::optional> storage = std::nullopt); + pbft_operation_manager(std::shared_ptr peers, std::optional> storage = std::nullopt); /* * Returns a (possibly freshly constructed) pbft_operation for a particular view/sequence/request_hash. * Guarenteed to consistently return the same pbft_operation instance over the lifetime of the * pbft_operations_manager - this is important because pbft_operation only stores sessions in memory */ - std::shared_ptr find_or_construct(uint64_t view, uint64_t sequence, const bzn::hash_t& request_hash, - std::shared_ptr> peers_list); + std::shared_ptr find_or_construct(uint64_t view, uint64_t sequence, const bzn::hash_t& request_hash); - std::shared_ptr find_or_construct(const pbft_msg& msg, std::shared_ptr> peers_list); + std::shared_ptr find_or_construct(const pbft_msg& msg); std::map> prepared_operations_since(uint64_t sequence); @@ -45,6 +45,7 @@ namespace bzn private: std::mutex pbft_lock; + std::shared_ptr peers; const std::optional> storage; std::map> held_operations; diff --git a/pbft/operations/pbft_persistent_operation.cpp b/pbft/operations/pbft_persistent_operation.cpp index 4d7bf4d2..54e3948f 100644 --- a/pbft/operations/pbft_persistent_operation.cpp +++ b/pbft/operations/pbft_persistent_operation.cpp @@ -73,9 +73,8 @@ pbft_persistent_operation::get_uuid() return OPERATIONS_UUID; } -pbft_persistent_operation::pbft_persistent_operation(uint64_t view, uint64_t sequence, const bzn::hash_t& request_hash, std::shared_ptr storage, size_t peers_size) +pbft_persistent_operation::pbft_persistent_operation(uint64_t view, uint64_t sequence, const bzn::hash_t& request_hash, std::shared_ptr storage) : pbft_operation(view, sequence, request_hash) - , peers_size(peers_size) , storage(std::move(storage)) , prefix(pbft_persistent_operation::generate_prefix(view, sequence, request_hash)) { @@ -97,7 +96,6 @@ pbft_persistent_operation::pbft_persistent_operation(uint64_t view, uint64_t seq // constructs operation already in storage without re-adding to storage pbft_persistent_operation::pbft_persistent_operation(std::shared_ptr storage, uint64_t view, uint64_t sequence, const bzn::hash_t& request_hash) : pbft_operation(view, sequence, request_hash) - , peers_size(1) // TODO: move peers_size out of operation. for now, this allows is_* to succeed if stage is set appropriately , storage(std::move(storage)) , prefix(pbft_persistent_operation::generate_prefix(view, sequence, request_hash)) { @@ -144,20 +142,20 @@ pbft_persistent_operation::get_stage() const } void -pbft_persistent_operation::advance_operation_stage(pbft_operation_stage new_stage) +pbft_persistent_operation::advance_operation_stage(pbft_operation_stage new_stage, const std::shared_ptr& peers) { switch (new_stage) { case pbft_operation_stage::prepare : throw std::runtime_error("cannot advance to initial stage"); case pbft_operation_stage::commit : - if (!this->is_prepared() || this->get_stage() != pbft_operation_stage::prepare) + if (!this->is_ready_for_commit(peers) || this->get_stage() != pbft_operation_stage::prepare) { throw std::runtime_error("illegal move to commit phase"); } break; case pbft_operation_stage::execute : - if (!this->is_committed() || this->get_stage() != pbft_operation_stage::commit) + if (!this->is_ready_for_execute(peers) || this->get_stage() != pbft_operation_stage::commit) { throw std::runtime_error("illegal move to execute phase"); } @@ -177,24 +175,37 @@ pbft_persistent_operation::advance_operation_stage(pbft_operation_stage new_stag bool pbft_persistent_operation::is_preprepared() const { + // TODO: maybe check if the sender of the preprepare is still in the peers list auto prefix = this->typed_prefix(pbft_msg_type::PBFT_MSG_PREPREPARE); return this->storage->get_keys_if(get_uuid(), prefix, this->increment_prefix(prefix)).size() > 0; } bool pbft_persistent_operation::is_prepared() const +{ + return this->get_stage() != pbft_operation_stage::prepare; +} + +bool +pbft_persistent_operation::is_committed() const +{ + return this->get_stage() == pbft_operation_stage::execute; +} + +bool +pbft_persistent_operation::is_ready_for_commit(const std::shared_ptr& peers) const { auto prefix = this->typed_prefix(pbft_msg_type::PBFT_MSG_PREPARE); return this->storage->get_keys_if(get_uuid(), prefix, this->increment_prefix(prefix)).size() - >= pbft::honest_majority_size(this->peers_size) && this->is_preprepared() && this->has_request(); + >= pbft::honest_majority_size(peers->current()->size()) && this->is_preprepared() && this->has_request(); } bool -pbft_persistent_operation::is_committed() const +pbft_persistent_operation::is_ready_for_execute(const std::shared_ptr& peers) const { auto prefix = this->typed_prefix(pbft_msg_type::PBFT_MSG_COMMIT); return this->storage->get_keys_if(get_uuid(), prefix, this->increment_prefix(prefix)).size() - >= pbft::honest_majority_size(this->peers_size) && this->is_prepared(); + >= pbft::honest_majority_size(peers->current()->size()) && this->is_prepared(); } void diff --git a/pbft/operations/pbft_persistent_operation.hpp b/pbft/operations/pbft_persistent_operation.hpp index b998d3b6..e4d534d8 100644 --- a/pbft/operations/pbft_persistent_operation.hpp +++ b/pbft/operations/pbft_persistent_operation.hpp @@ -23,7 +23,7 @@ namespace bzn class pbft_persistent_operation : public pbft_operation { public: - pbft_persistent_operation(uint64_t view, uint64_t sequence, const bzn::hash_t& request_hash, std::shared_ptr storage, size_t ); + pbft_persistent_operation(uint64_t view, uint64_t sequence, const bzn::hash_t& request_hash, std::shared_ptr storage); // constructs operation already in storage pbft_persistent_operation(std::shared_ptr storage, uint64_t view, uint64_t sequence, const bzn::hash_t& request_hash); @@ -31,11 +31,14 @@ namespace bzn void record_pbft_msg(const pbft_msg& msg, const bzn_envelope& encoded_msg) override; pbft_operation_stage get_stage() const override; - void advance_operation_stage(pbft_operation_stage new_stage) override; + void advance_operation_stage(pbft_operation_stage new_stage, const std::shared_ptr& peers) override; bool is_preprepared() const override; bool is_prepared() const override; bool is_committed() const override; + bool is_ready_for_commit(const std::shared_ptr& peers) const override; + bool is_ready_for_execute(const std::shared_ptr& peers) const override; + void record_request(const bzn_envelope& encoded_request) override; bool has_request() const override; bool has_db_request() const override; @@ -64,7 +67,6 @@ namespace bzn static bool parse_prefix(const std::string& prefix, uint64_t& view, uint64_t& sequence, bzn::hash_t& hash); std::string increment_prefix(const std::string& prefix) const; - const size_t peers_size; const std::shared_ptr storage; const std::string prefix; diff --git a/pbft/operations/test/CMakeLists.txt b/pbft/operations/test/CMakeLists.txt index 9239b8ab..afca0399 100644 --- a/pbft/operations/test/CMakeLists.txt +++ b/pbft/operations/test/CMakeLists.txt @@ -3,6 +3,6 @@ set(test_srcs pbft_persistent_operation_test.cpp pbft_operation_manager_test.cpp ) -set(test_libs pbft_operations storage pbft ${Protobuf_LIBRARIES}) +set(test_libs pbft_operations storage pbft smart_mocks ${Protobuf_LIBRARIES}) add_gmock_test(pbft_operation) diff --git a/pbft/operations/test/pbft_operation_manager_test.cpp b/pbft/operations/test/pbft_operation_manager_test.cpp index ef177849..c2ce1cbd 100644 --- a/pbft/operations/test/pbft_operation_manager_test.cpp +++ b/pbft/operations/test/pbft_operation_manager_test.cpp @@ -13,9 +13,10 @@ // along with this program. If not, see . #include -#include +#include #include #include +#include using namespace ::testing; @@ -48,15 +49,15 @@ namespace op->record_request(outer); - op->advance_operation_stage(bzn::pbft_operation_stage::commit); + op->advance_operation_stage(bzn::pbft_operation_stage::commit, static_peers_beacon_for(TEST_PEER_LIST)); EXPECT_TRUE(op->is_prepared()); } TEST(pbft_operation_manager_test, returned_operation_matches_key) { - bzn::pbft_operation_manager manager; - auto op = manager.find_or_construct(1, 6, "hash", peers_ptr); + bzn::pbft_operation_manager manager{static_peers_beacon_for(TEST_PEER_LIST)}; + auto op = manager.find_or_construct(1, 6, "hash"); EXPECT_EQ(op->get_view(), 1u); EXPECT_EQ(op->get_sequence(), 6u); EXPECT_EQ(op->get_request_hash(), "hash"); @@ -64,10 +65,10 @@ namespace TEST(pbft_operation_manager_test, returns_same_operation_instance) { - bzn::pbft_operation_manager manager; - auto op1 = manager.find_or_construct(1, 6, "hash", peers_ptr); - auto op2 = manager.find_or_construct(2, 6, "hash", peers_ptr); - auto op3 = manager.find_or_construct(1, 6, "hash", peers_ptr); + bzn::pbft_operation_manager manager{static_peers_beacon_for(TEST_PEER_LIST)}; + auto op1 = manager.find_or_construct(1, 6, "hash"); + auto op2 = manager.find_or_construct(2, 6, "hash"); + auto op3 = manager.find_or_construct(1, 6, "hash"); EXPECT_NE(op1, op2); EXPECT_EQ(op1, op3); @@ -75,9 +76,9 @@ namespace TEST(pbft_operation_manager_test, prepared_operations_since_only_prepared_ops) { - bzn::pbft_operation_manager manager; - auto op1 = manager.find_or_construct(1, 1, "hash", peers_ptr); - auto op2 = manager.find_or_construct(1, 2, "hash", peers_ptr); + bzn::pbft_operation_manager manager{static_peers_beacon_for(TEST_PEER_LIST)}; + auto op1 = manager.find_or_construct(1, 1, "hash"); + auto op2 = manager.find_or_construct(1, 2, "hash"); make_prepared(op1); @@ -88,9 +89,9 @@ namespace TEST(pbft_operation_manager_test, prepared_operations_since_prefers_prepared_ops_when_duplicate) { - bzn::pbft_operation_manager manager; - auto op1 = manager.find_or_construct(1, 1, "hash", peers_ptr); - auto op2 = manager.find_or_construct(2, 1, "hash", peers_ptr); + bzn::pbft_operation_manager manager{static_peers_beacon_for(TEST_PEER_LIST)}; + auto op1 = manager.find_or_construct(1, 1, "hash"); + auto op2 = manager.find_or_construct(2, 1, "hash"); make_prepared(op2); @@ -100,11 +101,11 @@ namespace TEST(pbft_operation_manager_test, prepared_operations_since_no_duplicates) { - bzn::pbft_operation_manager manager; - auto op1 = manager.find_or_construct(1, 1, "hash", peers_ptr); - auto op2 = manager.find_or_construct(2, 1, "hash", peers_ptr); - auto op3 = manager.find_or_construct(3, 1, "hash", peers_ptr); - auto op4 = manager.find_or_construct(4, 1, "hash", peers_ptr); + bzn::pbft_operation_manager manager{static_peers_beacon_for(TEST_PEER_LIST)}; + auto op1 = manager.find_or_construct(1, 1, "hash"); + auto op2 = manager.find_or_construct(2, 1, "hash"); + auto op3 = manager.find_or_construct(3, 1, "hash"); + auto op4 = manager.find_or_construct(4, 1, "hash"); make_prepared(op1); make_prepared(op2); @@ -116,11 +117,11 @@ namespace TEST(pbft_operation_manager_test, delete_clears_operations) { - bzn::pbft_operation_manager manager; - auto op1 = manager.find_or_construct(1, 1, "hash", peers_ptr); - auto op2 = manager.find_or_construct(2, 2, "hash", peers_ptr); - auto op3 = manager.find_or_construct(3, 3, "hash", peers_ptr); - auto op4 = manager.find_or_construct(4, 4, "hash", peers_ptr); + bzn::pbft_operation_manager manager{static_peers_beacon_for(TEST_PEER_LIST)}; + auto op1 = manager.find_or_construct(1, 1, "hash"); + auto op2 = manager.find_or_construct(2, 2, "hash"); + auto op3 = manager.find_or_construct(3, 3, "hash"); + auto op4 = manager.find_or_construct(4, 4, "hash"); EXPECT_EQ(manager.held_operations_count(), 4u); manager.delete_operations_until(2); diff --git a/pbft/operations/test/pbft_operation_test_common.cpp b/pbft/operations/test/pbft_operation_test_common.cpp index fcb23b31..da952e60 100644 --- a/pbft/operations/test/pbft_operation_test_common.cpp +++ b/pbft/operations/test/pbft_operation_test_common.cpp @@ -15,9 +15,10 @@ #include #include #include -#include +#include #include #include +#include using namespace ::testing; @@ -46,12 +47,13 @@ namespace bzn::hash_t request_hash = "somehash"; uint64_t view = 6; uint64_t sequence = 19; + std::shared_ptr static_beacon = static_peers_beacon_for(TEST_PEER_LIST); std::shared_ptr storage = std::make_shared(); std::vector> operations{ - std::make_shared(view, sequence, request_hash, std::make_shared>(TEST_PEER_LIST)), - std::make_shared(view, sequence, request_hash, storage, TEST_PEER_LIST.size()) + std::make_shared(view, sequence, request_hash), + std::make_shared(view, sequence, request_hash, this->storage) }; bzn_envelope empty_original_msg; @@ -94,7 +96,7 @@ namespace op->record_pbft_msg(this->prepare, msg); } - EXPECT_TRUE(op->is_prepared()); + EXPECT_TRUE(op->is_ready_for_commit(this->static_beacon)); } } @@ -111,7 +113,7 @@ namespace op->record_pbft_msg(this->prepare, msg); } - EXPECT_FALSE(op->is_prepared()); + EXPECT_FALSE(op->is_ready_for_commit(this->static_beacon)); } } @@ -127,7 +129,7 @@ namespace op->record_pbft_msg(this->prepare, msg); } - EXPECT_FALSE(op->is_prepared()); + EXPECT_FALSE(op->is_ready_for_commit(this->static_beacon)); } } @@ -145,7 +147,7 @@ namespace op->record_pbft_msg(this->prepare, msg); } - EXPECT_FALSE(op->is_prepared()); + EXPECT_FALSE(op->is_ready_for_commit(this->static_beacon)); } } @@ -164,7 +166,7 @@ namespace op->record_pbft_msg(this->prepare, msg); } - EXPECT_TRUE(op->is_prepared()); + EXPECT_TRUE(op->is_ready_for_commit(this->static_beacon)); } } @@ -217,7 +219,7 @@ namespace op->record_pbft_msg(this->prepare, env); EXPECT_EQ(op->get_prepares().size(), 2u); - EXPECT_FALSE(op->is_prepared()); + EXPECT_FALSE(op->is_ready_for_commit(this->static_beacon)); } } } diff --git a/pbft/operations/test/pbft_persistent_operation_test.cpp b/pbft/operations/test/pbft_persistent_operation_test.cpp index ebebe3d6..2b63d8bb 100644 --- a/pbft/operations/test/pbft_persistent_operation_test.cpp +++ b/pbft/operations/test/pbft_persistent_operation_test.cpp @@ -19,12 +19,17 @@ #include #include #include +#include using namespace ::testing; namespace { const std::vector UUIDS{"alice", "bob", "cindy", "dave"}; + const std::vector TEST_PEER_LIST{{ "127.0.0.1", 8081, "name1", "alice"} + , {"127.0.0.1", 8082, "name2", "bob"} + , {"127.0.0.1", 8083, "name3", "cindy"} + , {"127.0.0.1", 8084, "name4", "dave"}}; void record_pbft_messages(int from, int until, pbft_msg_type type, std::shared_ptr op) { @@ -67,8 +72,10 @@ namespace const std::string request_hash = "a very hashy hash"; const size_t peers_size = 4; + std::shared_ptr static_beacon = static_peers_beacon_for(TEST_PEER_LIST); + std::shared_ptr storage = std::make_shared(); - std::shared_ptr operation = std::make_shared(this->view, this->sequence, this->request_hash, this->storage, this->peers_size); + std::shared_ptr operation = std::make_shared(this->view, this->sequence, this->request_hash, this->storage); }; @@ -77,18 +84,18 @@ namespace record_request(this->operation); record_pbft_messages(0, 1, PBFT_MSG_PREPREPARE, this->operation); record_pbft_messages(0, 4, PBFT_MSG_PREPARE, this->operation); - this->operation->advance_operation_stage(bzn::pbft_operation_stage::commit); + this->operation->advance_operation_stage(bzn::pbft_operation_stage::commit, static_peers_beacon_for(TEST_PEER_LIST)); - EXPECT_TRUE(this->operation->is_prepared()); + EXPECT_TRUE(this->operation->is_ready_for_commit(this->static_beacon)); EXPECT_EQ(this->operation->get_stage(), bzn::pbft_operation_stage::commit); this->operation = nullptr; - auto op2 = std::make_shared(this->view, this->sequence, this->request_hash, this->storage, this->peers_size); - EXPECT_TRUE(op2->is_prepared()); + auto op2 = std::make_shared(this->view, this->sequence, this->request_hash, this->storage); + EXPECT_TRUE(op2->is_ready_for_commit(this->static_beacon)); EXPECT_EQ(op2->get_stage(), bzn::pbft_operation_stage::commit); - auto op3 = std::make_shared(this->view, this->sequence+1, this->request_hash, this->storage, this->peers_size); - EXPECT_FALSE(op3->is_prepared()); + auto op3 = std::make_shared(this->view, this->sequence+1, this->request_hash, this->storage); + EXPECT_FALSE(op3->is_ready_for_commit(this->static_beacon)); EXPECT_EQ(op3->get_stage(), bzn::pbft_operation_stage::prepare); } @@ -99,11 +106,11 @@ namespace EXPECT_EQ(this->operation->get_database_msg().header().nonce(), 9999u); this->operation = nullptr; - auto op2 = std::make_shared(this->view, this->sequence, this->request_hash, this->storage, this->peers_size); + auto op2 = std::make_shared(this->view, this->sequence, this->request_hash, this->storage); EXPECT_TRUE(op2->has_db_request()); EXPECT_EQ(op2->get_database_msg().header().nonce(), 9999u); - auto op3 = std::make_shared(this->view+1, this->sequence, this->request_hash, this->storage, this->peers_size); + auto op3 = std::make_shared(this->view+1, this->sequence, this->request_hash, this->storage); EXPECT_FALSE(op3->has_db_request()); } @@ -118,26 +125,26 @@ namespace EXPECT_TRUE(this->operation->has_request()); this->operation = nullptr; - auto op2 = std::make_shared(this->view, this->sequence, this->request_hash, this->storage, this->peers_size); + auto op2 = std::make_shared(this->view, this->sequence, this->request_hash, this->storage); EXPECT_EQ(op2->get_stage(), bzn::pbft_operation_stage::prepare); EXPECT_TRUE(op2->is_preprepared()); EXPECT_TRUE(op2->has_request()); record_pbft_messages(2, 4, PBFT_MSG_PREPARE, op2); - EXPECT_TRUE(op2->is_prepared()); - op2->advance_operation_stage(bzn::pbft_operation_stage::commit); + EXPECT_TRUE(op2->is_ready_for_commit(this->static_beacon)); + op2->advance_operation_stage(bzn::pbft_operation_stage::commit, this->static_beacon); record_pbft_messages(0, 4, PBFT_MSG_COMMIT, op2); - EXPECT_TRUE(op2->is_committed()); - op2->advance_operation_stage(bzn::pbft_operation_stage::execute); + EXPECT_TRUE(op2->is_ready_for_execute(this->static_beacon)); + op2->advance_operation_stage(bzn::pbft_operation_stage::execute, this->static_beacon); } TEST_F(persistent_operation_test, no_contamination_from_different_request) { - auto op2 = std::make_shared(this->view, this->sequence, this->request_hash, this->storage, this->peers_size); - auto op3 = std::make_shared(this->view+1, this->sequence, this->request_hash, this->storage, this->peers_size); - auto op4 = std::make_shared(this->view, this->sequence, this->request_hash+"xx", this->storage, this->peers_size); + auto op2 = std::make_shared(this->view, this->sequence, this->request_hash, this->storage); + auto op3 = std::make_shared(this->view+1, this->sequence, this->request_hash, this->storage); + auto op4 = std::make_shared(this->view, this->sequence, this->request_hash+"xx", this->storage); //op2 gets just a preprepare, op3 gets 2f prepares, op4 gets 2f+1 prepares @@ -150,11 +157,11 @@ namespace record_pbft_messages(0, 2, PBFT_MSG_PREPARE, op3); record_pbft_messages(0, 3, PBFT_MSG_PREPARE, op4); - op4->advance_operation_stage(bzn::pbft_operation_stage::commit); + op4->advance_operation_stage(bzn::pbft_operation_stage::commit, this->static_beacon); - EXPECT_FALSE(op2->is_prepared()); - EXPECT_FALSE(op3->is_prepared()); - EXPECT_TRUE(op4->is_prepared()); + EXPECT_FALSE(op2->is_ready_for_commit(this->static_beacon)); + EXPECT_FALSE(op3->is_ready_for_commit(this->static_beacon)); + EXPECT_TRUE(op4->is_ready_for_commit(this->static_beacon)); } TEST_F(persistent_operation_test, remembers_messages_after_rehydrate) @@ -164,12 +171,12 @@ namespace record_pbft_messages(0, 2, PBFT_MSG_PREPARE, this->operation); this->operation = nullptr; - auto op2 = std::make_shared(this->view, this->sequence, this->request_hash, this->storage, this->peers_size); + auto op2 = std::make_shared(this->view, this->sequence, this->request_hash, this->storage); record_pbft_messages(2, 4, PBFT_MSG_PREPARE, op2); - op2->advance_operation_stage(bzn::pbft_operation_stage::commit); + op2->advance_operation_stage(bzn::pbft_operation_stage::commit, this->static_beacon); - EXPECT_TRUE(op2->is_prepared()); + EXPECT_TRUE(op2->is_ready_for_commit(this->static_beacon)); EXPECT_EQ(op2->get_preprepare().sender(), UUIDS.at(0)); EXPECT_EQ(op2->get_prepares().size(), 4u); } @@ -178,7 +185,7 @@ namespace { for (auto i : boost::irange(0, 100)) { - auto op = std::make_shared(1, i, "some_hash", this->storage, 4); + auto op = std::make_shared(1, i, "some_hash", this->storage); record_request(op); record_pbft_messages(0, 1, PBFT_MSG_PREPREPARE, op); @@ -186,7 +193,7 @@ namespace record_pbft_messages(0, (i % 4) + 1, PBFT_MSG_PREPARE, op); if ((i % 4 + 1) > 2) { - op->advance_operation_stage(bzn::pbft_operation_stage::commit); + op->advance_operation_stage(bzn::pbft_operation_stage::commit, this->static_beacon); } } @@ -197,7 +204,7 @@ namespace { for (auto i : boost::irange(0, 100)) { - auto op = std::make_shared(1, i, "some_hash", this->storage, 4); + auto op = std::make_shared(1, i, "some_hash", this->storage); record_request(op); record_pbft_messages(0, 1, PBFT_MSG_PREPREPARE, op); } diff --git a/pbft/pbft.cpp b/pbft/pbft.cpp index 4cfc626f..69a45d48 100644 --- a/pbft/pbft.cpp +++ b/pbft/pbft.cpp @@ -36,7 +36,7 @@ using namespace bzn; pbft::pbft( std::shared_ptr node , std::shared_ptr io_context - , const bzn::peers_list_t& peers + , std::shared_ptr peers , std::shared_ptr options , std::shared_ptr service , std::shared_ptr failure_detector @@ -53,21 +53,18 @@ pbft::pbft( , failure_detector(std::move(failure_detector)) , io_context(io_context) , audit_heartbeat_timer(this->io_context->make_unique_steady_timer()) - , new_config_timer(this->io_context->make_unique_steady_timer()) - , join_retry_timer(this->io_context->make_unique_steady_timer()) , crypto(std::move(crypto)) , operation_manager(std::move(operation_manager)) - , configurations(std::make_shared(storage)) - , checkpoint_manager(std::make_shared(this->io_context, this->storage, this->configurations, this->node)) + , peers_beacon(std::move(peers)) + , checkpoint_manager(std::make_shared(this->io_context, this->storage, this->peers_beacon, this->node)) , monitor(std::move(monitor)) { - if (peers.empty()) + if (this->peers_beacon->current()->empty()) { throw std::runtime_error("No peers found!"); } this->initialize_persistent_state(); - this->initialize_configuration(peers); this->service->save_service_state_at(((this->next_issued_sequence_number.value() / CHECKPOINT_INTERVAL) + 1) * CHECKPOINT_INTERVAL); } @@ -140,7 +137,8 @@ pbft::start() } ); - this->join_swarm(); + auto peers = this->peers_beacon->ordered(); + this->pinned_primary = peers->at(this->view.value() % peers->size()); }); } @@ -208,19 +206,12 @@ pbft::handle_membership_message(const bzn_envelope& msg, std::shared_ptrhandle_join_or_leave(msg, inner_msg, session, hash); - break; case PBFT_MMSG_GET_STATE: this->handle_get_state(inner_msg, std::move(session)); break; case PBFT_MMSG_SET_STATE: this->handle_set_state(inner_msg); break; - case PBFT_MMSG_JOIN_RESPONSE: - this->handle_join_response(inner_msg); - break; default: LOG(error) << "Invalid membership message received " << inner_msg.DebugString().substr(0, MAX_MESSAGE_SIZE); @@ -234,6 +225,18 @@ pbft::handle_message(const pbft_msg& msg, const bzn_envelope& original_msg) std::lock_guard lock(this->pbft_lock); + auto peers = this->peers_beacon->current(); + auto find = std::find_if(peers->begin(), peers->end(), + [&](const auto& peer) + { + return peer.uuid == original_msg.sender(); + }); + if (find == std::end(*peers)) + { + LOG(debug) << "Dropping message because it is not from a peer"; + return; + } + if (!this->preliminary_filter_msg(msg)) { return; @@ -301,7 +304,7 @@ pbft::setup_request_operation(const bzn_envelope& request_env, const bzn::hash_t { const uint64_t request_seq = this->next_issued_sequence_number.value(); this->next_issued_sequence_number = request_seq + 1; - auto op = this->operation_manager->find_or_construct(this->view.value(), request_seq, request_hash, this->current_peers_ptr()); + auto op = this->operation_manager->find_or_construct(this->view.value(), request_seq, request_hash); op->record_request(request_env); return op; @@ -387,8 +390,15 @@ pbft::handle_request(const bzn_envelope& request_env, const std::shared_ptrget_current_primary(); + if (!primary.has_value()) + { + LOG(error) << "Would forward request to primary, but we don't know the primary so we can't"; + return; + } + this->node - ->send_signed_message(bzn::make_endpoint(this->get_primary()), std::make_shared(request_env)); + ->send_signed_message(bzn::make_endpoint(*primary), std::make_shared(request_env)); const bzn::hash_t req_hash = this->crypto->hash(request_env); LOG(info) << "Forwarded request to primary, " << bzn::bytes_to_debug_string(req_hash); @@ -428,7 +438,7 @@ pbft::handle_preprepare(const pbft_msg& msg, const bzn_envelope& original_msg) } else { - auto op = this->operation_manager->find_or_construct(msg, this->current_peers_ptr()); + auto op = this->operation_manager->find_or_construct(msg); auto env_copy{original_msg}; env_copy.clear_piggybacked_requests(); op->record_pbft_msg(msg, env_copy); @@ -444,11 +454,6 @@ pbft::handle_preprepare(const pbft_msg& msg, const bzn_envelope& original_msg) , ACCEPTED_PREPREPARES_KEY, log_key}; - if (op->has_config_request()) - { - this->handle_config_message(msg, op); - } - this->do_preprepared(op); this->maybe_advance_operation_state(op); } @@ -458,7 +463,7 @@ void pbft::handle_prepare(const pbft_msg& msg, const bzn_envelope& original_msg) { // Prepare messages are never rejected, assuming the sanity checks passed - auto op = this->operation_manager->find_or_construct(msg, this->current_peers_ptr()); + auto op = this->operation_manager->find_or_construct(msg); op->record_pbft_msg(msg, original_msg); this->maybe_advance_operation_state(op); @@ -468,109 +473,12 @@ void pbft::handle_commit(const pbft_msg& msg, const bzn_envelope& original_msg) { // Commit messages are never rejected, assuming the sanity checks passed - auto op = this->operation_manager->find_or_construct(msg, this->current_peers_ptr()); + auto op = this->operation_manager->find_or_construct(msg); op->record_pbft_msg(msg, original_msg); this->maybe_advance_operation_state(op); } -void -pbft::handle_join_or_leave(const bzn_envelope& env - , const pbft_membership_msg& msg - , std::shared_ptr session - , const std::string& msg_hash) -{ - if (msg.has_peer_info()) - { - // build a peer_address_t from the message - auto const &peer_info = msg.peer_info(); - bzn::peer_address_t peer(peer_info.host(), static_cast(peer_info.port()), - peer_info.name(), peer_info.uuid()); - - // test for re-join of existing swarm member - if (msg.type() == PBFT_MMSG_JOIN && this->is_peer(peer_info.uuid())) - { - // send response - if (session && session->is_open()) - { - pbft_membership_msg response; - response.set_type(PBFT_MMSG_JOIN_RESPONSE); - auto env = this->wrap_message(response); - LOG(debug) << "Sending JOIN_RESPONSE to " << peer_info.uuid(); - session->send_message(std::make_shared(env.SerializeAsString())); - } - - return; - } - - if (this->new_config_in_flight) - { - if (session) - { - session->close(); - } - - return; - } - - this->add_session_to_sessions_waiting(msg_hash, session); - - if (!this->is_primary()) - { - this->forward_request_to_primary(env); - return; - } - - auto config = std::make_shared(*(this->configurations->get(this->configurations->newest_committed()))); - if (msg.type() == PBFT_MMSG_JOIN) - { - // see if we can add this peer - if (!config->add_peer(peer)) - { - LOG(debug) << "Can't add new peer due to conflict"; - - if (session) - { - session->close(); - } - - return; - } - } - else if (msg.type() == PBFT_MMSG_LEAVE) - { - if (!config->remove_peer(peer)) - { - LOG(debug) << "Couldn't remove requested peer"; - return; - } - } - - this->configurations->add(config); - this->new_config_in_flight = true; - this->broadcast_new_configuration(config, msg.type() == PBFT_MMSG_JOIN ? msg_hash : ""); - } - else - { - LOG(debug) << "Malformed join/leave message"; - } -} - -void -pbft::handle_join_response(const pbft_membership_msg& /*msg*/) -{ - if (this->in_swarm == swarm_status::joining) - { - this->join_retry_timer->cancel(); - this->in_swarm = swarm_status::waiting; - LOG(debug) << "Successfully joined the swarm, waiting for NEW_VIEW message..."; - } - else - { - LOG(debug) << "Received JOIN response, ignoring"; - } -} - void pbft::handle_get_state(const pbft_membership_msg& msg, std::shared_ptr session) const { @@ -599,7 +507,6 @@ pbft::handle_get_state(const pbft_membership_msg& msg, std::shared_ptrconfigurations->current()->to_string()); // TODO: the latest stable checkpoint may have advanced by the time we pull it here, which will cause the receiver // to reject this message. This is innocuous, but we could avoid the awkwardness by requesting a specific checkpoint @@ -662,14 +569,9 @@ pbft::handle_set_state(const pbft_membership_msg& msg) { this->view = newview.view(); LOG(info) << "setting view to " << this->view.value(); + this->set_primary_from_newview(msg.newview_msg()); } } - - pbft_configuration current_configuration; - current_configuration.from_string(msg.current_configuration()); - this->configurations->add(std::make_shared(current_configuration)); - this->configurations->set_committed(current_configuration.get_hash()); - this->configurations->set_current(current_configuration.get_hash(), this->view.value()); } void @@ -677,7 +579,7 @@ pbft::broadcast(const bzn_envelope& msg) { auto msg_ptr = std::make_shared(msg); - for (const auto& peer : this->current_peers()) + for (const auto& peer : *this->peers_beacon->current()) { this->node->send_signed_message(make_endpoint(peer), msg_ptr); } @@ -713,7 +615,7 @@ pbft::async_signed_broadcast(std::shared_ptr msg_env) msg_env->set_timestamp(this->now()); auto targets = std::make_shared>(); - for (const auto& peer : this->current_peers()) + for (const auto& peer : *this->peers_beacon->current()) { targets->emplace_back(bzn::make_endpoint(peer)); } @@ -724,12 +626,12 @@ pbft::async_signed_broadcast(std::shared_ptr msg_env) void pbft::maybe_advance_operation_state(const std::shared_ptr& op) { - if (op->get_stage() == pbft_operation_stage::prepare && op->is_prepared()) + if (op->get_stage() == pbft_operation_stage::prepare && op->is_ready_for_commit(this->peers_beacon)) { this->do_prepared(op); } - if (op->get_stage() == pbft_operation_stage::commit && op->is_committed()) + if (op->get_stage() == pbft_operation_stage::commit && op->is_ready_for_execute(this->peers_beacon)) { this->do_committed(op); } @@ -783,18 +685,8 @@ pbft::do_preprepared(const std::shared_ptr& op) void pbft::do_prepared(const std::shared_ptr& op) { - // accept new configuration if applicable - if (op->has_config_request()) - { - pbft_configuration config; - if (config.from_string(op->get_config_request().configuration())) - { - this->configurations->set_prepared(config.get_hash()); - } - } - LOG(debug) << "Entering commit phase for operation " << op->get_sequence(); - op->advance_operation_stage(pbft_operation_stage::commit); + op->advance_operation_stage(pbft_operation_stage::commit, this->peers_beacon); pbft_msg msg = this->common_message_setup(op, PBFT_MSG_COMMIT); @@ -805,7 +697,7 @@ void pbft::do_committed(const std::shared_ptr& op) { LOG(debug) << "Operation " << op->get_sequence() << " " << bzn::bytes_to_debug_string(op->get_request_hash()) << " is committed-local"; - op->advance_operation_stage(pbft_operation_stage::execute); + op->advance_operation_stage(pbft_operation_stage::execute, this->peers_beacon); // If we have a pending session for this request, attach to the operation just before we pass off to the service. // If we were to do that before this moment, then maybe the pbft_operation we attached it to never gets executed and @@ -825,56 +717,6 @@ pbft::do_committed(const std::shared_ptr& op) LOG(debug) << "No pending session for this operation"; } - // commit new configuration if applicable - if (op->has_config_request()) - { - pbft_configuration config; - if (config.from_string(op->get_config_request().configuration())) - { - if (this->configurations->current()->get_hash() != config.get_hash()) - { - this->configurations->set_committed(config.get_hash()); - - this->new_config_timer->cancel(); - this->new_config_timer->expires_from_now(NEW_CONFIG_INTERVAL); - this->new_config_timer->async_wait( - std::bind(&pbft::handle_new_config_timeout, shared_from_this(), std::placeholders::_1)); - - // the hash registered with the failure detector was the internal request - this->failure_detector->request_executed(op->get_config_request().join_request_hash()); - - // send response to new node - auto session_it = this->sessions_waiting_on_forwarded_requests.find( - op->get_config_request().join_request_hash()); - if (session_it != this->sessions_waiting_on_forwarded_requests.end()) - { - if (session_it->second->is_open()) - { - pbft_membership_msg response; - response.set_type(PBFT_MMSG_JOIN_RESPONSE); - LOG(debug) << "Sending JOIN_RESPONSE"; - auto env = this->wrap_message(response); - session_it->second->send_message(std::make_shared(env.SerializeAsString())); - - } - - this->sessions_waiting_on_forwarded_requests.erase(session_it); - } - else - { - LOG(debug) << "Unable to send join response, session is not valid"; - } - } - else - { - LOG(debug) << "Skipping re-applying current configuration"; - } - } - - // now this config is committed we can accept join requests again - this->new_config_in_flight = false; - } - if (this->audit_enabled) { audit_message msg; @@ -901,39 +743,40 @@ pbft::do_committed(const std::shared_ptr& op) bzn_envelope request; request.set_database_msg(msg.SerializeAsString()); auto new_op = std::make_shared(op->get_view(), op->get_sequence() - , this->crypto->hash(request), nullptr); + , this->crypto->hash(request)); new_op->record_request(request); this->io_context->post(std::bind(&pbft_service_base::apply_operation, this->service, new_op)); } } -void -pbft::handle_new_config_timeout(const boost::system::error_code& ec) +bool +pbft::is_primary() const { - if (ec == boost::asio::error::operation_aborted) - { - return; - } - - if (ec) - { - LOG(error) << "handle_new_config_timeout error: " << ec.message(); - return; - } - - this->initiate_viewchange(); + auto primary = this->get_current_primary(); + return primary.has_value() && (*primary).uuid == this->uuid; } -bool -pbft::is_primary() const +std::optional +pbft::get_current_primary() const { - return this->get_primary().uuid == this->uuid; + return this->pinned_primary; } -const peer_address_t& -pbft::get_primary(std::optional view) const +std::optional +pbft::predict_primary(uint64_t view) const { - return this->current_peers()[view.value_or(this->view.value()) % this->current_peers().size()]; + if (view == this->view.value()) + { + return this->get_current_primary(); + } + + auto peers = this->peers_beacon->ordered(); + if (peers->size() == 0) + { + return std::nullopt; + } + + return peers->at(view % peers->size()); } bzn_envelope @@ -1001,7 +844,6 @@ pbft::handle_failure() LOG (error) << "handle_failure - PBFT failure - invalidating current view and sending VIEWCHANGE to view: " << this->view.value() + 1; this->notify_audit_failure_detected(); - this->new_config_timer->cancel(); this->initiate_viewchange(); } @@ -1057,7 +899,7 @@ pbft::quorum_size() const size_t pbft::max_faulty_nodes() const { - return this->current_peers().size()/3; + return (this->peers_beacon->current()->size()-1)/3; } void @@ -1140,10 +982,11 @@ pbft::get_node() bool pbft::is_peer(const bzn::uuid_t& sender) const { - return std::find_if (std::begin(this->current_peers()), std::end(this->current_peers()), [&](const auto& address) + auto ptr = this->peers_beacon->current(); + return std::find_if (ptr->begin(), ptr->end(), [&](const auto& address) { return sender == address.uuid; - }) != this->current_peers().end(); + }) != ptr->end(); } std::map> @@ -1166,7 +1009,7 @@ pbft::validate_and_extract_checkpoint_hashes(const pbft_msg &viewchange_message) // filter checkpoint_hashes to only those pairs (checkpoint, senders) such that |senders| >= 2f+1 std::map> retval; std::copy_if(checkpoint_hashes.begin(), checkpoint_hashes.end(), std::inserter(retval, retval.end()) - , [&](const auto& h) {return h.second.size() >= this->honest_member_size(this->current_peers().size());}); + , [&](const auto& h) {return h.second.size() >= this->honest_member_size(this->peers_beacon->current()->size());}); return retval; } @@ -1299,6 +1142,19 @@ pbft::get_sequences_and_request_hashes_from_proofs( bool pbft::is_valid_newview_message(const pbft_msg& theirs, const bzn_envelope& original_theirs) const { + auto expected_primary = this->predict_primary(theirs.view()); + if (!expected_primary.has_value()) + { + LOG(error) << "rejecting newview because we have no peers list"; + return false; + } + + if (original_theirs.sender() != expected_primary.value().uuid) + { + LOG(error) << "rejecting newview because it is not from the primary of that view"; + return false; + } + // - does it contain 2f+1 viewchange messages if (static_cast(theirs.viewchange_messages_size()) < 2 * this->max_faulty_nodes() + 1) { @@ -1441,8 +1297,6 @@ pbft::make_newview( pbft_msg newview; newview.set_type(PBFT_MSG_NEWVIEW); newview.set_view(new_view_index); - newview.set_config_hash(this->configurations->newest_prepared()); - newview.set_config(this->configurations->get(this->configurations->newest_prepared())->to_string()); // V is the set of 2f+1 view change messages for (const auto &sender_viewchange_envelope: viewchange_envelopes_from_senders) @@ -1507,14 +1361,8 @@ pbft::build_newview(uint64_t new_view, const std::map& view auto env = this->wrap_message(pre_prepare); if (pre_prepare.request_type() == pbft_request_type::PBFT_REQUEST_PAYLOAD && attach_reqs) { - auto config = this->configurations->get(old_view); - if (!config) - { - LOG(error) << "config not found for view " << old_view; - continue; - } auto op = this->operation_manager->find_or_construct(old_view, pre_prepare.sequence() - , pre_prepare.request_hash(), config->get_peers()); + , pre_prepare.request_hash()); if (op->has_request()) { *(env.add_piggybacked_requests()) = op->get_request(); @@ -1596,14 +1444,8 @@ pbft::save_all_requests(const pbft_msg& msg, const bzn_envelope& original_msg) const uint64_t& pre_prep_view{pp_msg.view()}; - auto config = this->configurations->get(pre_prep_view); - if (!config) - { - LOG(error) << "config not found for view " << pre_prep_view; - continue; - } auto op = this->operation_manager->find_or_construct( - pre_prep_view, pp_msg.sequence(), pp_msg.request_hash(), config->get_peers()); + pre_prep_view, pp_msg.sequence(), pp_msg.request_hash()); op->record_request(request_env); } @@ -1641,9 +1483,10 @@ pbft::handle_viewchange(const pbft_msg& msg, const bzn_envelope& original_msg) const auto viewchange = std::find_if(this->valid_viewchange_messages_for_view.begin() , this->valid_viewchange_messages_for_view.end(), [&](const auto& p) { - return ((this->get_primary(p.first).uuid == this->get_uuid()) && + auto predicted = this->predict_primary(p.first); + return predicted.has_value() && predicted.value().uuid == this->get_uuid() && (p.first > this->view.value()) && - (p.second.size() == 2 * this->max_faulty_nodes() + 1)); + (p.second.size() == 2 * this->max_faulty_nodes() + 1); }); if (viewchange == this->valid_viewchange_messages_for_view.end()) @@ -1660,7 +1503,6 @@ pbft::handle_viewchange(const pbft_msg& msg, const bzn_envelope& original_msg) } auto newview_msg = this->build_newview(viewchange->first, viewchange_envelopes_from_senders); - this->move_to_new_configuration(newview_msg.config_hash(), this->view.value() + 1); LOG(debug) << "Sending NEWVIEW for view " << this->view.value() + 1; this->async_signed_broadcast(newview_msg); } @@ -1668,32 +1510,7 @@ pbft::handle_viewchange(const pbft_msg& msg, const bzn_envelope& original_msg) void pbft::handle_newview(const pbft_msg& msg, const bzn_envelope& original_msg) { - // are we just now joining the swarm? - if (this->in_swarm == swarm_status::waiting) - { - auto newconfig = std::make_shared(); - if (!newconfig->from_string(msg.config())) - { - LOG(debug) << "newview received with invalid configuration"; - return; - } - - auto hash = newconfig->get_hash(); - if (this->configurations->current()->get_hash() != hash) - { - this->configurations->add(newconfig); - - // we need to switch to this configuration now so we have the peer info to validate the message - // TODO: since the config tells us how to validate the NEW_VIEW, but the NEW_VIEW contains the config, we - // can't really trust this. We need to get the config from an external source. - this->move_to_new_configuration(hash, msg.view()); - } - - // KEP-1574: We're unable to do a full newview validation here as we don't have the requests yet. - // Since we're already assuming no byzantine nodes, skip the validation for now. - this->in_swarm = swarm_status::joined; - } - else if (!this->is_valid_newview_message(msg, original_msg)) + if (!this->is_valid_newview_message(msg, original_msg)) { LOG (debug) << "handle_newview - ignoring invalid NEWVIEW message "; return; @@ -1708,19 +1525,12 @@ pbft::handle_newview(const pbft_msg& msg, const bzn_envelope& original_msg) // initially set next sequence from viewchange then update from preprepares later this->next_issued_sequence_number = viewchange.sequence() + 1; - LOG(debug) << "handle_newview - received valid NEWVIEW message"; + this->set_primary_from_newview(original_msg); - // validate requested configuration and switch to it - if (!this->is_configuration_acceptable_in_new_view(msg.config_hash()) || - !this->move_to_new_configuration(msg.config_hash(), msg.view())) - { - LOG(debug) << "unable to switch to configuration in new view"; - return; - } + LOG(debug) << "handle_newview - received valid NEWVIEW message"; this->view = msg.view(); this->view_is_valid = true; - this->new_config_in_flight = false; // after moving to the new view processes the preprepares LOG(debug) << "Processing " << msg.pre_prepare_messages_size() << " pre-prepares"; @@ -1755,7 +1565,21 @@ pbft::get_status() std::lock_guard lock(this->pbft_lock); status_str += "my uuid: " + this->uuid + "\n"; - status_str += "primary: " + this->get_primary(this->get_view()).uuid + "\n"; + + auto primary = this->get_current_primary(); + if (primary.has_value()) + { + status_str += "primary: " + primary.value().uuid + "\n"; + status["primary"]["host"] = primary.value().host; + status["primary"]["host_port"] = primary.value().port; + status["primary"]["name"] = primary.value().name; + status["primary"]["uuid"] = primary.value().uuid; + } + else + { + status_str += "primary unknown\n"; + } + status_str += "view: " + std::to_string(this->get_view()) + "\n"; status_str += "last exec: " + std::to_string(this->last_executed_sequence_number) + "\n"; status_str += "last local cp: " + std::to_string(this->checkpoint_manager->get_latest_local_checkpoint().first) + "\n"; @@ -1769,12 +1593,6 @@ pbft::get_status() status["outstanding_operations_count"] = uint64_t(this->operation_manager->held_operations_count()); status["is_primary"] = this->is_primary(); - auto primary = this->get_primary(); - status["primary"]["host"] = primary.host; - status["primary"]["host_port"] = primary.port; - status["primary"]["name"] = primary.name; - status["primary"]["uuid"] = primary.uuid; - status["latest_stable_checkpoint"]["sequence_number"] = this->checkpoint_manager->get_latest_stable_checkpoint().first; status["latest_stable_checkpoint"]["hash"] = this->checkpoint_manager->get_latest_stable_checkpoint().second; status["latest_checkpoint"]["sequence_number"] = this->checkpoint_manager->get_latest_local_checkpoint().first; @@ -1784,7 +1602,7 @@ pbft::get_status() status["view"] = this->view.value(); status["peer_index"] = bzn::json_message(); - for (const auto& p : this->current_peers()) + for (const auto& p : *this->peers_beacon->current()) { bzn::json_message peer; peer["host"] = p.host; @@ -1797,54 +1615,10 @@ pbft::get_status() return status; } -bool -pbft::initialize_configuration(const bzn::peers_list_t& peers) -{ - bool config_good = true; - - if (!this->configurations->current()) - { - auto config = std::make_shared(); - for (auto &p : peers) - { - config_good &= config->add_peer(p); - } - - if (!config_good) - { - LOG(warning) << "One or more peers could not be added to configuration"; - LOG(warning) << config->get_peers()->size() << " peers were added"; - } - - this->configurations->add(config); - this->configurations->set_current(config->get_hash(), this->view.value()); - } - - return config_good; -} - -std::shared_ptr> -pbft::current_peers_ptr() const -{ - auto config = this->configurations->current(); - if (config) - { - return config->get_peers(); - } - - throw std::runtime_error("No current configuration!"); -} - -const std::vector& -pbft::current_peers() const -{ - return *(this->current_peers_ptr()); -} - const peer_address_t& pbft::get_peer_by_uuid(const std::string& uuid) const { - for (auto const& peer : this->current_peers()) + for (auto const& peer : *this->peers_beacon->current()) { if (peer.uuid == uuid) { @@ -1856,55 +1630,6 @@ pbft::get_peer_by_uuid(const std::string& uuid) const throw std::runtime_error("peer missing from peers list"); } -void -pbft::broadcast_new_configuration(pbft_configuration::shared_const_ptr config, const std::string& join_request_hash) -{ - pbft_config_msg cfg_msg; - cfg_msg.set_configuration(config->to_string()); - cfg_msg.set_join_request_hash(join_request_hash); - bzn_envelope req; - req.set_pbft_internal_request(cfg_msg.SerializeAsString()); - - auto op = this->setup_request_operation(req, this->crypto->hash(req)); - this->do_preprepare(op); -} - -bool -pbft::is_configuration_acceptable_in_new_view(const hash_t& config_hash) -{ - return this->configurations->is_acceptable(config_hash); -} - -void -pbft::handle_config_message(const pbft_msg& msg, const std::shared_ptr& op) -{ - assert(op->has_config_request()); - auto config = std::make_shared(); - if (msg.type() == PBFT_MSG_PREPREPARE && config->from_string(op->get_config_request().configuration())) - { - if (this->proposed_config_is_acceptable(config)) - { - // store this configuration - this->configurations->add(config); - } - } -} - -bool -pbft::move_to_new_configuration(const hash_t& config_hash, uint64_t view) -{ - LOG(debug) << "Moving to config hash: " << config_hash << " in view " << view; - - // TODO: garbage collect old configurations (KEP-1006) - return this->configurations->set_current(config_hash, view); -} - -bool -pbft::proposed_config_is_acceptable(std::shared_ptr /*config*/) -{ - return true; -} - timestamp_t pbft::now() const { @@ -2001,67 +1726,6 @@ pbft::honest_majority_size(size_t swarm_size) return pbft::faulty_nodes_bound(swarm_size) * 2 + 1; } -void -pbft::join_swarm() -{ - // are we already in the peers list? - if (this->is_peer(this->uuid)) - { - this->in_swarm = swarm_status::joined; - return; - } - - pbft_membership_msg join_msg; - join_msg.set_type(PBFT_MMSG_JOIN); - join_msg.mutable_peer_info()->set_host(this->options->get_listener().address().to_string()); - join_msg.mutable_peer_info()->set_port(this->options->get_listener().port()); - join_msg.mutable_peer_info()->set_uuid(this->uuid); - - // is_peer checks against uuid only, we need to bail if the list contains a node with the same IP and port, - // So, check the peers list for node with same ip and port and post error and bail if found. - const auto bad_peer = std::find_if( - std::begin(this->current_peers()) - , std::end(this->current_peers()) - , [&](const bzn::peer_address_t& address) - { - return address.port == join_msg.peer_info().port() && address.host == join_msg.peer_info().host(); - }); - - if (bad_peer != std::end(this->current_peers())) - { - LOG (error) << "Bootstrap configuration file validation failure - peer with UUID: " << bad_peer->uuid << " hides local peer"; - throw std::runtime_error("Bad peer found in Bootstrap Configuration file"); - } - - uint32_t selected = this->generate_random_number(0, this->current_peers().size() - 1); - - LOG(info) << "Sending request to join swarm to node " << this->current_peers()[selected].uuid; - auto msg_ptr = std::make_shared(this->wrap_message(join_msg)); - this->node->send_signed_message(make_endpoint(this->current_peers()[selected]), msg_ptr); - - this->in_swarm = swarm_status::joining; - this->join_retry_timer->expires_from_now(JOIN_RETRY_INTERVAL); - this->join_retry_timer->async_wait( - std::bind(&pbft::handle_join_retry_timeout, shared_from_this(), std::placeholders::_1)); -} - -void -pbft::handle_join_retry_timeout(const boost::system::error_code& ec) -{ - if (ec == boost::asio::error::operation_aborted) - { - return; - } - - if (ec) - { - LOG(error) << "handle_new_join_retry_timeout error: " << ec.message(); - return; - } - - this->join_swarm(); -} - uint32_t pbft::generate_random_number(uint32_t min, uint32_t max) { @@ -2104,3 +1768,30 @@ pbft::latest_stable_checkpoint() const { return this->checkpoint_manager->get_latest_stable_checkpoint(); } + +std::shared_ptr +pbft::peers() const +{ + return this->peers_beacon; +} + +void +pbft::set_primary_from_newview(const bzn_envelope& new_view) +{ + auto peers = this->peers_beacon->current(); + auto find = std::find_if(peers->begin(), peers->end(), + [&](const auto& peer) + { + return peer.uuid == new_view.sender(); + }); + + if (find == std::end(*peers)) + { + LOG(error) << "Cannot operate in this view because its primary is not in my peers list; voting for view change"; + this->pinned_primary = std::nullopt; + this->initiate_viewchange(); + return; + } + + this->pinned_primary = *find; +} diff --git a/pbft/pbft.hpp b/pbft/pbft.hpp index 6fe7606f..df165325 100644 --- a/pbft/pbft.hpp +++ b/pbft/pbft.hpp @@ -20,7 +20,6 @@ #include #include #include -#include #include #include #include @@ -38,8 +37,6 @@ namespace { const std::chrono::milliseconds HEARTBEAT_INTERVAL{std::chrono::milliseconds(5000)}; - const std::chrono::seconds NEW_CONFIG_INTERVAL{std::chrono::seconds(30)}; - const std::chrono::seconds JOIN_RETRY_INTERVAL{std::chrono::seconds(30)}; const uint64_t CHECKPOINT_INTERVAL = 100; //TODO: KEP-574 const double HIGH_WATER_INTERVAL_IN_CHECKPOINTS = 200.0; //TODO: KEP-574 const uint64_t MAX_REQUEST_AGE_MS = 3600000; // 1 hour @@ -77,7 +74,7 @@ namespace bzn pbft( std::shared_ptr node , std::shared_ptr io_context - , const bzn::peers_list_t& peers + , std::shared_ptr peers , std::shared_ptr options , std::shared_ptr service , std::shared_ptr failure_detector @@ -97,7 +94,9 @@ namespace bzn bool is_primary() const override; - const peer_address_t& get_primary(std::optional view = std::nullopt) const override; + std::optional get_current_primary() const override; + + std::optional predict_primary(uint64_t view) const override; const bzn::uuid_t& get_uuid() const override; @@ -146,10 +145,10 @@ namespace bzn static uint32_t generate_random_number(uint32_t min, uint32_t max); - std::shared_ptr> current_peers_ptr() const override; - checkpoint_t latest_stable_checkpoint() const; + std::shared_ptr peers() const override; + private: bool preliminary_filter_msg(const pbft_msg& msg); @@ -158,8 +157,6 @@ namespace bzn void handle_prepare(const pbft_msg& msg, const bzn_envelope& original_msg); void handle_commit(const pbft_msg& msg, const bzn_envelope& original_msg); void handle_checkpoint(const pbft_msg& msg, const bzn_envelope& original_msg); - void handle_join_or_leave(const bzn_envelope& env, const pbft_membership_msg& msg, std::shared_ptr session, const std::string& msg_hash); - void handle_join_response(const pbft_membership_msg& msg); void handle_get_state(const pbft_membership_msg& msg, std::shared_ptr session) const; void handle_set_state(const pbft_membership_msg& msg); void handle_config_message(const pbft_msg& msg, const std::shared_ptr& op); @@ -197,8 +194,6 @@ namespace bzn void handle_audit_heartbeat_timeout(const boost::system::error_code& ec); - void handle_new_config_timeout(const boost::system::error_code& ec); - void handle_join_retry_timeout(const boost::system::error_code& ec); void notify_audit_failure_detected(); @@ -209,13 +204,6 @@ namespace bzn size_t max_faulty_nodes() const; void initialize_persistent_state(); - bool initialize_configuration(const bzn::peers_list_t& peers); - const std::vector& current_peers() const; - - void broadcast_new_configuration(pbft_configuration::shared_const_ptr config, const std::string& join_request_hash); - bool is_configuration_acceptable_in_new_view(const hash_t& config_hash); - bool move_to_new_configuration(const hash_t& config_hash, uint64_t view); - bool proposed_config_is_acceptable(std::shared_ptr config); void maybe_record_request(const bzn_envelope &env, const std::shared_ptr &op); @@ -223,8 +211,6 @@ namespace bzn bool already_seen_request(const bzn_envelope& msg, const request_hash_t& hash) const; void saw_request(const bzn_envelope& msg, const request_hash_t& hash); - void join_swarm(); - // VIEWCHANGE/NEWVIEW Helper methods void initiate_viewchange(std::optional opt_view = std::nullopt); std::shared_ptr make_viewchange(uint64_t new_view, uint64_t n, const std::unordered_map& stable_checkpoint_proof, const std::map>& prepared_operations); @@ -241,6 +227,7 @@ namespace bzn std::map map_request_to_hash(const bzn_envelope& original_msg); void save_all_requests(const pbft_msg& msg, const bzn_envelope& original_msg); + void set_primary_from_newview(const bzn_envelope& env); std::shared_ptr storage; @@ -267,16 +254,9 @@ namespace bzn const std::shared_ptr io_context; std::unique_ptr audit_heartbeat_timer; - std::unique_ptr new_config_timer; - std::unique_ptr join_retry_timer; bool audit_enabled = true; - enum class swarm_status {not_joined, joining, waiting, joined}; - swarm_status in_swarm = swarm_status::not_joined; - - bool new_config_in_flight = false; - std::multimap> recent_requests; std::shared_ptr crypto; @@ -288,8 +268,10 @@ namespace bzn std::map>> valid_viewchange_messages_for_view; // set of bzn_envelope, strings since we cannot have a set bzn_envelope saved_newview; + std::optional pinned_primary; + std::shared_ptr operation_manager; - std::shared_ptr configurations; + std::shared_ptr peers_beacon; std::shared_ptr checkpoint_manager; std::shared_ptr monitor; @@ -321,7 +303,6 @@ namespace bzn FRIEND_TEST(bzn::test::pbft_test, ensure_save_all_requests_records_requests); friend class pbft_proto_test; - friend class pbft_join_leave_test; friend class pbft_viewchange_test; std::map> sessions_waiting_on_forwarded_requests; diff --git a/pbft/pbft_base.hpp b/pbft/pbft_base.hpp index 32b1781c..fb1773b2 100644 --- a/pbft/pbft_base.hpp +++ b/pbft/pbft_base.hpp @@ -14,8 +14,8 @@ #pragma once -#include -#include +#include +#include #include #include #include @@ -38,7 +38,9 @@ namespace bzn virtual bool is_primary() const = 0; - virtual const peer_address_t& get_primary(std::optional view) const = 0; + virtual std::optional get_current_primary() const = 0; + + virtual std::optional predict_primary(uint64_t view) const = 0; virtual const bzn::uuid_t& get_uuid() const = 0; @@ -46,7 +48,7 @@ namespace bzn virtual const peer_address_t& get_peer_by_uuid(const std::string& uuid) const = 0; - virtual std::shared_ptr> current_peers_ptr() const = 0; + virtual std::shared_ptr peers() const = 0; virtual ~pbft_base() = default; diff --git a/pbft/pbft_checkpoint_manager.cpp b/pbft/pbft_checkpoint_manager.cpp index 7b98977f..8a4c0021 100644 --- a/pbft/pbft_checkpoint_manager.cpp +++ b/pbft/pbft_checkpoint_manager.cpp @@ -30,12 +30,12 @@ using namespace bzn; pbft_checkpoint_manager::pbft_checkpoint_manager( std::shared_ptr io_context, std::shared_ptr storage, - std::shared_ptr config_store, + std::shared_ptr peers_beacon, std::shared_ptr node ) : io_context(std::move(io_context)) , storage(std::move(storage)) - , config_store(std::move(config_store)) + , peers_beacon(std::move(peers_beacon)) , node(std::move(node)) { this->init_persists(); @@ -86,7 +86,7 @@ pbft_checkpoint_manager::local_checkpoint_reached(const bzn::checkpoint_t& cp) msg.set_checkpoint_msg(cp_msg.SerializeAsString()); auto msg_ptr = std::make_shared(msg); - for (const auto& peer : *(this->config_store->current()->get_peers())) + for (const auto& peer : *(this->peers_beacon->current())) { this->node->send_signed_message(make_endpoint(peer), msg_ptr); } @@ -158,7 +158,7 @@ pbft_checkpoint_manager::handle_checkpoint_message(const bzn_envelope& msg, std: void pbft_checkpoint_manager::maybe_stabilize_checkpoint(const checkpoint_t& cp) { - auto peers = this->config_store->current()->get_peers(); + auto peers = this->peers_beacon->current(); // how many messages do we have supporting this checkpoint from peers in the current config? size_t checkpoint_attestants = std::count_if(this->partial_checkpoint_proofs[cp].begin(), this->partial_checkpoint_proofs[cp].end(), diff --git a/pbft/pbft_checkpoint_manager.hpp b/pbft/pbft_checkpoint_manager.hpp index e6300df4..001d30e5 100644 --- a/pbft/pbft_checkpoint_manager.hpp +++ b/pbft/pbft_checkpoint_manager.hpp @@ -17,7 +17,7 @@ #include #include #include -#include +#include #include namespace bzn @@ -37,7 +37,7 @@ class pbft_checkpoint_manager : public std::enable_shared_from_this io_context, std::shared_ptr storage, - std::shared_ptr config_store, + std::shared_ptr peers_beacon, std::shared_ptr node); void handle_checkpoint_message(const bzn_envelope& msg, std::shared_ptr /*session*/ = nullptr); @@ -63,7 +63,7 @@ class pbft_checkpoint_manager : public std::enable_shared_from_this io_context; std::shared_ptr storage; - std::shared_ptr config_store; + std::shared_ptr peers_beacon; std::shared_ptr node; std::unique_ptr trigger_catchup_timer; diff --git a/pbft/pbft_config_store.cpp b/pbft/pbft_config_store.cpp deleted file mode 100644 index 3231382d..00000000 --- a/pbft/pbft_config_store.cpp +++ /dev/null @@ -1,245 +0,0 @@ -// Copyright (C) 2018 Bluzelle -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License, version 3, -// as published by the Free Software Foundation. -// -// 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 - -using namespace bzn; - -pbft_config_store::pbft_config_store(std::shared_ptr storage) -: storage(storage) -{ - persistent::init_kv_container(this->storage, CONFIG_STORE_CONFIGS_KEY, this->configs); - persistent::init_kv_container(this->storage, CONFIG_STORE_VIEW_CONFIGS_KEY, this->view_configs); -} - -void -pbft_config_store::add(pbft_configuration::shared_const_ptr config) -{ - std::lock_guard lock(this->lock); - this->index = this->index.value() + 1; - auto res = this->configs.insert({config->get_hash() - , {this->storage, {this->index.value(), config}, CONFIG_STORE_CONFIGS_KEY, config->get_hash()}}); - if (!res.second) - { - // reset the configuration info without losing its previous view(s) - res.first->second = {this->index.value(), config, pbft_config_state::accepted, res.first->second.value().views}; - } -} - -pbft_configuration::shared_const_ptr -pbft_config_store::get(const hash_t& hash) const -{ - std::lock_guard lock(this->lock); - return this->private_get(hash); -} - -pbft_configuration::shared_const_ptr -pbft_config_store::private_get(const hash_t& hash) const -{ - auto it = this->configs.find(hash); - return it == this->configs.end() ? nullptr : it->second.value().config; -} - -pbft_configuration::shared_const_ptr -pbft_config_store::get(uint64_t view) const -{ - std::lock_guard lock(this->lock); - auto it = this->view_configs.find(view); - return it == this->view_configs.end() ? nullptr : this->private_get(it->second.value()); -} - -bool -pbft_config_store::set_prepared(const hash_t& hash) -{ - std::lock_guard lock(this->lock); - return this->set_state(hash, pbft_config_state::prepared); -} - -bool -pbft_config_store::set_committed(const hash_t& hash) -{ - std::lock_guard lock(this->lock); - if (this->set_state(hash, pbft_config_state::committed)) - { - // when a configuration is committed, we won't accept new_view with any earlier config - for (auto& elem : this->configs) - { - auto it = this->configs.find(hash); - assert(it != this->configs.end()); - - if (elem.second.value().state != pbft_config_state::current && elem.second.value().index - < it->second.value().index) - { - this->set_state(elem.first, pbft_config_state::deprecated); - } - } - - return true; - } - - return false; -} - -bool -pbft_config_store::set_current(const hash_t& hash, uint64_t view) -{ - std::lock_guard lock(this->lock); - if (this->view_configs.find(view) != this->view_configs.end()) - { - if (this->view_configs[view].value() == hash) - { - return true; - } - LOG(error) << "Attempt to set configuration for a view that already has one: " << view; - return false; - } - - auto it = this->configs.find(hash); - if (it != this->configs.end()) - { - auto mutable_config{it->second.value()}; - mutable_config.state = pbft_config_state::current; - mutable_config.views.insert(view); - it->second = mutable_config; - - this->view_configs[view] = persistent{this->storage, hash, CONFIG_STORE_VIEW_CONFIGS_KEY, view}; - this->current_config = hash; - return true; - } - - return false; -} - -pbft_configuration::shared_const_ptr -pbft_config_store::current() const -{ - std::lock_guard lock(this->lock); - auto it = this->configs.find(this->current_config.value()); - return it == this->configs.end() ? nullptr : it->second.value().config; -} - -hash_t -pbft_config_store::newest_prepared() const -{ - std::lock_guard lock(this->lock); - return this->newest({pbft_config_state::prepared, pbft_config_state::committed, pbft_config_state::current}); -} - -hash_t -pbft_config_store::newest_committed() const -{ - std::lock_guard lock(this->lock); - return this->newest({pbft_config_state::committed, pbft_config_state::current}); -} - -pbft_config_store::pbft_config_state -pbft_config_store::get_state(const hash_t& hash) const -{ - auto it = this->configs.find(hash); - return it == this->configs.end() ? pbft_config_state::unknown : it->second.value().state; -} - -bool pbft_config_store::is_acceptable(const hash_t& hash) const -{ - std::lock_guard lock(this->lock); - return this->get_state(hash) != pbft_config_state::unknown && this->get_state(hash) != pbft_config_state::deprecated; -} - -bool -pbft_config_store::set_state(const hash_t& hash, pbft_config_state state) -{ - auto it = this->configs.find(hash); - if (it != this->configs.end()) - { - auto mutable_config{it->second.value()}; - mutable_config.state = state; - it->second = mutable_config; - return true; - } - return false; -} - -hash_t -pbft_config_store::newest(const std::list& states) const -{ - hash_t hash; - uint64_t max{0}; - for (auto elem : this->configs) - { - if (elem.second.value().index > max && std::any_of(states.begin(), states.end(), [elem](auto state) - { - return elem.second.value().state == state; - })) - { - max = elem.second.value().index; - hash = elem.first; - } - } - - return hash; -} - -template <> -std::string -persistent::to_string(const pbft_config_store::config_info& info) -{ - bzn::json_message json; - json["index"] = info.index; - json["config"] = info.config->to_json(); - json["state"] = static_cast(info.state); - - json["views"] = bzn::json_message(); - for (auto view : info.views) - { - json["views"].append(view); - } - - return json.toStyledString(); -} - -template <> -pbft_config_store::config_info -persistent::from_string(const std::string& value) -{ - Json::Value json; - Json::Reader reader; - if (reader.parse(value, json)) - { - if (json.isMember("index") && json.isMember("config") && json.isMember("state")) - { - try - { - pbft_config_store::config_info info; - info.index = json["index"].asUInt64(); - - pbft_configuration cfg; - cfg.from_json(json["config"]); - info.config = std::make_shared(cfg); - info.state = static_cast(json["state"].asUInt64()); - - for (auto& v : json["views"]) - { - info.views.insert(v.asUInt64()); - } - - return info; - } - catch(...) - {} - } - } - - LOG(error) << "bad config_info from persistent state"; - throw std::runtime_error("bad config_info from persistent state"); -} diff --git a/pbft/pbft_config_store.hpp b/pbft/pbft_config_store.hpp deleted file mode 100644 index a6a662da..00000000 --- a/pbft/pbft_config_store.hpp +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright (C) 2018 Bluzelle -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License, version 3, -// as published by the Free Software Foundation. -// -// 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 . - -#pragma once - -#include -#include -#include -#include -#include -#include -#include - -namespace -{ - std::string CONFIG_STORE_CONFIGS_KEY{"config_store_configs"}; - std::string CONFIG_STORE_VIEW_CONFIGS_KEY{"config_store_view_configs"}; - std::string CONFIG_STORE_CURRENT_CONFIG_KEY{"config_store_current_config"}; - std::string CONFIG_STORE_INDEX_KEY{"config_store_index"}; -} - -namespace bzn -{ - using hash_t = std::string; - - class pbft_config_store - { - public: - pbft_config_store(std::shared_ptr storage); - - // add a new accepted configuration to storage - void add(pbft_configuration::shared_const_ptr config); - - // get the configuration with the given hash - pbft_configuration::shared_const_ptr get(const hash_t& hash) const; - - // get the configuration applicable to the given view number (could be archived) - pbft_configuration::shared_const_ptr get(uint64_t view) const; - - // set the configuration with the given hash to be prepared - bool set_prepared(const hash_t& hash); - - // set the configuration with the given hash to be committed - bool set_committed(const hash_t& hash); - - // set the configuration with the given hash to be the currently active one - bool set_current(const hash_t& hash, uint64_t view); - - // get the configuration marked as current - pbft_configuration::shared_const_ptr current() const; - - // returns the most recent configuration that is prepared, committed or current - hash_t newest_prepared() const; - - // returns the most recent configuration that is committed or current - hash_t newest_committed() const; - - // is the given configuration in a state that can be accepted? - bool is_acceptable(const hash_t& hash) const; - - enum class pbft_config_state {unknown, accepted, prepared, committed, current, deprecated}; - - struct config_info - { - uint64_t index = 0; - pbft_configuration::shared_const_ptr config; - pbft_config_state state = pbft_config_state::accepted; - std::set views{}; - }; - - private: - std::shared_ptr storage; - pbft_config_state get_state(const hash_t& hash) const; - bool set_state(const hash_t& hash, pbft_config_state state); - hash_t newest(const std::list& states) const; - pbft_configuration::shared_const_ptr private_get(const hash_t& hash) const; - - std::map> configs; - std::map> view_configs; - persistent current_config{storage, "", CONFIG_STORE_CURRENT_CONFIG_KEY}; - persistent index {storage, 0, CONFIG_STORE_INDEX_KEY}; - - mutable std::mutex lock; - - FRIEND_TEST(pbft_config_store_test, state_test); - }; -} - diff --git a/pbft/pbft_configuration.cpp b/pbft/pbft_configuration.cpp deleted file mode 100644 index 0d5963ad..00000000 --- a/pbft/pbft_configuration.cpp +++ /dev/null @@ -1,270 +0,0 @@ -// Copyright (C) 2018 Bluzelle -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License, version 3, -// as published by the Free Software Foundation. -// -// 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 "pbft_configuration.hpp" -#include -#include -#include -#include -#include -#include -#include - -using namespace bzn; - -pbft_configuration::pbft_configuration() -{ - this->sorted_peers = std::make_shared>(); - this->cached_hash = this->create_hash(); -} - -bool -pbft_configuration::operator==(const pbft_configuration& other) const -{ - return this->get_hash() == other.get_hash(); -} - -bool -pbft_configuration::operator!=(const pbft_configuration& other) const -{ - return !(this->operator==(other)); -} - -bool -pbft_configuration::from_string(const std::string& str) -{ - Json::Value json; - Json::Reader reader; - if (!reader.parse(str, json)) - { - LOG(error) << "Unable to parse configuration: " << str.substr(0, MAX_MESSAGE_SIZE) << "..."; - return false; - } - - return this->from_json(json); -} - -std::string -pbft_configuration::to_string() const -{ - return this->to_json().toStyledString(); -} - -bool -pbft_configuration::from_json(const Json::Value& json) -{ - if (!json.isMember("peers")/* || !json["peers"].isArray()*/) - { - LOG(error) << "Invalid configuration: " << json.toStyledString().substr(0, MAX_MESSAGE_SIZE) << "..."; - return false; - } - - bool result = true; - this->peers.clear(); - for (auto& p : json["peers"]) - { - bzn::peer_address_t peer(p["host"].asString(), static_cast(p["port"].asUInt()), - p["name"].asString(), p["uuid"].asString()); - - if (this->conflicting_peer_exists(peer) || !this->valid_peer(peer)) - { - LOG(warning) << "Attempt to add conflicting or invalid peer: " - << json.toStyledString().substr(0, MAX_MESSAGE_SIZE) << "..."; - result = false; - } - else - { - this->peers.insert(peer); - } - } - - this->cache_sorted_peers(); - return result; -} - -Json::Value -pbft_configuration::to_json() const -{ - bzn::json_message json; - json["peers"] = bzn::json_message(); - for(const auto& p : *(this->sorted_peers)) - { - bzn::json_message peer; - peer["host"] = p.host; - peer["port"] = p.port; - peer["name"] = p.name; - peer["uuid"] = p.uuid; - - json["peers"].append(peer); - } - - return json; -} - -hash_t -pbft_configuration::create_hash() const -{ - // TODO: better hash function - - return std::to_string(std::hash{}(this->to_string())); -} - -hash_t -pbft_configuration::get_hash() const -{ - return cached_hash; -} - -std::shared_ptr> -pbft_configuration::get_peers() const -{ - return this->sorted_peers; -} - -bool -pbft_configuration::insert_peer(const bzn::peer_address_t& peer) -{ - if (this->conflicting_peer_exists(peer) || !this->valid_peer(peer)) - { - return false; - } - - this->peers.insert(peer); - return true; -} - -bool -pbft_configuration::add_peer(const bzn::peer_address_t& peer) -{ - if (!this->insert_peer(peer)) - { - return false; - } - - this->cache_sorted_peers(); - return true; -} - -bool -pbft_configuration::remove_peer(const bzn::peer_address_t& peer) -{ - auto p = this->peers.find(peer); - if (p != this->peers.end()) - { - this->peers.erase(p); - this->cache_sorted_peers(); - return true; - } - - return false; -} - -void -pbft_configuration::cache_sorted_peers() -{ - // peer_address_t contains const members so sorting requires some juggling. - // first copy peers into a vector so we can access them via an index - std::vector unordered_peers_list; - std::copy(this->peers.begin(), this->peers.end(), std::back_inserter(unordered_peers_list)); - - // now create a vector of indices from 1..n - std::vector indicies(peers.size()); - std::iota(indicies.begin(), indicies.end(), 0); - - // now sort the indices based on the peer uuids in the vector - std::sort(indicies.begin(), indicies.end(), - [&unordered_peers_list](const auto& i1, const auto& i2) - { - return unordered_peers_list[i1].uuid < unordered_peers_list[i2].uuid; - } - ); - - // and finally get each peer sequentially by sorted index and add to the destination vector - auto sorted = std::make_shared>(); - std::transform(indicies.begin(), indicies.end(), std::back_inserter(*sorted), - [&unordered_peers_list](auto& peer_index) - { - return unordered_peers_list[peer_index]; - } - ); - - this->sorted_peers = std::move(sorted); - this->cached_hash = this->create_hash(); -} - -bool -pbft_configuration::conflicting_peer_exists(const bzn::peer_address_t &peer) const -{ - for (auto& p : this->peers) - { - if (p.uuid == peer.uuid) - { - LOG(debug) << "rejecting peer with duplicate uuid: " << peer.uuid; - return true; - } - - if (p.host == peer.host) - { - if (p.port == peer.port) - { - LOG(debug) << boost::format("rejecting peer with duplicate port: %1%/%2%") - % p.port % peer.port; - return true; - } - } - } - - return false; -} - -bool -pbft_configuration::valid_peer(const bzn::peer_address_t &peer) const -{ - if (peer.uuid.empty() || peer.host.empty() || !peer.port) - { - return false; - } - - // TODO: validate host address? - - return true; -} - -std::pair>, std::shared_ptr>> -pbft_configuration::diff(const pbft_configuration& other) const -{ - auto added = std::make_shared>(); - auto removed = std::make_shared>(); - - auto const& mine = this->peers; - auto const& theirs = other.peers; - - for (auto& p : mine) - { - if (theirs.find(p) == theirs.end()) - { - removed->push_back(p); - } - } - - for (auto& p : theirs) - { - if (mine.find(p) == mine.end()) - { - added->push_back(p); - } - } - - return std::make_pair(added, removed); -} diff --git a/pbft/pbft_configuration.hpp b/pbft/pbft_configuration.hpp deleted file mode 100644 index 1dd0bbb1..00000000 --- a/pbft/pbft_configuration.hpp +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright (C) 2018 Bluzelle -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License, version 3, -// as published by the Free Software Foundation. -// -// 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 . - -#pragma once - -#include -#include -#include -#include -#include -#include - -namespace bzn -{ - using hash_t = std::string; - - class pbft_configuration - { - public: - using shared_const_ptr = std::shared_ptr; - - pbft_configuration(); - bool operator==(const pbft_configuration& other) const; - bool operator!=(const pbft_configuration& other) const; - - // de-serialize from string - returns true for success - bool from_string(const std::string& str); - - // serialize to string - std::string to_string() const; - - // de-serialize from json - returns true for success - bool from_json(const Json::Value& json); - - // serialize to json - Json::Value to_json() const; - - // returns the hash of this configuration - hash_t get_hash() const; - - // returns a sorted vector of peers - std::shared_ptr> get_peers() const; - - // add a new peer - returns true if success, false if invalid or duplicate peer - bool add_peer(const bzn::peer_address_t& peer); - - // removes an existing peer - returns true if found and removed - bool remove_peer(const bzn::peer_address_t& peer); - - // computes differences between this config and another - // returns pair of vectors of added and removed peers - std::pair>, std::shared_ptr>> - diff(const pbft_configuration& other) const; - - private: - void cache_sorted_peers(); - bool conflicting_peer_exists(const bzn::peer_address_t &peer) const; - bool valid_peer(const bzn::peer_address_t &peer) const; - bool insert_peer(const bzn::peer_address_t& peer); - hash_t create_hash() const; - - std::unordered_set peers; - std::shared_ptr> sorted_peers; - hash_t cached_hash; - }; -} diff --git a/pbft/test/CMakeLists.txt b/pbft/test/CMakeLists.txt index d2f0b18b..670f9b79 100644 --- a/pbft/test/CMakeLists.txt +++ b/pbft/test/CMakeLists.txt @@ -4,9 +4,6 @@ set(test_srcs pbft_audit_test.cpp pbft_test_common.cpp pbft_checkpoint_tests.cpp - pbft_configuration_test.cpp - pbft_config_store_test.cpp - pbft_join_leave_test.cpp pbft_proto_test.cpp pbft_catchup_test.cpp pbft_timestamp_test.cpp @@ -14,7 +11,8 @@ set(test_srcs pbft_proto_test.cpp pbft_newview_test.cpp pbft_persistent_state_test.cpp - pbft_viewchange_test.cpp) -set(test_libs pbft pbft_operations crypto options ${Protobuf_LIBRARIES} bootstrap storage ${ROCKSDB_LIBRARIES} smart_mocks) + pbft_viewchange_test.cpp + pbft_peer_change_test.cpp) +set(test_libs pbft pbft_operations crypto options ${Protobuf_LIBRARIES} storage ${ROCKSDB_LIBRARIES} smart_mocks) add_gmock_test(pbft) diff --git a/pbft/test/database_pbft_service_test.cpp b/pbft/test/database_pbft_service_test.cpp index d0ade555..8ef651cd 100644 --- a/pbft/test/database_pbft_service_test.cpp +++ b/pbft/test/database_pbft_service_test.cpp @@ -75,7 +75,7 @@ TEST(database_pbft_service, test_that_failed_storing_of_operation_does_not_throw EXPECT_CALL(*mock_storage, create(_, _, _)).WillOnce(Return(bzn::storage_result::exists)); EXPECT_CALL(*mock_storage, update(_, _, _)).WillOnce(Return(bzn::storage_result::ok)); - auto operation = std::make_shared(0, 1, "somehash", nullptr); + auto operation = std::make_shared(0, 1, "somehash"); database_msg dmsg; bzn_envelope request; request.set_database_msg(dmsg.SerializeAsString()); @@ -98,7 +98,7 @@ TEST(database_pbft_service, test_that_executed_operation_fires_callback_with_ope bzn::database_pbft_service dps(mock_io_context, mem_storage, mock_crud, mock_monitor, TEST_UUID); - auto operation = std::make_shared(0, 1, "somehash", nullptr); + auto operation = std::make_shared(0, 1, "somehash"); bool execute_handler_called_with_operation = false; database_msg msg; @@ -179,7 +179,7 @@ TEST(database_pbft_service, test_that_stored_operation_is_executed_in_order_and_ msg.mutable_create()->set_key("key2"); msg.mutable_create()->set_value("value2"); - auto operation2 = std::make_shared(0, 2, "somehasha", nullptr); + auto operation2 = std::make_shared(0, 2, "somehasha"); bzn_envelope env; env.set_database_msg(msg.SerializeAsString()); operation2->record_request(env); @@ -194,7 +194,7 @@ TEST(database_pbft_service, test_that_stored_operation_is_executed_in_order_and_ auto mock_session = std::make_shared(); EXPECT_CALL(*mock_session, is_open()).WillRepeatedly(Return(true)); - auto operation3 = std::make_shared(0, 3, "somehashb", nullptr); + auto operation3 = std::make_shared(0, 3, "somehashb"); env.set_database_msg(msg.SerializeAsString()); operation3->record_request(env); operation3->set_session(mock_session); @@ -207,7 +207,7 @@ TEST(database_pbft_service, test_that_stored_operation_is_executed_in_order_and_ msg.mutable_create()->set_key("key1"); msg.mutable_create()->set_value("value1"); - auto operation1 = std::make_shared(0, 1, "somehashc", nullptr); + auto operation1 = std::make_shared(0, 1, "somehashc"); env.set_database_msg(msg.SerializeAsString()); operation1->record_request(env); auto session2 = std::make_shared(); @@ -265,7 +265,7 @@ namespace test msg.mutable_create()->set_key("key" + std::to_string(seq)); msg.mutable_create()->set_value("value" + std::to_string(seq)); - auto operation = std::make_shared(0, seq, "somehash" + std::to_string(seq), nullptr); + auto operation = std::make_shared(0, seq, "somehash" + std::to_string(seq)); bzn_envelope env; env.set_database_msg(msg.SerializeAsString()); operation->record_request(env); diff --git a/pbft/test/pbft_catchup_test.cpp b/pbft/test/pbft_catchup_test.cpp index d1848aa8..a42bd7ed 100644 --- a/pbft/test/pbft_catchup_test.cpp +++ b/pbft/test/pbft_catchup_test.cpp @@ -115,7 +115,7 @@ namespace bzn .Times((Exactly(0))); auto nodes = TEST_PEER_LIST.begin(); - size_t req_nodes = 2 * this->faulty_nodes_bound(); + size_t req_nodes = this->faulty_nodes_bound(); for (size_t i = 0; i < req_nodes; i++) { bzn::peer_address_t node(*nodes++); @@ -123,7 +123,7 @@ namespace bzn } // one more checkpoint message and the node should request state from a random node - auto primary = this->pbft->get_primary(); + auto primary = this->pbft->get_current_primary().value(); EXPECT_CALL(*mock_node, send_signed_message(A(), ResultOf(is_get_state, Eq(true)))) .Times((Exactly(1))); @@ -175,7 +175,7 @@ namespace bzn this->build_pbft(); // get the node to request state - auto primary = this->pbft->get_primary(); + auto primary = this->pbft->get_current_primary().value(); EXPECT_CALL(*mock_node, send_signed_message(A(), ResultOf(is_get_state, Eq(true)))) .Times((Exactly(1))); @@ -209,7 +209,7 @@ namespace bzn this->build_pbft(); // get the node to request state - auto primary = this->pbft->get_primary(); + auto primary = this->pbft->get_current_primary().value(); EXPECT_CALL(*mock_node, send_signed_message(A(), ResultOf(is_get_state, Eq(true)))) .Times((Exactly(1))); diff --git a/pbft/test/pbft_checkpoint_tests.cpp b/pbft/test/pbft_checkpoint_tests.cpp index 2d695cb4..359c5648 100644 --- a/pbft/test/pbft_checkpoint_tests.cpp +++ b/pbft/test/pbft_checkpoint_tests.cpp @@ -13,10 +13,10 @@ #include #include +#include #include #include #include -#include #include #include #include @@ -30,25 +30,12 @@ namespace bzn::test public: std::shared_ptr mock_io = std::make_shared(); std::shared_ptr storage = std::make_shared(); - std::shared_ptr configs = std::make_shared(storage); std::shared_ptr node = std::make_shared(); - std::shared_ptr cp_manager = std::make_shared(mock_io, storage, configs, node); + std::shared_ptr cp_manager = std::make_shared(mock_io, storage, static_peers_beacon_for(TEST_PEER_LIST), node); checkpoint_t cp{100, "100"}; checkpoint_t cp2{200, "200"}; - pbft_checkpoint_test() - { - auto config = std::make_shared(); - for (auto &p : TEST_PEER_LIST) - { - config->add_peer(p); - } - - this->configs->add(config); - this->configs->set_current(config->get_hash(), 0); - } - void send_checkpoint_messages(const checkpoint_t& cp, size_t count = INT_MAX) { checkpoint_msg cp_msg; diff --git a/pbft/test/pbft_config_store_test.cpp b/pbft/test/pbft_config_store_test.cpp deleted file mode 100644 index b578f1ad..00000000 --- a/pbft/test/pbft_config_store_test.cpp +++ /dev/null @@ -1,165 +0,0 @@ -// Copyright (C) 2018 Bluzelle -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License, version 3, -// as published by the Free Software Foundation. -// -// 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 - -#include -#include -#include -#include -#include - -using namespace ::testing; - -namespace bzn -{ - - const std::vector TEST_PEER_LIST{{"127.0.0.1", 8081, "name1", "uuid1"}, - {"127.0.0.1", 8082, "name2", "uuid2"}, - {"127.0.0.1", 8083, "name3", "uuid3"}, - {"127.0.0.1", 8084, "name4", "uuid4"}}; - - - class pbft_config_store_test : public Test - { - public: - pbft_config_store_test() - : storage(std::make_shared()) - , store(storage) - {} - - std::shared_ptr storage; - bzn::pbft_config_store store; - }; - - TEST_F(pbft_config_store_test, initially_empty) - { - EXPECT_EQ(this->store.current(), nullptr); - } - - TEST_F(pbft_config_store_test, add_test) - { - auto config = std::make_shared(); - this->store.add(config); - - auto config2 = this->store.get(config->get_hash()); - ASSERT_TRUE(config2 != nullptr); - EXPECT_TRUE(*config == *config2); - } - - TEST_F(pbft_config_store_test, current_test) - { - auto config = std::make_shared(); - this->store.add(config); - EXPECT_EQ(this->store.current(), nullptr); - - auto config2 = std::make_shared(); - config2->add_peer(TEST_PEER_LIST[0]); - this->store.add(config2); - EXPECT_EQ(this->store.current(), nullptr); - - EXPECT_TRUE(this->store.set_current(config->get_hash(), 1)); - EXPECT_EQ(*(this->store.current()), *config); - EXPECT_NE(*(this->store.current()), *config2); - - EXPECT_TRUE(this->store.set_current(config2->get_hash(), 2)); - EXPECT_EQ(*(this->store.current()), *config2); - EXPECT_NE(*(this->store.current()), *config); - } - - TEST_F(pbft_config_store_test, state_test) - { - auto config = std::make_shared(); - this->store.add(config); - EXPECT_EQ(this->store.get_state(config->get_hash()), pbft_config_store::pbft_config_state::accepted); - EXPECT_TRUE(this->store.set_prepared(config->get_hash())); - EXPECT_EQ(this->store.get_state(config->get_hash()), pbft_config_store::pbft_config_state::prepared); - EXPECT_TRUE(this->store.set_committed(config->get_hash())); - EXPECT_EQ(this->store.get_state(config->get_hash()), pbft_config_store::pbft_config_state::committed); - EXPECT_TRUE(this->store.set_current(config->get_hash(), 1)); - EXPECT_EQ(this->store.get_state(config->get_hash()), pbft_config_store::pbft_config_state::current); - EXPECT_TRUE(this->store.set_current(config->get_hash(), 2)); - - auto config2 = std::make_shared(); - config2->add_peer(TEST_PEER_LIST[0]); - EXPECT_EQ(this->store.get_state(config2->get_hash()), pbft_config_store::pbft_config_state::unknown); - EXPECT_FALSE(this->store.set_prepared(config2->get_hash())); - EXPECT_FALSE(this->store.set_committed(config2->get_hash())); - EXPECT_FALSE(this->store.set_current(config2->get_hash(), 3)); - - this->store.add(config2); - EXPECT_FALSE(this->store.set_current(config2->get_hash(), 2)); - EXPECT_TRUE(this->store.set_current(config2->get_hash(), 3)); - EXPECT_EQ(this->store.get_state(config2->get_hash()), pbft_config_store::pbft_config_state::current); - EXPECT_EQ(this->store.current(), config2); - EXPECT_EQ(this->store.get_state(config->get_hash()), pbft_config_store::pbft_config_state::current); - EXPECT_NE(this->store.current(), config); - - EXPECT_EQ(this->store.get(1)->get_hash(), config->get_hash()); - EXPECT_EQ(this->store.get(2)->get_hash(), config->get_hash()); - EXPECT_EQ(this->store.get(3)->get_hash(), config2->get_hash()); - } - - TEST_F(pbft_config_store_test, newest_test) - { - bzn::hash_t empty_hash; - - auto config1 = std::make_shared(); - this->store.add(config1); - auto config2 = std::make_shared(); - config2->add_peer(TEST_PEER_LIST[0]); - this->store.add(config2); - auto config3 = std::make_shared(*config2); - config3->add_peer(TEST_PEER_LIST[1]); - this->store.add(config3); - - EXPECT_EQ(this->store.newest_prepared(), empty_hash); - EXPECT_EQ(this->store.newest_committed(), empty_hash); - EXPECT_EQ(this->store.current(), nullptr); - - EXPECT_TRUE(this->store.set_prepared(config1->get_hash())); - EXPECT_EQ(this->store.newest_prepared(), config1->get_hash()); - - EXPECT_TRUE(this->store.set_committed(config2->get_hash())); - EXPECT_EQ(this->store.newest_prepared(), config2->get_hash()); - EXPECT_EQ(this->store.newest_committed(), config2->get_hash()); - - EXPECT_TRUE(this->store.set_prepared(config3->get_hash())); - EXPECT_TRUE(this->store.set_current(config3->get_hash(), 1)); - EXPECT_EQ(this->store.newest_prepared(), config3->get_hash()); - EXPECT_EQ(this->store.newest_committed(), config3->get_hash()); - - EXPECT_TRUE(this->store.set_current(config2->get_hash(), 2)); - EXPECT_EQ(this->store.newest_prepared(), config3->get_hash()); - EXPECT_EQ(this->store.newest_committed(), config3->get_hash()); - EXPECT_EQ(this->store.current(), config2); - } - - TEST_F(pbft_config_store_test, old_becomes_new_test) - { - auto config1 = std::make_shared(); - this->store.add(config1); - auto config2 = std::make_shared(); - config2->add_peer(TEST_PEER_LIST[0]); - this->store.add(config2); - - EXPECT_TRUE(this->store.set_prepared(config1->get_hash())); - EXPECT_EQ(this->store.newest_prepared(), config1->get_hash()); - - EXPECT_TRUE(this->store.set_prepared(config2->get_hash())); - EXPECT_EQ(this->store.newest_prepared(), config2->get_hash()); - this->store.add(config1); - EXPECT_EQ(this->store.newest_prepared(), config2->get_hash()); - - EXPECT_TRUE(this->store.set_prepared(config1->get_hash())); - EXPECT_EQ(this->store.newest_prepared(), config1->get_hash()); - } -} diff --git a/pbft/test/pbft_configuration_test.cpp b/pbft/test/pbft_configuration_test.cpp deleted file mode 100644 index ffc59b42..00000000 --- a/pbft/test/pbft_configuration_test.cpp +++ /dev/null @@ -1,137 +0,0 @@ -// Copyright (C) 2018 Bluzelle -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License, version 3, -// as published by the Free Software Foundation. -// -// 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 - -#include -#include -#include - -using namespace ::testing; - -namespace -{ - - const std::vector TEST_PEER_LIST{{"127.0.0.1", 8081, "name1", "uuid1"}, - {"127.0.0.1", 8082, "name2", "uuid2"}, - {"127.0.0.1", 8083, "name3", "uuid3"}, - {"127.0.0.1", 8084, "name4", "uuid4"}}; - - - class pbft_configuration_test : public Test - { - public: - bzn::pbft_configuration cfg; - }; - - - TEST_F(pbft_configuration_test, initially_empty) - { - EXPECT_EQ(this->cfg.get_peers()->size(), 0U); - } - - TEST_F(pbft_configuration_test, single_add_succeeds) - { - EXPECT_TRUE(this->cfg.add_peer(TEST_PEER_LIST[0])); - EXPECT_EQ(this->cfg.get_peers()->size(), 1U); - } - - TEST_F(pbft_configuration_test, remove_succeeds) - { - EXPECT_TRUE(this->cfg.add_peer(TEST_PEER_LIST[0])); - EXPECT_EQ(this->cfg.get_peers()->size(), 1U); - EXPECT_TRUE(this->cfg.remove_peer(TEST_PEER_LIST[0])); - EXPECT_EQ(this->cfg.get_peers()->size(), 0U); - } - - TEST_F(pbft_configuration_test, duplicate_add_fails) - { - EXPECT_TRUE(this->cfg.add_peer(TEST_PEER_LIST[0])); - EXPECT_EQ(this->cfg.get_peers()->size(), 1U); - EXPECT_FALSE(this->cfg.add_peer(TEST_PEER_LIST[0])); - EXPECT_EQ(this->cfg.get_peers()->size(), 1U); - } - - TEST_F(pbft_configuration_test, bad_remove_fails) - { - EXPECT_TRUE(this->cfg.add_peer(TEST_PEER_LIST[0])); - EXPECT_EQ(this->cfg.get_peers()->size(), 1U); - EXPECT_FALSE(this->cfg.remove_peer(TEST_PEER_LIST[1])); - EXPECT_EQ(this->cfg.get_peers()->size(), 1U); - } - - void check_equal(const std::vector &p1, const std::vector &p2) - { - ASSERT_EQ(p1.size(), p2.size()); - for (uint i = 0; i < p1.size(); i++) - { - EXPECT_EQ(p1[i], p2[i]); - } - } - - TEST_F(pbft_configuration_test, sort_order) - { - // add the peers in reverse order (assumes TEST_PEER_LIST is sorted) - for (auto it = TEST_PEER_LIST.rbegin(); it != TEST_PEER_LIST.rend(); it++) - { - EXPECT_TRUE(this->cfg.add_peer(*it)); - } - - auto peers = this->cfg.get_peers(); - check_equal(*peers, TEST_PEER_LIST); - } - - TEST_F(pbft_configuration_test, to_from_json) - { - for (const auto &peer : TEST_PEER_LIST) - { - this->cfg.add_peer(peer); - } - - auto val = this->cfg.to_string(); - bzn::pbft_configuration cfg2; - cfg2.from_string(val); - check_equal(*(cfg2.get_peers()), TEST_PEER_LIST); - } - - TEST_F(pbft_configuration_test, reject_invalid_peer) - { - EXPECT_FALSE(this->cfg.add_peer(bzn::peer_address_t("", 8081, "", "uuid1"))); - EXPECT_FALSE(this->cfg.add_peer(bzn::peer_address_t("127.0.0.1", 0, "", "uuid1"))); - EXPECT_FALSE(this->cfg.add_peer(bzn::peer_address_t("127.0.0.1", 8081, "", ""))); - EXPECT_TRUE(this->cfg.add_peer(bzn::peer_address_t("127.0.0.1", 8081, "", "uuid1"))); - - EXPECT_FALSE(this->cfg.add_peer(bzn::peer_address_t("127.0.0.1", 8082, "", "uuid1"))); - EXPECT_FALSE(this->cfg.add_peer(bzn::peer_address_t("127.0.0.1", 8081, "", "uuid2"))); - EXPECT_TRUE(this->cfg.add_peer(bzn::peer_address_t("127.0.0.1", 8082, "", "uuid2"))); - } - - TEST_F(pbft_configuration_test, diff_test) - { - bzn::pbft_configuration config1; - config1.add_peer(TEST_PEER_LIST[0]); - config1.add_peer(TEST_PEER_LIST[1]); - config1.add_peer(TEST_PEER_LIST[2]); - - bzn::pbft_configuration config2; - config2.add_peer(TEST_PEER_LIST[0]); - config2.add_peer(TEST_PEER_LIST[2]); - config2.add_peer(TEST_PEER_LIST[3]); - - auto diffs = config1.diff(config2); - auto added = diffs.first; - auto removed = diffs.second; - ASSERT_EQ(added->size(), 1U); - EXPECT_EQ(added->front(), TEST_PEER_LIST[3]); - ASSERT_EQ(removed->size(), 1U); - EXPECT_EQ(removed->front(), TEST_PEER_LIST[1]); - } -} diff --git a/pbft/test/pbft_join_leave_test.cpp b/pbft/test/pbft_join_leave_test.cpp deleted file mode 100644 index 89b050cf..00000000 --- a/pbft/test/pbft_join_leave_test.cpp +++ /dev/null @@ -1,746 +0,0 @@ -// Copyright (C) 2018 Bluzelle -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License, version 3, -// as published by the Free Software Foundation. -// -// 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 - -#include -#include -#include -#include -#include - -using namespace ::testing; - -namespace bzn -{ - using namespace bzn::test; - const bzn::peer_address_t new_peer{"127.0.0.1", 8090, "name_new", "uuid_new"}; - const bzn::peer_address_t new_peer2{"127.0.0.1", 8091, "name_new2", "uuid_new2"}; - - std::optional extract_pbft_msg_option(bzn_envelope message) - { - if (message.payload_case() == bzn_envelope::kPbft) - { - pbft_msg pmsg; - if (pmsg.ParseFromString(message.pbft())) - { - return pmsg; - } - } - return {}; - } - - MATCHER_P(message_has_correct_req_hash, req_hash, "") - { - auto pmsg = extract_pbft_msg_option(*arg); - if (pmsg) - { - return pmsg->request_hash() == req_hash; - } - return false; - } - - MATCHER_P(message_has_correct_pbft_type, pbft_type, "") - { - auto pmsg = extract_pbft_msg_option(*arg); - if (pmsg) - { - return pmsg->type() == pbft_type; - } - return false; - } - - MATCHER_P(message_has_req_with_correct_type, req_type, "") - { - // note: arg is a bzn_envelope - auto pmsg = extract_pbft_msg_option(*arg); - if (pmsg->has_request()) - { - return pmsg->request().payload_case() == req_type; - } - else if (arg->piggybacked_requests_size()) - { - for (int i{0}; i < arg->piggybacked_requests_size(); ++i) - { - const auto request{arg->piggybacked_requests(i)}; - if (request.payload_case() == req_type) - { - return true; - } - } - } - return false; - } - - MATCHER(message_is_join_response, "") - { - bzn_envelope env; - if (env.ParseFromString(*arg)) - { - pbft_membership_msg msg; - return msg.ParseFromString(env.pbft_membership()) && msg.type() == PBFT_MMSG_JOIN_RESPONSE; - } - return false; - } - - class pbft_join_leave_test : public pbft_test - { - protected: - - pbft_msg - send_new_config_preprepare(std::shared_ptr pbft, std::shared_ptr mock_node, - const bzn::pbft_configuration &config) - { - // make and "send" a pre-prepare message for a new_config - pbft_config_msg cfg_msg; - cfg_msg.set_configuration(config.to_string()); - - pbft_msg preprepare; - preprepare.set_view(1); - preprepare.set_sequence(1); - preprepare.set_type(PBFT_MSG_PREPREPARE); - preprepare.set_request_type(pbft_request_type::PBFT_REQUEST_INTERNAL); - preprepare.mutable_request()->set_pbft_internal_request(cfg_msg.SerializeAsString()); - - auto monitor = std::make_shared>(); - auto crypto = std::make_shared(std::make_shared(), monitor); - auto expect_hash = crypto->hash(preprepare.request()); - preprepare.set_request_hash(expect_hash); - - // receiving node should send out prepare messsage to everyone - for (auto const &p : TEST_PEER_LIST) - { - EXPECT_CALL(*(mock_node), - send_signed_message(bzn::make_endpoint(p), - AllOf( - message_has_correct_req_hash(expect_hash) , - message_has_correct_pbft_type(PBFT_MSG_PREPARE)))) - .Times(Exactly(1)); - } - - auto wmsg = wrap_pbft_msg(preprepare, "uuid0"); - pbft->handle_message(preprepare, wmsg); - return preprepare; - } - - void - send_new_config_prepare(std::shared_ptr pbft, bzn::peer_address_t node, std::shared_ptr op) - { - pbft_msg prepare; - prepare.set_view(op->get_view()); - prepare.set_sequence(op->get_sequence()); - prepare.set_type(PBFT_MSG_PREPARE); - prepare.set_request_hash(op->get_request_hash()); - - auto wmsg = wrap_pbft_msg(prepare, node.uuid); - pbft->handle_message(prepare, wmsg); - } - - void - send_new_config_commit(std::shared_ptr pbft, bzn::peer_address_t node, std::shared_ptr op) - { - pbft_msg commit; - commit.set_view(op->get_view()); - commit.set_sequence(op->get_sequence()); - commit.set_type(PBFT_MSG_COMMIT); - commit.set_request_hash(op->get_request_hash()); - - auto wmsg = wrap_pbft_msg(commit, node.uuid); - pbft->handle_message(commit, wmsg); - } - - void handle_membership_message(const bzn_envelope &msg, std::shared_ptr session = nullptr) - { - return this->pbft->handle_membership_message(msg, session); - } - - std::shared_ptr configurations(std::shared_ptr the_pbft) - { - return the_pbft->configurations; - } - - bool is_configuration_acceptable_in_new_view(hash_t config_hash) - { - return this->pbft->is_configuration_acceptable_in_new_view(config_hash); - } - - std::shared_ptr find_operation(const pbft_msg &msg) - { - return this->operation_manager->find_or_construct(msg, this->pbft->current_peers_ptr()); - } - - bool move_to_new_configuration(hash_t config_hash, uint64_t view) - { - return this->pbft->move_to_new_configuration(config_hash, view); - } - - void handle_preprepare(const pbft_msg& msg, const bzn_envelope& original_msg) - { - return this->pbft->handle_preprepare(msg, original_msg); - } - - void handle_prepare(const pbft_msg& msg, const bzn_envelope& original_msg) - { - return this->pbft->handle_prepare(msg, original_msg); - } - - void handle_commit(const pbft_msg& msg, const bzn_envelope& original_msg) - { - return this->pbft->handle_commit(msg, original_msg); - } - - void handle_viewchange(std::shared_ptr the_pbft, const pbft_msg& msg, const bzn_envelope& original_msg) - { - the_pbft->handle_viewchange(msg, original_msg); - } - - void handle_newview(std::shared_ptr the_pbft, const bzn_envelope& original_msg) - { - pbft_msg msg; - ASSERT_TRUE(msg.ParseFromString(original_msg.pbft())); - the_pbft->handle_newview(msg, original_msg); - } - - void set_swarm_status_waiting(std::shared_ptr the_pbft) - { - the_pbft->in_swarm = pbft::swarm_status::waiting; - } - - bool is_in_swarm(std::shared_ptr the_pbft) - { - return the_pbft->in_swarm == pbft::swarm_status::joined; - } - - const std::vector& current_peers(std::shared_ptr the_pbft) - { - return the_pbft->current_peers(); - } - }; - - TEST_F(pbft_join_leave_test, join_request_generates_new_config_preprepare) - { - this->build_pbft(); - - auto info = new pbft_peer_info; - info->set_host(new_peer.host); - info->set_name(new_peer.name); - info->set_port(new_peer.port); - info->set_uuid(new_peer.uuid); - - pbft_membership_msg join_msg; - join_msg.set_type(PBFT_MMSG_JOIN); - join_msg.set_allocated_peer_info(info); - - // each peer should be sent a pre-prepare for new_config when the join is received - for (auto const &p : TEST_PEER_LIST) - { - EXPECT_CALL(*(this->mock_node), - send_signed_message(bzn::make_endpoint(p), - AllOf(message_has_req_with_correct_type(bzn_envelope::kPbftInternalRequest), - message_has_correct_pbft_type(PBFT_MSG_PREPREPARE)))) - .Times(Exactly(1)); - } - - auto wmsg = wrap_pbft_membership_msg(join_msg, new_peer.uuid); - this->handle_membership_message(wmsg); - - // now there's a config change in flight, another join request should be rejected - auto info2 = new pbft_peer_info(*info); - info2->set_uuid("another_uuid"); - pbft_membership_msg join_msg2; - join_msg2.set_type(PBFT_MMSG_JOIN); - join_msg2.set_allocated_peer_info(info2); - auto wmsg2 = wrap_pbft_membership_msg(join_msg2, info2->uuid()); - EXPECT_CALL(*this->mock_session, close()).Times(Exactly(1)); - this->handle_membership_message(wmsg2, this->mock_session); - } - - TEST_F(pbft_join_leave_test, valid_leave_request_test) - { - this->build_pbft(); - auto const& peer = *(TEST_PEER_LIST.begin()); - auto info = new pbft_peer_info; - info->set_host(peer.host); - info->set_name(peer.name); - info->set_port(peer.port); - info->set_uuid(peer.uuid); - - pbft_membership_msg leave_msg; - leave_msg.set_type(PBFT_MMSG_LEAVE); - leave_msg.set_allocated_peer_info(info); - - // each peer should be sent a pre-prepare for new_config when the leave is received - for (auto const &p : TEST_PEER_LIST) - { - EXPECT_CALL(*(this->mock_node), - send_signed_message(bzn::make_endpoint(p), - AllOf(message_has_req_with_correct_type(bzn_envelope::kPbftInternalRequest), - message_has_correct_pbft_type(PBFT_MSG_PREPREPARE)))) - .Times(Exactly(1)); - } - - auto wmsg = wrap_pbft_membership_msg(leave_msg, peer.uuid); - this->handle_membership_message(wmsg); - } - - TEST_F(pbft_join_leave_test, invalid_leave_request_test) - { - this->build_pbft(); - - auto info = new pbft_peer_info; - info->set_host(new_peer.host); - info->set_name(new_peer.name); - info->set_port(new_peer.port); - info->set_uuid(new_peer.uuid); - - pbft_membership_msg leave_msg; - leave_msg.set_type(PBFT_MMSG_LEAVE); - leave_msg.set_allocated_peer_info(info); - - // leave message should be ignored - EXPECT_CALL(*(this->mock_node), - send_signed_message(Matcher(_), AllOf(message_has_req_with_correct_type(bzn_envelope::kPbftInternalRequest), - message_has_correct_pbft_type(PBFT_MSG_PREPREPARE)))) - .Times(Exactly(0)); - - auto wmsg = wrap_pbft_membership_msg(leave_msg, new_peer.uuid); - this->handle_membership_message(wmsg); - } - - TEST_F(pbft_join_leave_test, test_new_config_preprepare_handling) - { - this->build_pbft(); - - pbft_configuration config; - config.add_peer(new_peer); - - send_new_config_preprepare(pbft, this->mock_node, config); - - // the config should now be stored by this node, but not marked current - ASSERT_NE(this->configurations(this->pbft)->get(config.get_hash()), nullptr); - EXPECT_NE(*(this->configurations(this->pbft)->current()), config); - EXPECT_TRUE(this->is_configuration_acceptable_in_new_view(config.get_hash())); - } - - TEST_F(pbft_join_leave_test, test_new_config_prepare_handling) - { - this->build_pbft(); - - // PREPREPARE step - pbft_configuration config; - config.add_peer(new_peer); - auto msg = send_new_config_preprepare(pbft, this->mock_node, config); - - LOG(info) << bytes_to_debug_string(msg.request_hash()); - - auto op = this->find_operation(msg); - ASSERT_NE(op, nullptr); - - // PREPARE step - auto nodes = TEST_PEER_LIST.begin(); - size_t req_nodes = pbft::honest_majority_size(TEST_PEER_LIST.size()); - for (size_t i = 0; i < req_nodes - 1; i++) - { - bzn::peer_address_t node(*nodes++); - send_new_config_prepare(pbft, node, op); - } - - // the next prepare should trigger a commit message - for (auto const &p : TEST_PEER_LIST) - { - EXPECT_CALL(*(mock_node), - send_signed_message(bzn::make_endpoint(p), - AllOf(message_has_correct_req_hash(msg.request_hash()), - message_has_correct_pbft_type(PBFT_MSG_COMMIT)))) - .Times(Exactly(1)); - } - - bzn::peer_address_t node(*nodes++); - send_new_config_prepare(pbft, node, op); - - // and now the config should be enabled and acceptable, but not current - ASSERT_NE(this->configurations(this->pbft)->get(config.get_hash()), nullptr); - EXPECT_EQ(this->configurations(this->pbft)->newest_prepared(), config.get_hash()); - EXPECT_NE(*(this->configurations(this->pbft)->current()), config); - EXPECT_TRUE(this->is_configuration_acceptable_in_new_view(config.get_hash())); - } - - TEST_F(pbft_join_leave_test, test_new_config_commit_handling) - { - this->build_pbft(); - - // set up a second pbft to be the new primary after a view change - auto mock_node2 = std::make_shared(); - auto mock_io_context2 = std::make_shared>(); - auto audit_heartbeat_timer2 = std::make_unique>(); - auto new_config_timer2 = std::make_unique>(); - auto join_retry_timer2 = std::make_unique>(); - auto mock_service2 = std::make_shared>(); - auto mock_options = std::make_shared(); - auto storage2 = std::make_shared(); - auto manager2 = std::make_shared(storage2); - EXPECT_CALL(*(mock_io_context2), make_unique_steady_timer()) - .Times(AtMost(3)).WillOnce(Invoke([&](){return std::move(audit_heartbeat_timer2);})) - .WillOnce(Invoke([&](){return std::move(new_config_timer2);})) - .WillOnce(Invoke([&](){return std::move(join_retry_timer2);})); - EXPECT_CALL(*mock_io_context2, post(_)).WillRepeatedly(Invoke([](auto func){boost::asio::post(func);})); - EXPECT_CALL(*mock_options, get_swarm_id()).WillRepeatedly(Return("swarm_id")); - EXPECT_CALL(*mock_options, get_uuid()).WillRepeatedly(Return("uuid2")); - EXPECT_CALL(*mock_options, get_simple_options()).WillRepeatedly(ReturnRef(this->options->get_simple_options())); - auto monitor = std::make_shared>(); - auto pbft2 = std::make_shared(mock_node2, mock_io_context2, TEST_PEER_LIST, mock_options, mock_service2, - this->mock_failure_detector, this->crypto, manager2, storage2, monitor); - pbft2->set_audit_enabled(false); - pbft2->start(); - - // insert and enable a dummy configuration prior to the new one to be proposed - auto dummy_config = std::make_shared(); - dummy_config->add_peer(new_peer2); - this->configurations(this->pbft)->add(dummy_config); - EXPECT_TRUE(this->is_configuration_acceptable_in_new_view(dummy_config->get_hash())); - EXPECT_TRUE(this->configurations(this->pbft)->set_prepared(dummy_config->get_hash())); - - // PREPREPARE step - auto config = std::make_shared(*(this->configurations(this->pbft)->current())); - config->add_peer(new_peer); - this->configurations(pbft2)->add(config); - this->configurations(pbft2)->set_prepared(config->get_hash()); - auto msg = send_new_config_preprepare(pbft, this->mock_node, *config); - - auto op = this->find_operation(msg); - ASSERT_NE(op, nullptr); - - // PREPARE step - for (auto const &p : TEST_PEER_LIST) - { - EXPECT_CALL(*(mock_node), - send_signed_message(bzn::make_endpoint(p), - AllOf(message_has_correct_req_hash(msg.request_hash()), - message_has_correct_pbft_type(PBFT_MSG_COMMIT)))) - .Times(Exactly(1)); - } - - for (auto const &p : TEST_PEER_LIST) - { - send_new_config_prepare(pbft, p, op); - } - - // COMMIT step - auto nodes = TEST_PEER_LIST.begin(); - size_t req_nodes = pbft::honest_majority_size(TEST_PEER_LIST.size()); - for (size_t i = 0; i < req_nodes - 1; i++) - { - bzn::peer_address_t node(*nodes++); - send_new_config_commit(pbft, node, op); - } - EXPECT_TRUE(this->is_configuration_acceptable_in_new_view(dummy_config->get_hash())); - - this->new_config_timer_callback = nullptr; - - // one more commit should do it... - bzn::peer_address_t node(*nodes++); - send_new_config_commit(pbft, node, op); - - // and now the config should be committed - ASSERT_NE(this->configurations(this->pbft)->get(config->get_hash()), nullptr); - EXPECT_EQ(this->configurations(this->pbft)->newest_committed(), config->get_hash()); - EXPECT_NE(*(this->configurations(this->pbft)->current()), *config); - EXPECT_TRUE(this->is_configuration_acceptable_in_new_view(config->get_hash())); - EXPECT_FALSE(this->is_configuration_acceptable_in_new_view(dummy_config->get_hash())); - - // pbft should set a timer to do a viewchange - ASSERT_TRUE(this->new_config_timer_callback != nullptr); - - // when timer callback is called, pbft should broadcast viewchange message - for (auto const &p : TEST_PEER_LIST) - { - EXPECT_CALL(*mock_node, send_signed_message(bzn::make_endpoint(p), ResultOf(is_viewchange, Eq(true)))) - .Times((Exactly(1))) - .WillRepeatedly(Invoke([&](auto, auto wmsg) - { - // reflect viewchange message to second pbft from all nodes - pbft_msg msg; - ASSERT_TRUE(msg.ParseFromString(wmsg->pbft())); - wmsg->set_sender(p.uuid); - this->handle_viewchange(pbft2, msg, *wmsg); - })); - } - - // once second pbft gets f+1 viewchanges it will broadcast a viewchange message - EXPECT_CALL(*mock_node2, send_signed_message(A(), ResultOf(is_viewchange, Eq(true)))) - .Times((Exactly(TEST_PEER_LIST.size()))) - .WillRepeatedly(Invoke([&](auto, auto wmsg) - { - pbft_msg msg; - ASSERT_TRUE(msg.ParseFromString(wmsg->pbft())); - })); - - auto newview_env = std::make_shared(); - // second pbft should send a newview with the new configuration - EXPECT_CALL(*mock_node2, send_signed_message(A(), ResultOf(is_newview, Eq(true)))) - .Times((Exactly(TEST_PEER_LIST.size() + 1))) - .WillRepeatedly(Invoke([&](auto, auto wmsg) { - pbft_msg msg; - ASSERT_TRUE(msg.ParseFromString(wmsg->pbft())); - EXPECT_TRUE(msg.config_hash() == config->get_hash()); - EXPECT_TRUE(msg.config() == config->to_string()); - - newview_env->set_sender(pbft2->get_uuid()); - newview_env = wmsg; - })); - - // kick off timer callback - this->new_config_timer_callback(boost::system::error_code()); - - - // set up a third pbft to receive the new_view message and join the swarm - auto mock_node3 = std::make_shared(); - auto mock_io_context3 = std::make_shared>(); - auto audit_heartbeat_timer3 = std::make_unique>(); - auto new_config_timer3 = std::make_unique>(); - auto join_retry_timer3 = std::make_unique>(); - auto mock_service3 = std::make_shared>(); - auto mock_options3 = std::make_shared(); - auto storage3 = std::make_shared(); - auto manager3 = std::make_shared(storage3); - EXPECT_CALL(*(mock_io_context3), make_unique_steady_timer()) - .Times(AtMost(3)).WillOnce(Invoke([&](){return std::move(audit_heartbeat_timer3);})) - .WillOnce(Invoke([&](){return std::move(new_config_timer3);})) - .WillOnce(Invoke([&](){return std::move(join_retry_timer3);})); - EXPECT_CALL(*mock_io_context3, post(_)).WillRepeatedly(Invoke([](auto func){boost::asio::post(func);})); - EXPECT_CALL(*mock_options3, get_swarm_id()).WillRepeatedly(Return("swarm_id")); - EXPECT_CALL(*mock_options3, get_uuid()).WillRepeatedly(Return("uuid3")); - EXPECT_CALL(*mock_options3, get_simple_options()).WillRepeatedly(ReturnRef(this->options->get_simple_options())); - auto pbft3 = std::make_shared(mock_node3, mock_io_context3, TEST_PEER_LIST, mock_options3, mock_service3, - this->mock_failure_detector, this->crypto, manager3, storage3, monitor); - pbft3->set_audit_enabled(false); - pbft3->start(); - - // simulate that the new pbft has just joined the swarm and is waiting for a new_view - this->set_swarm_status_waiting(pbft3); - EXPECT_CALL(*(mock_node3), - send_signed_message(A(), ResultOf(test::is_prepare, Eq(true)))) - .Times(Exactly(TEST_PEER_LIST.size() + 1)); - this->handle_newview(pbft3, *newview_env); - EXPECT_TRUE(this->is_in_swarm(pbft3)); - - mock_node2->clear(); - mock_node3->clear(); - } - - TEST_F(pbft_join_leave_test, test_move_to_new_config) - { - this->build_pbft(); - - auto current_config = this->configurations(this->pbft)->current(); - auto config = std::make_shared(); - config->add_peer(new_peer); - this->configurations(this->pbft)->add(config); - EXPECT_TRUE(this->move_to_new_configuration(config->get_hash(), 2)); - EXPECT_TRUE(this->move_to_new_configuration(config->get_hash(), 3)); - EXPECT_EQ(this->configurations(this->pbft)->current(), config); - } - - TEST_F(pbft_join_leave_test, node_not_in_swarm_asks_to_join) - { - this->uuid = "somenode"; - EXPECT_CALL(*this->mock_node, send_signed_message(A(), ResultOf(test::is_join, Eq(true)))) - .Times(Exactly(1)) - .WillOnce(Invoke([&](auto, auto) - { - pbft_membership_msg response; - response.set_type(PBFT_MMSG_JOIN_RESPONSE); - this->handle_membership_message(test::wrap_pbft_membership_msg(response, "")); - })); - EXPECT_CALL(*this->join_retry_timer, async_wait(_)); - this->options->get_mutable_simple_options().set("listener_port", "9500"); - this->build_pbft(); - } - - TEST_F(pbft_join_leave_test, node_in_swarm_doesnt_ask_to_join) - { - EXPECT_CALL(*this->mock_node, send_signed_message(A(), ResultOf(test::is_join, Eq(true)))) - .Times(Exactly(0)); - this->build_pbft(); - } - - TEST_F(pbft_join_leave_test, new_node_can_join_swarm) - { - this->build_pbft(); - - // each peer should be sent a pre-prepare for new_config when the join is received - for (auto const &p : TEST_PEER_LIST) - { - EXPECT_CALL(*(this->mock_node), - send_signed_message(bzn::make_endpoint(p), ResultOf(test::is_preprepare, Eq(true)))) - .Times(Exactly(1)) - .WillOnce(Invoke([&](auto, auto &envelope) - { - pbft_msg msg; - EXPECT_TRUE(msg.ParseFromString(envelope->pbft())); - ASSERT_TRUE(msg.request().payload_case() == bzn_envelope::kPbftInternalRequest); - pbft_config_msg cfg_msg; - EXPECT_TRUE(cfg_msg.ParseFromString(msg.request().pbft_internal_request())); - - if (p.uuid == TEST_NODE_UUID) - { - EXPECT_CALL(*(mock_node), - send_signed_message(A(), ResultOf(test::is_prepare, Eq(true)))) - .Times(Exactly(TEST_PEER_LIST.size())); - - // reflect the pre-prepare back - this->handle_preprepare(msg, *envelope); - } - - pbft_msg prepare; - prepare.set_view(msg.view()); - prepare.set_sequence(msg.sequence()); - prepare.set_type(PBFT_MSG_PREPARE); - prepare.set_request_hash(this->crypto->hash(msg.request())); - - auto wmsg2 = wrap_pbft_msg(prepare); - wmsg2.set_sender(p.uuid); - (*wmsg2.add_piggybacked_requests()) = *envelope; - this->handle_prepare(prepare, wmsg2); - })); - } - - // the prepares triggered above will cause commits to be sent - for (auto const &p : TEST_PEER_LIST) - { - EXPECT_CALL(*(this->mock_node), - send_signed_message(bzn::make_endpoint(p), ResultOf(test::is_commit, Eq(true)))) - .Times(Exactly(1)) - .WillOnce(Invoke([&](auto, auto &wmsg) - { - pbft_msg msg; - EXPECT_TRUE(msg.ParseFromString(wmsg->pbft())); - - // return the message from whom it was sent to - // note that we're under lock here so can't call handle_message - wmsg->set_sender(p.uuid); - this->handle_commit(msg, *wmsg); - - })); - } - - // once committed the session will be checked - EXPECT_CALL(*this->mock_session, is_open()) - .Times(Exactly(1)) - .WillOnce(Return(true)); - - // ... and then a response should be sent - EXPECT_CALL(*this->mock_session, send_message(Matcher>(message_is_join_response()))) - .Times(Exactly(1)); - - // set up the join message - auto info = new pbft_peer_info; - info->set_name(new_peer.name); - info->set_host(new_peer.host); - info->set_port(new_peer.port); - info->set_uuid(new_peer.uuid); - - pbft_membership_msg join_msg; - join_msg.set_type(PBFT_MMSG_JOIN); - join_msg.set_allocated_peer_info(info); - - // here we go... - this->handle_membership_message(test::wrap_pbft_membership_msg(join_msg, this->pbft->get_uuid()), this->mock_session); - } - - TEST_F(pbft_join_leave_test, existing_node_can_rejoin_swarm) - { - this->build_pbft(); - - EXPECT_CALL(*this->mock_session, is_open()) - .Times(Exactly(1)) - .WillOnce(Return(true)); - EXPECT_CALL(*this->mock_session, send_message(Matcher>(message_is_join_response()))) - .Times(Exactly(1)); - - auto peer = *TEST_PEER_LIST.begin(); - auto info = new pbft_peer_info; - info->set_name(peer.name); - info->set_host(peer.host); - info->set_port(peer.port); - info->set_uuid(peer.uuid); - - pbft_membership_msg join_msg; - join_msg.set_type(PBFT_MMSG_JOIN); - join_msg.set_allocated_peer_info(info); - - this->handle_membership_message(test::wrap_pbft_membership_msg(join_msg, peer.uuid), this->mock_session); - } - - TEST_F(pbft_join_leave_test, cant_add_conflicting_peer) - { - this->build_pbft(); - - auto info = new pbft_peer_info; - auto peer = *TEST_PEER_LIST.begin(); - info->set_host(peer.host); - info->set_name(peer.name); - info->set_port(peer.port); - info->set_uuid("bogus_uuid"); - - pbft_membership_msg join_msg; - join_msg.set_type(PBFT_MMSG_JOIN); - join_msg.set_allocated_peer_info(info); - auto wmsg = wrap_pbft_membership_msg(join_msg, info->uuid()); - - EXPECT_CALL(*this->mock_session, close()).Times(Exactly(1)); - this->handle_membership_message(wmsg, this->mock_session); - } - - TEST_F(pbft_join_leave_test, pbft_join_swarm_does_not_bail_on_good_peers_list) - { - this->options->get_mutable_simple_options().set("uuid", "current_uuid"); - auto monitor = std::make_shared>(); - this->pbft = std::make_shared( - this->mock_node - , this->mock_io_context - , GOOD_TEST_PEER_LIST - , this->options - , this->mock_service - , this->mock_failure_detector - , this->crypto - , this->operation_manager - , this->storage - , monitor - ); - this->pbft->set_audit_enabled(false); - EXPECT_NO_THROW({ - this->pbft->start(); - this->pbft_built = true; - }); - } - - TEST_F(pbft_join_leave_test, pbft_join_swarm_bails_on_bad_peers_list_with_127_0_0_1_and_same_listen_port) - { - this->options->get_mutable_simple_options().set("uuid", "current_uuid"); - auto monitor = std::make_shared>(); - this->pbft = std::make_shared( - this->mock_node - , this->mock_io_context - , BAD_TEST_PEER_LIST_0 - , this->options - , this->mock_service - , this->mock_failure_detector - , this->crypto - , this->operation_manager - , this->storage - , monitor - ); - this->pbft->set_audit_enabled(false); - EXPECT_THROW({this->pbft->start(); }, std::runtime_error); - this->pbft_built = true; - } -} diff --git a/pbft/test/pbft_newview_test.cpp b/pbft/test/pbft_newview_test.cpp index bfacfba5..aaadec47 100644 --- a/pbft/test/pbft_newview_test.cpp +++ b/pbft/test/pbft_newview_test.cpp @@ -108,18 +108,17 @@ namespace bzn build_pbft(); // the pbft sut must be the current view's primay - EXPECT_EQ(this->uuid, this->pbft->get_primary().uuid); + EXPECT_EQ(this->uuid, this->pbft->get_current_primary().value().uuid); - this->pbft->view = this->pbft->view.value() + 1; - EXPECT_FALSE(this->uuid == this->pbft->get_primary().uuid); + EXPECT_NE(this->uuid, this->pbft->predict_primary(this->pbft->view.value() + 1).value().uuid); // given a view, get_primary must provide the address of a primary // TODO: this is a pretty sketchy test. for (size_t view{0}; view < 100; ++view) { - const bzn::uuid_t uuid = this->pbft->get_primary(view).uuid; - const bzn::uuid_t accepted_uuid = this->pbft->current_peers()[view % this->pbft->current_peers().size()].uuid; + const bzn::uuid_t uuid = this->pbft->predict_primary(view).value().uuid; + const bzn::uuid_t accepted_uuid = this->pbft->peers()->ordered()->at(view % this->pbft->peers()->ordered()->size()).uuid; EXPECT_EQ(uuid, accepted_uuid); } } diff --git a/pbft/test/pbft_peer_change_test.cpp b/pbft/test/pbft_peer_change_test.cpp new file mode 100644 index 00000000..4bc0d93c --- /dev/null +++ b/pbft/test/pbft_peer_change_test.cpp @@ -0,0 +1,252 @@ +// Copyright (C) 2019 Bluzelle +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License, version 3, +// as published by the Free Software Foundation. +// +// 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 + +/* + * the goal here is to check that pbft remains functional and does the right thing even when the peers list + * can change at any moment, by putting it through a series of tests and each time changing the peers list at a different moment + */ + + +namespace +{ + const bzn::peers_list_t PEER_LIST_A{{ "127.0.0.1", 8080, "name0", "uuid0"} + , {"127.0.0.1", 8081, "name1", "uuid1"} + , {"127.0.0.1", 8082, "name2", "uuid2"} + , {"127.0.0.1", 8083, "name3", "uuid3"}}; + + const bzn::peers_list_t PEER_LIST_A_PLUS{{ "127.0.0.1", 8080, "name0", "uuid0"} + , {"127.0.0.1", 8081, "name1", "uuid1"} + , {"127.0.0.1", 8082, "name2", "uuid2"} + , {"127.0.0.1", 8083, "name3", "uuid3"} + , {"127.0.0.1", 8084, "name4", "uuid4"}}; + + const bzn::peers_list_t PEER_LIST_B{{ "127.0.0.1", 8080, "name0", "uuid0"} + , {"127.0.0.1", 8081, "name1", "uuid1"} + , {"127.0.0.1", 8082, "name2", "uuid2"} + , {"127.0.0.1", 8085, "name5", "uuid5"}}; + + using peer_switch_t = std::pair; + + std::map peer_cases{ + std::make_pair("add_peer", std::make_pair(PEER_LIST_A, PEER_LIST_A_PLUS)), + std::make_pair("remove_peer", std::make_pair(PEER_LIST_A_PLUS, PEER_LIST_A)), + std::make_pair("replace_peer", std::make_pair(PEER_LIST_A, PEER_LIST_B)) + }; + + /* + * Which switch point are we using, how many are there in total, and what peers lists are we switching between + * + * The second param is passed so that the common test code can verify that the callsite has the correct (potentially updated) + * total number of switch points + * + * The third parameter is passed as a string that's a key in the global map instead of passing the lists + * directly in order to avoid filling the test names with junk + */ + using test_param_t = std::tuple; +} + +using namespace ::testing; + +class changeover_test : public bzn::test::pbft_test, public testing::WithParamInterface +{ + +public: + + changeover_test() + : current_peers_list(std::make_shared(this->before_list())) + { + this->set_up_beacon(); + } + + ~changeover_test() + { + EXPECT_EQ(this->potential_switch_points_hit, this->max_change_point()); + EXPECT_LT(this->this_change_point(), this->potential_switch_points_hit); + } + +protected: + void switch_here() + { + if (this->potential_switch_points_hit == this->this_change_point()) + { + this->do_switch(); + } + + this->potential_switch_points_hit++; + } + + unsigned int this_change_point() + { + return std::get<0>(GetParam()); + } + + unsigned int max_change_point() + { + return std::get<1>(GetParam()); + } + + bzn::peers_list_t before_list() + { + auto pair = peer_cases.at(std::get<2>(GetParam())); + return pair.first; + } + + bzn::peers_list_t after_list() + { + auto pair = peer_cases.at(std::get<2>(GetParam())); + return pair.second; + } + + std::shared_ptr current_peers_list; + +private: + void do_switch() + { + this->current_peers_list = std::make_shared(this->after_list()); + } + + void set_up_beacon() + { + auto changing_beacon = std::make_shared(); + + EXPECT_CALL(*changing_beacon, start()).Times(AtMost(1)); + + EXPECT_CALL(*changing_beacon, current()).WillRepeatedly(Invoke( + [&]() + { + return this->current_peers_list; + })); + + EXPECT_CALL(*changing_beacon, ordered()).WillRepeatedly(Invoke( + [&]() + { + auto ordered = std::make_shared(); + std::for_each(this->current_peers_list->begin(), this->current_peers_list->end(), + [&](const auto& peer) + { + ordered->push_back(peer); + }); + + std::sort(ordered->begin(), ordered->end(), + [](const auto& peer1, const auto& peer2) + { + return peer1.uuid.compare(peer2.uuid) < 0; + }); + + return ordered; + })); + + EXPECT_CALL(*changing_beacon, refresh(_)).Times(AnyNumber()); + this->beacon = changing_beacon; + } + + unsigned int potential_switch_points_hit = 0; +}; + +auto test_namer = [](const ::testing::TestParamInfo& info) +{ + std::stringstream res; + res << std::get<0>(info.param) << "_out_of_" << std::get<1>(info.param) << "_" << std::get<2>(info.param); + return res.str(); +}; + +//gtest is going to want to apply our parameter set over an entire test suite, and we would rather not have that because +//the number of possible changeover points varies per-test. Therefore, we define a test suite per test. + +class changeover_test_operations : public changeover_test +{}; + +TEST_P(changeover_test_operations, perform_pbft_operation) +{ + EXPECT_CALL(*(this->mock_io_context), post(_)).Times(Exactly(1)); + + this->build_pbft(); + + switch_here(); + + pbft_msg preprepare = pbft_msg(this->preprepare_msg); + preprepare.set_sequence(1); + this->pbft->handle_message(preprepare, default_original_msg); + + switch_here(); + + for (const auto& peer : *(this->current_peers_list)) + { + pbft_msg prepare = pbft_msg(preprepare); + prepare.set_type(PBFT_MSG_PREPARE); + this->pbft->handle_message(prepare, bzn::test::from(peer.uuid)); + } + + switch_here(); + + for (const auto& peer : *(this->current_peers_list)) + { + pbft_msg commit = pbft_msg(preprepare); + commit.set_type(PBFT_MSG_COMMIT); + this->pbft->handle_message(commit, bzn::test::from(peer.uuid)); + } + +} + +INSTANTIATE_TEST_CASE_P(changeover_test_set, changeover_test_operations, + testing::Combine( + Range(0u, 3u), + Values(3u), + Values("add_peer", "remove_peer", "replace_peer") + ), ); + +class changeover_test_checkpoints : public changeover_test +{}; + +TEST_P(changeover_test_checkpoints, perform_checkpoinnt) +{ + std::shared_ptr cp_manager = std::make_shared(this->mock_io_context, storage, this->beacon, this->mock_node); + const bzn::hash_t cp_hash = "a very hashy hash"; + const uint64_t cp_seq = 42; + const std::pair cp{cp_seq, cp_hash}; + + switch_here(); + + cp_manager->local_checkpoint_reached(cp); + + switch_here(); + + for (const auto& peer : *(this->current_peers_list)) + { + checkpoint_msg cp_msg; + cp_msg.set_state_hash(cp_hash); + cp_msg.set_sequence(cp_seq); + bzn_envelope env; + env.set_checkpoint_msg(cp_msg.SerializeAsString()); + env.set_sender(peer.uuid); + cp_manager->handle_checkpoint_message(env); + } + + EXPECT_EQ(cp_manager->get_latest_stable_checkpoint(), cp); + EXPECT_EQ(cp_manager->get_latest_local_checkpoint(), cp); + + switch_here(); + + EXPECT_EQ(cp_manager->get_latest_stable_checkpoint(), cp); + EXPECT_EQ(cp_manager->get_latest_local_checkpoint(), cp); +} + +INSTANTIATE_TEST_CASE_P(changeover_test_set, changeover_test_checkpoints, + testing::Combine( + Range(0u, 3u), + Values(3u), + Values("add_peer", "remove_peer", "replace_peer") + ), ); diff --git a/pbft/test/pbft_proto_test.cpp b/pbft/test/pbft_proto_test.cpp index 7daf24cb..c1522f28 100644 --- a/pbft/test/pbft_proto_test.cpp +++ b/pbft/test/pbft_proto_test.cpp @@ -46,7 +46,7 @@ namespace bzn { if (operation == nullptr) { - operation = this->operation_manager->find_or_construct(this->view, msg.sequence(), msg.request_hash(), this->pbft->current_peers_ptr()); + operation = this->operation_manager->find_or_construct(this->view, msg.sequence(), msg.request_hash()); // the SUT needs the pre-prepare it sends to itself in order to execute state machine this->send_preprepare(operation->get_sequence(), operation->get_request()); @@ -140,7 +140,7 @@ namespace bzn } // tell pbft that this operation has been executed - this->service_execute_handler(this->operation_manager->find_or_construct(this->view, sequence, request_hash, pbft->current_peers_ptr())); + this->service_execute_handler(this->operation_manager->find_or_construct(this->view, sequence, request_hash)); } void diff --git a/pbft/test/pbft_test.cpp b/pbft/test/pbft_test.cpp index d12f03f4..16809915 100644 --- a/pbft/test/pbft_test.cpp +++ b/pbft/test/pbft_test.cpp @@ -45,7 +45,7 @@ namespace bzn::test EXPECT_CALL(*mock_node, send_signed_message(A(), A>())).Times(1).WillRepeatedly(Invoke( [&](auto ep, auto msg) { - EXPECT_EQ(ep, make_endpoint(this->pbft->get_primary())); + EXPECT_EQ(ep, make_endpoint(this->pbft->get_current_primary().value())); EXPECT_EQ(msg->payload_case(), bzn_envelope::kDatabaseMsg); })); @@ -215,8 +215,8 @@ namespace bzn::test auto mock_session = std::make_shared(); EXPECT_CALL(*mock_session, send_message(A>())).Times(Exactly(1)); - auto peers = std::make_shared>(); - auto op = std::make_shared(1, 1, "somehash", peers); + std::vector peers; + auto op = std::make_shared(1, 1, "somehash"); op->set_session(mock_session); dummy_pbft_service service(this->mock_io_context); diff --git a/pbft/test/pbft_test_common.cpp b/pbft/test/pbft_test_common.cpp index 44bfd4e4..52ded401 100644 --- a/pbft/test/pbft_test_common.cpp +++ b/pbft/test/pbft_test_common.cpp @@ -84,16 +84,6 @@ namespace bzn::test [&]() { return std::move(this->audit_heartbeat_timer); } )) - .WillOnce( - Invoke( - [&]() - { return std::move(this->new_config_timer); } - )) - .WillOnce( - Invoke( - [&]() - { return std::move(this->join_retry_timer); } - )) .WillRepeatedly( Invoke( [&]() @@ -152,6 +142,7 @@ namespace bzn::test preprepare_msg.set_request_hash(this->crypto->hash(this->request_msg)); *(this->default_original_msg.add_piggybacked_requests()) = this->request_msg; + this->default_original_msg.set_sender("uuid0"); } void @@ -161,7 +152,7 @@ namespace bzn::test this->pbft = std::make_shared( this->mock_node , this->mock_io_context - , TEST_PEER_LIST + , this->beacon , this->options , this->mock_service , this->mock_failure_detector @@ -202,6 +193,7 @@ namespace bzn::test } original.set_pbft(preprepare.SerializeAsString()); + original.set_sender("uuid0"); this->pbft->handle_message(preprepare, original); } diff --git a/pbft/test/pbft_test_common.hpp b/pbft/test/pbft_test_common.hpp index f56943ea..dc237ba4 100644 --- a/pbft/test/pbft_test_common.hpp +++ b/pbft/test/pbft_test_common.hpp @@ -20,7 +20,7 @@ #include #include #include -#include +#include #include #include #include @@ -31,9 +31,11 @@ #include #include #include +#include #include #include #include +#include using namespace ::testing; @@ -80,13 +82,16 @@ namespace bzn::test std::shared_ptr mock_session = std::make_shared>(); std::shared_ptr storage = std::make_shared(); + std::shared_ptr operation_manager = - std::make_shared(storage); + std::make_shared(static_peers_beacon_for(TEST_PEER_LIST), storage); std::shared_ptr options = std::make_shared(); std::shared_ptr monitor = std::make_shared>(); std::shared_ptr crypto = std::make_shared(options, monitor); + std::shared_ptr beacon = bzn::static_peers_beacon_for(bzn::test::TEST_PEER_LIST); + std::shared_ptr pbft; std::unique_ptr audit_heartbeat_timer = diff --git a/pbft/test/pbft_viewchange_test.cpp b/pbft/test/pbft_viewchange_test.cpp index 148cbf04..4cca186b 100644 --- a/pbft/test/pbft_viewchange_test.cpp +++ b/pbft/test/pbft_viewchange_test.cpp @@ -323,11 +323,13 @@ namespace bzn EXPECT_CALL(*mock_options, get_uuid()).WillRepeatedly(Invoke([](){return "uuid2";})); + auto peers = static_peers_beacon_for(TEST_PEER_LIST); + auto storage2 = std::make_shared(); - auto manager2 = std::make_shared(storage2); + auto manager2 = std::make_shared(peers, storage2); auto monitor = std::make_shared>(); - auto pbft2 = std::make_shared(mock_node2, mock_io_context2, TEST_PEER_LIST, mock_options, mock_service2 + auto pbft2 = std::make_shared(mock_node2, mock_io_context2, peers, mock_options, mock_service2 , this->mock_failure_detector, this->crypto, manager2, storage2, monitor); pbft2->set_audit_enabled(false); diff --git a/peers_beacon/CMakeLists.txt b/peers_beacon/CMakeLists.txt new file mode 100644 index 00000000..850e6423 --- /dev/null +++ b/peers_beacon/CMakeLists.txt @@ -0,0 +1,13 @@ +add_library(peers_beacon STATIC + peer_address.hpp + peers_beacon_base.hpp + peers_beacon.cpp + peers_beacon.hpp + ) + +target_link_libraries(peers_beacon utils) +target_include_directories(peers_beacon PRIVATE ${BLUZELLE_STD_INCLUDES}) +add_dependencies(peers_beacon boost openssl) + +add_subdirectory(test) + diff --git a/bootstrap/peer_address.hpp b/peers_beacon/peer_address.hpp similarity index 77% rename from bootstrap/peer_address.hpp rename to peers_beacon/peer_address.hpp index 52bacbb4..30779471 100644 --- a/bootstrap/peer_address.hpp +++ b/peers_beacon/peer_address.hpp @@ -39,9 +39,20 @@ namespace bzn return this->host == other.host && this->port == other.port && this->uuid == other.uuid; } - const std::string host; - const uint16_t port; - const std::string name; - const std::string uuid; + std::string host; + uint16_t port; + std::string name; + std::string uuid; + }; +} + +namespace std +{ + template<> struct hash + { + size_t operator()(const bzn::peer_address_t& x) const noexcept + { + return std::hash()(std::string{x.host} + std::to_string(x.port)); + } }; } diff --git a/peers_beacon/peers_beacon.cpp b/peers_beacon/peers_beacon.cpp new file mode 100644 index 00000000..274aa8ff --- /dev/null +++ b/peers_beacon/peers_beacon.cpp @@ -0,0 +1,313 @@ +// Copyright (C) 2019 Bluzelle +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License, version 3, +// as published by the Free Software Foundation. +// +// 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 + +using namespace bzn; + +peers_beacon::peers_beacon(std::shared_ptr io, std::shared_ptr utils, std::shared_ptr opt) + : io(io) + , options(opt) + , utils(utils) + , internal_current(std::make_shared()) + , refresh_timer(io->make_unique_steady_timer()) +{} + +void +peers_beacon::start() +{ + if (!this->refresh(true)) + { + throw std::runtime_error("could not construct initial peers list)"); + } + + this->run_timer(); +} + +void +peers_beacon::run_timer() +{ + this->refresh_timer->cancel(); + this->refresh_timer->expires_from_now( + std::chrono::seconds{ + this->options->get_simple_options().get(bzn::option_names::PEERS_REFRESH_INTERVAL_SECONDS) + }); + + this->refresh_timer->async_wait([weak_self = weak_from_this()](auto /*reason*/) + { + auto self = weak_self.lock(); + if (self) + { + self->refresh(); + self->check_removal(); + self->run_timer(); + } + }); +} + +std::shared_ptr +peers_beacon::current() const +{ + std::shared_lock lock(this->lock); + return this->internal_current; +} + +std::shared_ptr +peers_beacon::ordered() const +{ + std::shared_lock lock(this->lock); + return this->internal_current_ordered; +} + +void +peers_beacon::check_removal() +{ + auto peers = this->current(); + auto uuid = this->options->get_uuid(); + + bool in_swarm = peers->end() != std::find_if(peers->begin(), peers->end(), + [&uuid](const auto& peer) + { + return peer.uuid == uuid; + }); + + LOG(debug) << "We are " << (in_swarm ? "" : "not ") << "in this peers list"; + + + this->ever_been_in_swarm = this->ever_been_in_swarm || in_swarm; + + if (!in_swarm && ever_been_in_swarm) + { + LOG(info) << "we seem to have been removed from the swarm; exiting"; + this->io->stop(); + } +} + +bool +peers_beacon::refresh(bool first_run) +{ + bool has_esr = !this->options->get_swarm_id().empty() + && !this->options->get_swarm_info_esr_address().empty() + && !this->options->get_swarm_info_esr_url().empty() + && !this->options->get_simple_options().get(bzn::option_names::IGNORE_ESR); + bool has_file = !this->options->get_bootstrap_peers_file().empty(); + bool has_url = !this->options->get_bootstrap_peers_url().empty(); + + /* Here we chose not to fall back on another method if the first priority from our config is unavailable. This is + * chosen so that if, eg, a url based peers list is briefly unavailable, we do not abruptly switch to a very old + * peers list from an unmaintained esr contract. + */ + + if (has_esr) + { + if (first_run) + { + LOG(info) << "ESR chosen as peers source"; + } + return this->fetch_from_esr(); + } + + if (has_url) + { + if (first_run) + { + LOG(info) << "URL chosen as peers source"; + } + return this->fetch_from_url(); + } + + if (has_file) + { + if (first_run) + { + LOG(info) << "file chosen as peers source"; + } + return this->fetch_from_file(); + } + + throw std::runtime_error("no acceptable source for peers configured; must specify file or url or esr"); +} + +bool +peers_beacon::fetch_from_file() +{ + auto filename = this->options->get_bootstrap_peers_file(); + std::ifstream file(filename); + if (file.fail()) + { + LOG(error) << "Failed to read bootstrap peers file " << filename; + return false; + } + + LOG(info) << "Reading peers from " << filename; + + return parse_and_save_peers(file); +} + +bool +peers_beacon::fetch_from_url() +{ + auto url = this->options->get_bootstrap_peers_url(); + std::string peers = this->utils->sync_req(url); + + LOG(info) << "Downloaded peer list from " << url; + + std::stringstream stream; + stream << peers; + + return parse_and_save_peers(stream); +} + +bool +peers_beacon::fetch_from_esr() +{ + auto swarm_id = this->options->get_swarm_id(); + auto esr_address = this->options->get_swarm_info_esr_address(); + auto esr_url = this->options->get_swarm_info_esr_url(); + + peers_list_t new_peers; + auto peer_ids = this->utils->get_peer_ids(swarm_id, esr_address, esr_url); + for (const auto& peer_id : peer_ids) + { + bzn::peer_address_t peer_info{this->utils->get_peer_info(swarm_id, peer_id, esr_address, esr_url)}; + if (peer_info.host.empty() + || peer_info.port == 0 + //|| peer_info.name.empty() // is it important that a peer have a name? + || peer_info.uuid.empty() + ) + { + LOG(warning) << "Invalid peer information found in esr contract, ignoring info for peer: " << peer_id << " in swarm: " << swarm_id; + } + else + { + new_peers.emplace(peer_info); + } + } + + return this->switch_peers_list(new_peers); +} + +bool +peers_beacon::switch_peers_list(const peers_list_t& new_peers) +{ + if (new_peers.size() == 0) + { + LOG(error) << "Failed to read any peers"; + if (this->internal_current->size() > 0) + { + LOG(error) << "Keeping old peer list"; + } + else + { + LOG(error) << "Old peers list also empty"; + } + return false; + } + + if(*(this->internal_current) != new_peers) + { + LOG(info) << "Switching to new peers list with " << new_peers.size() << " peers"; + } + + auto new_ordered = std::make_shared(); + std::for_each(new_peers.begin(), new_peers.end(), + [&](const auto& peer) + { + new_ordered->push_back(peer); + }); + + std::sort(new_ordered->begin(), new_ordered->end(), + [](const auto& peer1, const auto& peer2) + { + return peer1.uuid.compare(peer2.uuid) < 0; + }); + + std::unique_lock lock(this->lock); + + this->internal_current = std::make_shared(new_peers); + this->internal_current_ordered = new_ordered; + + return true; +} + +bool +peers_beacon::parse_and_save_peers(std::istream& source) +{ + Json::Value root; + + try + { + source >> root; + } + catch (const std::exception& e) + { + LOG(error) << "Failed to parse peer JSON (" << e.what() << ")"; + LOG(error) << "Keeping old peer list"; + return false; + } + + auto new_peers = this->build_peers_list_from_json(root); + return this->switch_peers_list(new_peers); +} + +peers_list_t +peers_beacon::build_peers_list_from_json(const Json::Value& root) +{ + peers_list_t result; + + // Expect the read json to be an array of peer objects + for (const auto& peer : root) + { + std::string host; + std::string name; + std::string uuid; + uint16_t port; + + try + { + host = peer["host"].asString(); + port = peer["port"].asUInt(); + uuid = peer.isMember("uuid") ? peer["uuid"].asString() : "unknown"; + name = peer.isMember("name") ? peer["name"].asString() : "unknown"; + } + catch(std::exception& e) + { + LOG(warning) << "Ignoring malformed peer specification " << peer; + continue; + } + + // port wasn't actually a 16 bit uint + if (peer["port"].asUInt() != port) + { + LOG(warning) << "Ignoring peer with bad port " << peer; + continue; + } + + // peer didn't contain everything we need + if (host.empty() || port == 0) + { + LOG(warning) << "Ignoring underspecified peer (needs host and port) " << peer; + continue; + } + + LOG(trace) << "Found peer " << host << ":" << port << " (" << name << ")"; + + result.emplace(host, port, name, uuid); + } + + LOG(trace) << "Found " << result.size() << " well formed peers"; + return result; +} diff --git a/peers_beacon/peers_beacon.hpp b/peers_beacon/peers_beacon.hpp new file mode 100644 index 00000000..62a66e60 --- /dev/null +++ b/peers_beacon/peers_beacon.hpp @@ -0,0 +1,72 @@ +// Copyright (C) 2019 Bluzelle +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License, version 3, +// as published by the Free Software Foundation. +// +// 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 . + +#pragma once +#include +#include +#include +#include +#include +#include + +namespace bzn +{ + class peers_beacon : public peers_beacon_base, public std::enable_shared_from_this + { + public: + peers_beacon(std::shared_ptr io, std::shared_ptr utils, std::shared_ptr opt); + + // do an initial pull and start any necessary timers + void start() override; + + // get a pointer to the current peers list (which won't change, but can be replaced at any time) + std::shared_ptr current() const override; + + std::shared_ptr ordered() const override; + + // refresh from whatever source we are using + bool refresh(bool first_run = false) override; + + void check_removal(); + + private: + + bool fetch_from_esr(); + bool fetch_from_file(); + bool fetch_from_url(); + + bool parse_and_save_peers(std::istream& source); + peers_list_t build_peers_list_from_json(const Json::Value& root); + + bool switch_peers_list(const peers_list_t& new_peers); + + void run_timer(); + + bool ever_been_in_swarm = false; + + std::shared_ptr io; + + std::shared_ptr options; + std::shared_ptr utils; + + // this is kept as a shared ptr to avoid issues when a reader reads while the peers list changes + std::shared_ptr internal_current; + std::shared_ptr internal_current_ordered; + + std::unique_ptr refresh_timer; + + mutable std::shared_mutex lock; + }; +} + diff --git a/peers_beacon/peers_beacon_base.hpp b/peers_beacon/peers_beacon_base.hpp new file mode 100644 index 00000000..39cb1c34 --- /dev/null +++ b/peers_beacon/peers_beacon_base.hpp @@ -0,0 +1,41 @@ +// Copyright (C) 2019 Bluzelle +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License, version 3, +// as published by the Free Software Foundation. +// +// 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 . + +#pragma once +#include +#include +#include +#include + +namespace bzn +{ + using peers_list_t = std::unordered_set; + using ordered_peers_list_t = std::vector; + + class peers_beacon_base + { + public: + virtual void start() = 0; + + // get a pointer to the current peers list (which won't change, but can be replaced at any time) + virtual std::shared_ptr current() const = 0; + + virtual std::shared_ptr ordered() const = 0; + + // refresh from whatever source we are using; return success + virtual bool refresh(bool first_run = false) = 0; + + virtual ~peers_beacon_base() = default; + }; +} diff --git a/peers_beacon/test/CMakeLists.txt b/peers_beacon/test/CMakeLists.txt new file mode 100644 index 00000000..ddae573a --- /dev/null +++ b/peers_beacon/test/CMakeLists.txt @@ -0,0 +1,4 @@ +set(test_srcs peers_beacon_tests.cpp) +set(test_libs peers_beacon utils options smart_mocks) + +add_gmock_test(peers_beacon) diff --git a/peers_beacon/test/peers_beacon_tests.cpp b/peers_beacon/test/peers_beacon_tests.cpp new file mode 100644 index 00000000..d9b5d433 --- /dev/null +++ b/peers_beacon/test/peers_beacon_tests.cpp @@ -0,0 +1,237 @@ +// Copyright (C) 2019 Bluzelle +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License, version 3, +// as published by the Free Software Foundation. +// +// 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 +#include +#include + +namespace +{ + const std::string invalid_json = "[{\"Some key\": 124}, }}[} oh noes I broke it"; + const std::string no_peers = "[]"; + const std::string valid_peers = "[{\"name\": \"peer1\", \"host\": \"peer1.com\", \"port\": 12345, \"http_port\" : 8080}, {\"host\": \"nonamepeer.com\", \"port\": 54321}]"; + const std::string different_valid_peer = "[{\"name\": \"peer3\", \"host\": \"peer3.com\", \"port\": 12345, \"http_port\" : 8080}]"; + const std::string duplicate_peers = "[{\"name\": \"peer1\", \"host\": \"peer1.com\", \"port\": 12345, \"http_port\" : 8080}, {\"name\": \"peer1\", \"host\": \"peer1.com\", \"port\": 12345}]"; + const std::string underspecified_peer = "[{\"name\": \"peer1\", \"port\": 1024}]"; + const std::string bad_port = "[{\"name\": \"peer1\", \"host\": \"127.0.0.1\", \"port\": 70000}]"; + const std::string test_peers_filename = "peers.json"; + + const std::string our_uuid = "alice"; + const std::string peers_with_us = "[{\"host\": \"peer1.com\", \"port\": 12345, \"uuid\" : \"alice\"}]"; + const std::string peers_without_us = "[{\"host\": \"peer1.com\", \"port\": 12345, \"uuid\" : \"not alice\"}]"; + + const std::string sample_peers_url = "pastebin.com/raw/KvbcVhfZ"; + const std::string sample_peers_url_with_protocol = "http://www.pastebin.com/raw/KvbcVhfZ"; +} + +using namespace ::testing; + +class peers_beacon_test : public Test +{ +public: + std::shared_ptr opt = std::make_shared(); + bzn::simple_options inner_opt; + std::shared_ptr io = std::make_shared(); + std::shared_ptr peers; + std::shared_ptr utils = std::make_shared(); + + peers_beacon_test() + { + EXPECT_CALL(*opt, get_bootstrap_peers_file()).WillRepeatedly(Return(test_peers_filename)); + EXPECT_CALL(*opt, get_bootstrap_peers_url()).WillRepeatedly(Return("")); + EXPECT_CALL(*opt, get_swarm_id()).WillRepeatedly(Return("")); + this->inner_opt.set(bzn::option_names::PEERS_REFRESH_INTERVAL_SECONDS, "60"); + EXPECT_CALL(*opt, get_simple_options()).WillRepeatedly(ReturnRef(this->inner_opt)); + EXPECT_CALL(*opt, get_uuid()).WillRepeatedly(Return(our_uuid)); + + this->peers = std::make_shared(this->io, this->utils, this->opt); + } + + void set_peers_file(const std::string& peers_data) + { + std::ofstream ofile(test_peers_filename); + ofile << peers_data; + ofile.close(); + } + + ~peers_beacon_test() + { + unlink(test_peers_filename.c_str()); + } +}; + +TEST_F(peers_beacon_test, test_invalid_json) +{ + set_peers_file(invalid_json); + ASSERT_FALSE(peers->refresh()); + ASSERT_TRUE(peers->current()->empty()); +} + + +TEST_F(peers_beacon_test, test_no_peers) +{ + set_peers_file(no_peers); + ASSERT_FALSE(peers->refresh()); + ASSERT_TRUE(peers->current()->empty()); +} + + +TEST_F(peers_beacon_test, test_underspecified_peer) +{ + set_peers_file(underspecified_peer); + ASSERT_FALSE(peers->refresh()); + ASSERT_TRUE(peers->current()->empty()); +} + + +TEST_F(peers_beacon_test, test_bad_port) +{ + set_peers_file(bad_port); + ASSERT_FALSE(peers->refresh()); + ASSERT_TRUE(peers->current()->empty()); +} + + +TEST_F(peers_beacon_test, test_valid_peers) +{ + set_peers_file(valid_peers); + ASSERT_TRUE(peers->refresh()); + ASSERT_EQ(peers->current()->size(), 2U); + + bool seen_peer1 = false; + bool seen_peer2 = false; + + for (const bzn::peer_address_t& p : *(peers->current())) + { + if (p.port == 12345) seen_peer1 = true; + if (p.port == 54321) seen_peer2 = true; + } + + ASSERT_TRUE(seen_peer1 && seen_peer2); +} + +TEST_F(peers_beacon_test, test_unnamed_peers) +{ + set_peers_file(valid_peers); + ASSERT_TRUE(peers->refresh()); + ASSERT_EQ(peers->current()->size(), 2U); + + bool seen_name1 = false; + bool seen_name2 = false; + + for (const bzn::peer_address_t& p : *(peers->current())) + { + if (p.name == "peer1") seen_name1 = true; + if (p.name == "unknown") seen_name2 = true; + } + + ASSERT_TRUE(seen_name1 && seen_name2); +} + + +TEST_F(peers_beacon_test, test_duplicate_peers) +{ + set_peers_file(duplicate_peers); + ASSERT_TRUE(peers->refresh()); + ASSERT_EQ(peers->current()->size(), 1U); +} + +TEST_F(peers_beacon_test, test_changed_list) +{ + set_peers_file(valid_peers); + ASSERT_TRUE(peers->refresh()); + ASSERT_EQ(peers->current()->size(), 2U); + + set_peers_file(different_valid_peer); + ASSERT_TRUE(peers->refresh()); + ASSERT_EQ(peers->current()->size(), 1U); +} + +TEST_F(peers_beacon_test, test_automatic_refresh) +{ + set_peers_file(valid_peers); + peers->start(); + ASSERT_EQ(peers->current()->size(), 2U); + + set_peers_file(different_valid_peer); + this->io->trigger_timer(0); + + ASSERT_EQ(peers->current()->size(), 1U); + +} + +TEST_F(peers_beacon_test, test_url) +{ + const std::string test_url = "some fake url"; + EXPECT_CALL(*(this->opt), get_bootstrap_peers_url()).WillRepeatedly(Return(test_url)); + EXPECT_CALL(*(this->opt), get_bootstrap_peers_file()).WillRepeatedly(Return("")); + EXPECT_CALL(*(this->utils), sync_req(test_url, "")).WillRepeatedly(Return(valid_peers)); + + ASSERT_TRUE(peers->refresh()); + ASSERT_EQ(peers->current()->size(), 2U); +} + +TEST_F(peers_beacon_test, test_esr) +{ + bzn::peer_address_t alice("alice.com", 8080, "alice", "alice's key"); + + bzn::uuid_t swarm_id = "foo"; + std::string esr_address = "bar"; + std::string esr_url = "example.tld"; + + EXPECT_CALL(*(this->opt), get_bootstrap_peers_url()).WillRepeatedly(Return("")); + EXPECT_CALL(*(this->opt), get_bootstrap_peers_file()).WillRepeatedly(Return("")); + + EXPECT_CALL(*(this->opt), get_swarm_id()).WillRepeatedly(Return(swarm_id)); + EXPECT_CALL(*(this->opt), get_swarm_info_esr_address()).WillRepeatedly(Return(esr_address)); + EXPECT_CALL(*(this->opt), get_swarm_info_esr_url()).WillRepeatedly(Return(esr_url)); + + EXPECT_CALL(*(this->utils), get_peer_ids(swarm_id, esr_address, esr_url)).WillRepeatedly(Return(std::vector{"alice"})); + EXPECT_CALL(*(this->utils), get_peer_info(swarm_id, "alice", esr_address, esr_url)).WillRepeatedly(Invoke( + [&](auto, auto, auto, auto) + { + return alice; + } + )); + + ASSERT_TRUE(peers->refresh()); + ASSERT_EQ(peers->current()->size(), 1U); + ASSERT_EQ(peers->ordered()->front().name, "alice"); +} + +TEST_F(peers_beacon_test, test_halt) +{ + EXPECT_CALL(*(this->io), stop()).Times(Exactly(0)); + set_peers_file(peers_without_us); + + ASSERT_TRUE(peers->refresh()); + peers->check_removal(); + + set_peers_file(peers_with_us); + + ASSERT_TRUE(peers->refresh()); + peers->check_removal(); + + + set_peers_file(peers_without_us); + EXPECT_CALL(*(this->io), stop()).Times(Exactly(1)); + + ASSERT_TRUE(peers->refresh()); + peers->check_removal(); +} + diff --git a/swarm/CMakeLists.txt b/swarm/CMakeLists.txt index 48f414e0..09409af8 100644 --- a/swarm/CMakeLists.txt +++ b/swarm/CMakeLists.txt @@ -1,4 +1,4 @@ add_executable(swarm main.cpp) add_dependencies(swarm boost jsoncpp rocksdb) target_include_directories(swarm PRIVATE ${BLUZELLE_STD_INCLUDES}) -target_link_libraries(swarm node pbft audit crud chaos options bootstrap storage crypto monitor proto ${Protobuf_LIBRARIES} status ${ROCKSDB_LIBRARIES} ${Boost_LIBRARIES} ${JSONCPP_LIBRARIES} pthread) +target_link_libraries(swarm node pbft audit crud chaos options utils peers_beacon storage crypto monitor proto ${Protobuf_LIBRARIES} status ${ROCKSDB_LIBRARIES} ${Boost_LIBRARIES} ${JSONCPP_LIBRARIES} pthread) diff --git a/swarm/main.cpp b/swarm/main.cpp index a1954c50..2228865e 100644 --- a/swarm/main.cpp +++ b/swarm/main.cpp @@ -13,7 +13,7 @@ // along with this program. If not, see . #include -#include +#include #include #include #include @@ -29,6 +29,7 @@ #include #include #include +#include #ifdef __APPLE__ #pragma GCC diagnostic push @@ -101,45 +102,6 @@ init_logging(const bzn::options& options) } -bool -init_peers(bzn::bootstrap_peers& peers, const std::string& peers_file, const std::string& peers_url, const std::string& swarm_info_esr_url, const std::string& swarm_info_esr_address, const bzn::uuid_t& swarm_id) -{ - if (peers_file.empty() && peers_url.empty() && swarm_id.empty()) - { - LOG(error) << "Bootstrap peers must be specified options (bootstrap_file, bootstrap_url or swarm_id)"; - return false; - } - - if (!swarm_id.empty()) - { - peers.fetch_peers_from_esr_contract(swarm_info_esr_url, swarm_info_esr_address, swarm_id); - if (!peers.get_peers().empty()) - { - return true; - } - - LOG(warning) << "Ethereum Swarm Registry contained no peer listing for the swarm with id " << swarm_id << " checking other sources"; - } - - if (!peers_file.empty()) - { - peers.fetch_peers_from_file(peers_file); - } - - if (!peers_url.empty()) - { - peers.fetch_peers_from_url(peers_url); - } - - if (peers.get_peers().empty()) - { - LOG(error) << "Failed to find any bootstrap peers"; - return false; - } - return true; -} - - boost::uintmax_t get_dir_size(const boost::filesystem::path& dir) { @@ -243,13 +205,11 @@ main(int argc, const char* argv[]) init_logging(*options); - bzn::bootstrap_peers peers(options->peer_validation_enabled()); - if (!init_peers(peers, options->get_bootstrap_peers_file(), options->get_bootstrap_peers_url(), - options->get_swarm_info_esr_url(), options->get_swarm_info_esr_address(), - options->get_swarm_id())) - throw std::runtime_error("Bootstrap peers initialization failed."); - auto io_context = std::make_shared(); + auto utils = std::make_shared(); + + auto peers = std::make_shared(io_context, utils, options); + peers->start(); // setup signal handler... boost::asio::signal_set signals(io_context->get_io_context(), SIGINT, SIGTERM); @@ -293,9 +253,9 @@ main(int argc, const char* argv[]) } auto crud = std::make_shared(io_context, stable_storage, std::make_shared(io_context), node, options->get_owner_public_key()); - auto operation_manager = std::make_shared(unstable_storage); + auto operation_manager = std::make_shared(peers, unstable_storage); - auto pbft = std::make_shared(node, io_context, peers.get_peers(), options, + auto pbft = std::make_shared(node, io_context, peers, options, std::make_shared(io_context, unstable_storage, crud, monitor, options->get_uuid()) ,failure_detector, crypto, operation_manager, unstable_storage, monitor); diff --git a/utils/CMakeLists.txt b/utils/CMakeLists.txt index 178d9923..0a60dcfd 100644 --- a/utils/CMakeLists.txt +++ b/utils/CMakeLists.txt @@ -1,6 +1,4 @@ add_library(utils STATIC - http_req.cpp - http_req.hpp blacklist.cpp blacklist.hpp make_endpoint.hpp @@ -9,8 +7,11 @@ add_library(utils STATIC bytes_to_debug_string.hpp crypto.cpp crypto.hpp + utils_interface_base.hpp + utils_interface.hpp esr_peer_info.cpp - esr_peer_info.hpp) + http_req.cpp +) target_link_libraries(utils ${JSONCPP_LIBRARIES} ${OPENSSL_LIBRARIES}) diff --git a/utils/blacklist.cpp b/utils/blacklist.cpp index 205e71f5..1a00044b 100644 --- a/utils/blacklist.cpp +++ b/utils/blacklist.cpp @@ -13,7 +13,7 @@ // along with this program. If not, see . #include -#include +#include #include namespace @@ -42,7 +42,9 @@ namespace bzn::utils::blacklist bzn::json_message response; Json::Reader reader; - if (!reader.parse(bzn::utils::http::sync_req(url, post_fields), response)) + bzn::utils_interface utils_object; + + if (!reader.parse(utils_object.sync_req(url, post_fields), response)) { LOG(error) << "Unable to parse response from Ropsten - could not validate peer"; return false; diff --git a/utils/esr_peer_info.cpp b/utils/esr_peer_info.cpp index 44e70f83..d0d3f7e6 100644 --- a/utils/esr_peer_info.cpp +++ b/utils/esr_peer_info.cpp @@ -13,8 +13,7 @@ // along with this program. If not, see . #include -#include -#include +#include #include #include #include @@ -338,30 +337,29 @@ namespace } } - +// TODO: replace this with a function that uses the ABI to create the request data +// data_string_for_get_peer_info has been moved out of the anonymous namespace to make it possible to +// unit test directly. namespace bzn::utils::esr { - // TODO: replace this with a function that uses the ABI to create the request data - // data_string_for_get_peer_info has been moved out of the anonymous namespace to make it possible to - // unit test directly. const std::string data_string_for_get_peer_info(const std::string& swarm_id, const std::string& peer_id) { const std::string SWARM_ID_PARAMETER { - size_type_to_hex(swarm_id.size(), 64) // size of variable parameter - + pad_str_to_mod_64(string_to_hex(swarm_id)) // swarm id parameter + size_type_to_hex(swarm_id.size(), 64) // size of variable parameter + + pad_str_to_mod_64(string_to_hex(swarm_id)) // swarm id parameter }; const std::string PEER_ID_PARAMETER { - size_type_to_hex(peer_id.size(), 64) - + pad_str_to_mod_64(string_to_hex(peer_id)) + size_type_to_hex(peer_id.size(), 64) + + pad_str_to_mod_64(string_to_hex(peer_id)) }; const std::string PREAMBLE { - GET_PEER_INFO_SIGNATURE - + size_type_to_hex( PARAMETER_OFFSET, 64) - + size_type_to_hex( PARAMETER_OFFSET + SWARM_ID_PARAMETER.size() / 2, 64) + GET_PEER_INFO_SIGNATURE + + size_type_to_hex( PARAMETER_OFFSET, 64) + + size_type_to_hex( PARAMETER_OFFSET + SWARM_ID_PARAMETER.size() / 2, 64) }; return std::string{ @@ -370,28 +368,29 @@ namespace bzn::utils::esr + PEER_ID_PARAMETER }; } +} +using namespace bzn; - std::vector - get_peer_ids(const bzn::uuid_t& swarm_id, const std::string& esr_address, const std::string& url) - { - const auto DATA{data_string_for_get_peers(swarm_id)}; - const auto REQUEST{make_request( esr_address, DATA)}; - const auto response{bzn::utils::http::sync_req(url, REQUEST)}; - const auto json_response{str_to_json(response)}; - const auto result{json_response["result"].asCString() + 2}; // + 2 skips the '0x' - return parse_get_peers_result_to_vector(result); - } +std::vector +utils_interface::get_peer_ids(const bzn::uuid_t& swarm_id, const std::string& esr_address, const std::string& url) const +{ + const auto DATA{data_string_for_get_peers(swarm_id)}; + const auto REQUEST{make_request( esr_address, DATA)}; + const auto response{this->sync_req(url, REQUEST)}; + const auto json_response{str_to_json(response)}; + const auto result{json_response["result"].asCString() + 2}; // + 2 skips the '0x' + return parse_get_peers_result_to_vector(result); +} - bzn::peer_address_t - get_peer_info(const bzn::uuid_t& swarm_id, const std::string& peer_id, const std::string& esr_address, const std::string& url) - { - const auto DATA{data_string_for_get_peer_info(swarm_id, peer_id)}; - const auto REQUEST{make_request( esr_address, DATA)}; - const auto response{bzn::utils::http::sync_req(url, REQUEST)}; - const auto json_response{str_to_json(response)}; - const auto result{json_response["result"].asCString() + 2}; - return parse_get_peer_info_result_to_peer_address(peer_id, result); - } +bzn::peer_address_t +utils_interface::get_peer_info(const bzn::uuid_t& swarm_id, const std::string& peer_id, const std::string& esr_address, const std::string& url) const +{ + const auto DATA{utils::esr::data_string_for_get_peer_info(swarm_id, peer_id)}; + const auto REQUEST{make_request( esr_address, DATA)}; + const auto response{this->sync_req(url, REQUEST)}; + const auto json_response{str_to_json(response)}; + const auto result{json_response["result"].asCString() + 2}; + return parse_get_peer_info_result_to_peer_address(peer_id, result); } diff --git a/utils/http_req.cpp b/utils/http_req.cpp index 81cccc08..22f6d7bc 100644 --- a/utils/http_req.cpp +++ b/utils/http_req.cpp @@ -1,4 +1,4 @@ -// Copyright (C) 2018 Bluzelle +// Copyright (C) 2019 Bluzelle // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License, version 3, @@ -13,6 +13,7 @@ // along with this program. If not, see . #include +#include #include #include #include @@ -31,20 +32,21 @@ namespace const std::regex URL_REGEX("(http|https)://([^/ :]+):?([^/ ]*)(/.*)?"); const std::string CERT_DIRS[] = { - "/system/etc/security/cacerts", // Android - "/usr/local/share/certs", // FreeBSD - "/etc/pki/tls/certs", // Fedora/RHEL - "/etc/ssl/certs", // Ubuntu - "/etc/openssl/certs", // NetBSD - "/var/ssl/certs", // AIX + "/system/etc/security/cacerts", // Android + "/usr/local/share/certs", // FreeBSD + "/etc/pki/tls/certs", // Fedora/RHEL + "/etc/ssl/certs", // Ubuntu + "/etc/openssl/certs", // NetBSD + "/var/ssl/certs", // AIX }; } -namespace bzn::utils::http +namespace bzn { // Performs an HTTP GET or POST and returns the body of the HTTP response... - std::string sync_req(const std::string& url, const std::string& post) + std::string + utils_interface::sync_req(const std::string& url, const std::string& post) const { using tcp = boost::asio::ip::tcp; namespace ssl = boost::asio::ssl; @@ -144,4 +146,4 @@ namespace bzn::utils::http return res.body(); } -} // namespace bzn::utils::http +} // namespace bzn::utils::http \ No newline at end of file diff --git a/utils/make_endpoint.hpp b/utils/make_endpoint.hpp index 17f0b498..4f0181cb 100644 --- a/utils/make_endpoint.hpp +++ b/utils/make_endpoint.hpp @@ -15,7 +15,7 @@ #pragma once #include -#include +#include namespace bzn { diff --git a/utils/test/utils_test.cpp b/utils/test/utils_test.cpp index 4f24156b..4dd67206 100644 --- a/utils/test/utils_test.cpp +++ b/utils/test/utils_test.cpp @@ -18,9 +18,8 @@ #include #include #include -#include +#include #include -#include using namespace::testing; @@ -392,10 +391,12 @@ TEST(util_test, test_that_verifying_a_signature_with_empty_inputs_will_fail_grac TEST(util_test, test_that_esr_returns_peers_list) { + bzn::utils_interface iface; // Check for swarm that doesn't exist, must return empty vector. { const std::pair BAD_SWARM{"NonExistentBluzelleSwarm", 0}; - const auto peer_ids = bzn::utils::esr::get_peer_ids(BAD_SWARM.first, bzn::utils::DEFAULT_SWARM_INFO_ESR_ADDRESS, bzn::utils::ROPSTEN_URL); + bzn::utils_interface iface; + const auto peer_ids = iface.get_peer_ids(BAD_SWARM.first, bzn::utils::DEFAULT_SWARM_INFO_ESR_ADDRESS, bzn::utils::ROPSTEN_URL); EXPECT_EQ( BAD_SWARM.second, peer_ids.size()); } @@ -403,7 +404,7 @@ TEST(util_test, test_that_esr_returns_peers_list) { std::vector accepted_ids(swarm.second.size()); std::transform(swarm.second.begin(), swarm.second.end(), accepted_ids.begin(), [](const auto &pinfo){return pinfo.uuid;}); - const auto peer_ids = bzn::utils::esr::get_peer_ids(swarm.first, bzn::utils::DEFAULT_SWARM_INFO_ESR_ADDRESS, bzn::utils::ROPSTEN_URL); + const auto peer_ids = iface.get_peer_ids(swarm.first, bzn::utils::DEFAULT_SWARM_INFO_ESR_ADDRESS, bzn::utils::ROPSTEN_URL); EXPECT_EQ(swarm.second.size(), peer_ids.size()); EXPECT_EQ(accepted_ids, peer_ids); } @@ -412,13 +413,14 @@ TEST(util_test, test_that_esr_returns_peers_list) TEST(util_test, test_that_esr_returns_peers_list_with_deletions) { + bzn::utils_interface iface; // There is a swarm in the ESR called BluzelleSwarm4. It contins 2 nodes, there were originally 4, the 2nd and 4th // node have been deleted which will leave two 0 length strings. const std::string CONTRACT{"D5B3d7C061F817ab05aF9Fab3b61EEe036e4f4fc"}; const std::string SWARM_ID{"BluzelleSwarm4"}; const std::vector ACCEPTED_IDS{"MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEvsJhAghU+bWyVjiSg5VMHJKLqs2NGKGNmWTkl3zU8syKIPo+CEuXey7YAAS7pMOFErkjpMyi4FEWnG2ysuN1Pg==", "MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEbrgU0EQ2UHRG5UFFfcDIaqfarezPvT1uRJAA++8mRc3FgK3DYeTrOdVVopCBLA+EaNiJdkLDObvRkkFny6185g=="}; - const auto peer_ids = bzn::utils::esr::get_peer_ids( SWARM_ID, CONTRACT, bzn::utils::ROPSTEN_URL); + const auto peer_ids = iface.get_peer_ids( SWARM_ID, CONTRACT, bzn::utils::ROPSTEN_URL); EXPECT_EQ(ACCEPTED_IDS.size(), peer_ids.size()); EXPECT_EQ(ACCEPTED_IDS, peer_ids); @@ -427,12 +429,13 @@ TEST(util_test, test_that_esr_returns_peers_list_with_deletions) TEST(util_test, test_that_esr_fails_nicely) { + bzn::utils_interface iface; const std::string ESR_CONTRACT{"3a38a7ed11431975fa4a5403a246850479e7b930"}; const std::string ESR_URL{bzn::utils::ROPSTEN_URL+ "/uvek7IebbbHoP8Bb9NkV"}; // testing non existant node const std::string SWARM_ID{"testswarm-333333333333333333333333"}; - const auto peer_info = bzn::utils::esr::get_peer_info(SWARM_ID, "NOPE", ESR_CONTRACT, ESR_URL); + const auto peer_info = iface.get_peer_info(SWARM_ID, "NOPE", ESR_CONTRACT, ESR_URL); EXPECT_EQ(peer_info.uuid, "NOPE"); EXPECT_TRUE(peer_info.host.empty()); EXPECT_EQ(peer_info.port, 0); @@ -442,11 +445,12 @@ TEST(util_test, test_that_esr_fails_nicely) TEST(util_test, test_that_esr_works_with_large_swarm_id) { + bzn::utils_interface iface; const std::string ESR_CONTRACT{"3a38a7ed11431975fa4a5403a246850479e7b930"}; const std::string ESR_URL{bzn::utils::ROPSTEN_URL+ "/uvek7IebbbHoP8Bb9NkV"}; const std::string SWARM_ID{"testswarm-444444444444444444444444444444444444444444444444444444444444"}; - const auto peer_info = bzn::utils::esr::get_peer_info(SWARM_ID, "TestUUID1", ESR_CONTRACT, ESR_URL); + const auto peer_info = iface.get_peer_info(SWARM_ID, "TestUUID1", ESR_CONTRACT, ESR_URL); EXPECT_EQ(peer_info.uuid, "TestUUID1"); EXPECT_EQ(peer_info.host, "127.0.0.1"); EXPECT_EQ(peer_info.port, 51010); @@ -456,12 +460,13 @@ TEST(util_test, test_that_esr_works_with_large_swarm_id) TEST(util_test, test_that_esr_returns_peer_info_with_large_node_name) { + bzn::utils_interface iface; const std::string ESR_CONTRACT{"3a38a7ed11431975fa4a5403a246850479e7b930"}; const std::string ESR_URL{bzn::utils::ROPSTEN_URL+ "/uvek7IebbbHoP8Bb9NkV"}; // testing esr get peer info parser with a node name larger than 32 characters const std::string SWARM_ID{"testswarm-333333333333333333333333"}; - const auto peer_info = bzn::utils::esr::get_peer_info(SWARM_ID, "TestUUID2", ESR_CONTRACT, ESR_URL); + const auto peer_info = iface.get_peer_info(SWARM_ID, "TestUUID2", ESR_CONTRACT, ESR_URL); EXPECT_EQ(peer_info.uuid, "TestUUID2"); EXPECT_EQ(peer_info.host, "127.0.0.1"); EXPECT_EQ(peer_info.port, 51010); @@ -471,13 +476,14 @@ TEST(util_test, test_that_esr_returns_peer_info_with_large_node_name) TEST(util_test, test_that_live_esr_returns_peer_info) { + bzn::utils_interface iface; const std::string ESR_CONTRACT{"3a38a7ed11431975fa4a5403a246850479e7b930"}; const std::string ESR_URL{bzn::utils::ROPSTEN_URL+ "/uvek7IebbbHoP8Bb9NkV"}; { const std::string SWARM_ID{"debug-swarm"}; const std::string NODE_ID{"nodeuuid111111111111111111111111"}; - const auto peer_info = bzn::utils::esr::get_peer_info(SWARM_ID, NODE_ID, ESR_CONTRACT, ESR_URL); + const auto peer_info = iface.get_peer_info(SWARM_ID, NODE_ID, ESR_CONTRACT, ESR_URL); EXPECT_EQ(peer_info.uuid, NODE_ID); EXPECT_EQ(peer_info.host, "127.0.0.1"); EXPECT_EQ(peer_info.port, 51010); @@ -488,7 +494,7 @@ TEST(util_test, test_that_live_esr_returns_peer_info) // testing that the ESR_CONTRACT can be given with the 0x prepended. const std::string SWARM_ID{"debug-swarm"}; const std::string NODE_ID{"nodeuuid111111111111111111111111"}; - const auto peer_info = bzn::utils::esr::get_peer_info(SWARM_ID, NODE_ID, "0x" + ESR_CONTRACT, ESR_URL); + const auto peer_info = iface.get_peer_info(SWARM_ID, NODE_ID, "0x" + ESR_CONTRACT, ESR_URL); EXPECT_EQ(peer_info.uuid, NODE_ID); EXPECT_EQ(peer_info.host, "127.0.0.1"); EXPECT_EQ(peer_info.port, 51010); diff --git a/utils/utils_interface.hpp b/utils/utils_interface.hpp new file mode 100644 index 00000000..5ffa3c13 --- /dev/null +++ b/utils/utils_interface.hpp @@ -0,0 +1,31 @@ +// Copyright (C) 2019 Bluzelle +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License, version 3, +// as published by the Free Software Foundation. +// +// 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 . + +#pragma once +#include + +namespace bzn +{ + class utils_interface : public utils_interface_base + { + public: + std::vector get_peer_ids(const bzn::uuid_t& swarm_id, const std::string& esr_address, const std::string& url) const; + bzn::peer_address_t get_peer_info(const bzn::uuid_t& swarm_id, const std::string& peer_id, const std::string& esr_address, const std::string& url) const; + + // Performs an HTTP GET or POST and returns the body of the HTTP response + std::string sync_req(const std::string& url, const std::string& post = "") const; + }; +} + + diff --git a/utils/utils_interface_base.hpp b/utils/utils_interface_base.hpp new file mode 100644 index 00000000..bf51fab6 --- /dev/null +++ b/utils/utils_interface_base.hpp @@ -0,0 +1,36 @@ +// Copyright (C) 2019 Bluzelle +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License, version 3, +// as published by the Free Software Foundation. +// +// 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 . + +#pragma once +#include +#include +#include +#include + + +namespace bzn +{ + class utils_interface_base + { + public: + virtual std::vector get_peer_ids(const bzn::uuid_t& swarm_id, const std::string& esr_address, const std::string& url) const = 0; + virtual bzn::peer_address_t get_peer_info(const bzn::uuid_t& swarm_id, const std::string& peer_id, const std::string& esr_address, const std::string& url) const = 0; + + // Performs an HTTP GET or POST and returns the body of the HTTP response + virtual std::string sync_req(const std::string& url, const std::string& post = "") const = 0; + + virtual ~utils_interface_base() = default; + }; +} +