From 3945302a2419be0e9bea90ef3278b141714eaf1b Mon Sep 17 00:00:00 2001 From: Andrei Lebedev Date: Tue, 4 Sep 2018 07:14:26 +0300 Subject: [PATCH] BFT OS components (#1703) * BFT OS trunk * On demand OS transport (#1635) * On demand OS connection manager (#1645) * On-demand ordering gate and related fixes (#1675) Signed-off-by: Andrei Lebedev --- cmake/functions.cmake | 10 +- .../impl/interactive_common_cli.cpp | 2 +- irohad/consensus/yac/cluster_order.hpp | 10 +- .../yac/impl/supermajority_checker_impl.cpp | 10 +- .../yac/impl/supermajority_checker_impl.hpp | 9 +- irohad/consensus/yac/impl/yac.cpp | 207 ++++++------------ .../yac/impl/yac_crypto_provider_impl.cpp | 29 +-- .../yac/impl/yac_crypto_provider_impl.hpp | 6 +- irohad/consensus/yac/impl/yac_gate_impl.cpp | 4 +- irohad/consensus/yac/impl/yac_gate_impl.hpp | 4 +- .../yac/storage/impl/yac_block_storage.cpp | 2 +- .../yac/storage/impl/yac_proposal_storage.cpp | 11 +- .../yac/storage/impl/yac_vote_storage.cpp | 48 ++-- .../yac/storage/yac_block_storage.hpp | 11 +- irohad/consensus/yac/storage/yac_common.hpp | 8 +- .../yac/storage/yac_proposal_storage.hpp | 9 +- .../yac/storage/yac_vote_storage.hpp | 94 ++++---- .../consensus/yac/supermajority_checker.hpp | 11 +- .../yac/transport/impl/network_impl.cpp | 99 ++------- .../yac/transport/impl/network_impl.hpp | 41 +--- .../yac/transport/yac_network_interface.hpp | 48 +--- irohad/consensus/yac/yac.hpp | 42 +--- irohad/consensus/yac/yac_crypto_provider.hpp | 20 +- irohad/consensus/yac/yac_gate.hpp | 8 +- irohad/consensus/yac/yac_types.hpp | 22 ++ irohad/main/impl/ordering_init.cpp | 8 +- irohad/main/impl/ordering_init.hpp | 4 +- .../converters/impl/json_command_factory.cpp | 4 +- .../converters/impl/json_query_factory.cpp | 2 +- irohad/model/converters/json_common.hpp | 18 +- .../transport/impl/mst_transport_grpc.cpp | 5 +- irohad/network/impl/async_grpc_client.hpp | 7 +- irohad/network/ordering_gate.hpp | 2 +- irohad/network/ordering_service.hpp | 5 - irohad/ordering/CMakeLists.txt | 43 +++- .../impl/on_demand_connection_manager.cpp | 76 +++++++ .../impl/on_demand_connection_manager.hpp | 88 ++++++++ .../ordering/impl/on_demand_ordering_gate.cpp | 76 +++++++ .../ordering/impl/on_demand_ordering_gate.hpp | 81 +++++++ .../impl/on_demand_ordering_service_impl.cpp | 194 ++++++++++++++++ .../impl/on_demand_ordering_service_impl.hpp | 116 ++++++++++ .../impl/on_demand_os_client_grpc.cpp | 79 +++++++ .../impl/on_demand_os_client_grpc.hpp | 80 +++++++ .../impl/on_demand_os_server_grpc.cpp | 42 ++++ .../impl/on_demand_os_server_grpc.hpp | 43 ++++ irohad/ordering/impl/ordering_gate_impl.cpp | 1 + .../impl/ordering_gate_transport_grpc.hpp | 3 +- .../impl/ordering_service_transport_grpc.cpp | 14 +- ...l.cpp => single_peer_ordering_service.cpp} | 12 +- ...l.hpp => single_peer_ordering_service.hpp} | 8 +- .../ordering/on_demand_ordering_service.hpp | 29 +++ irohad/ordering/on_demand_os_transport.hpp | 133 +++++++++++ irohad/torii/impl/command_service.cpp | 1 + libs/cache/collection_set.hpp | 74 +++++++ libs/common/types.hpp | 36 ++- libs/common/visitor.hpp | 7 +- schema/ordering.proto | 25 +++ schema/yac.proto | 10 +- .../backend/protobuf/impl/proposal.cpp | 7 + shared_model/backend/protobuf/proposal.hpp | 5 + shared_model/interfaces/CMakeLists.txt | 1 + .../interfaces/iroha_internal/proposal.hpp | 20 +- .../iroha_internal/transaction_batch.cpp | 32 +-- .../iroha_internal/transaction_batch.hpp | 39 ---- .../transaction_batch_factory.cpp | 41 ++++ .../transaction_batch_factory.hpp | 63 ++++++ ...transaction_batch_template_definitions.hpp | 6 +- .../iroha_internal/transaction_sequence.cpp | 5 +- .../iroha_internal/transaction_sequence.hpp | 1 + test/framework/batch_helper.hpp | 30 +-- test/framework/mock_stream.h | 145 ++++++++++++ .../consensus/consensus_sunny_day.cpp | 26 +-- .../transport/ordering_gate_service_test.cpp | 21 +- .../irohad/consensus/yac/network_test.cpp | 4 +- .../consensus/yac/yac_block_storage_test.cpp | 4 +- .../yac/yac_crypto_provider_test.cpp | 4 +- .../irohad/consensus/yac/yac_gate_test.cpp | 16 +- .../module/irohad/consensus/yac/yac_mocks.hpp | 23 +- .../yac/yac_proposal_storage_test.cpp | 2 +- .../consensus/yac/yac_rainy_day_test.cpp | 37 ++-- .../yac/yac_simple_cold_case_test.cpp | 104 +++++---- .../consensus/yac/yac_sunny_day_test.cpp | 93 ++++---- .../consensus/yac/yac_unknown_peer_test.cpp | 26 +-- test/module/irohad/network/network_mocks.hpp | 1 + test/module/irohad/ordering/CMakeLists.txt | 26 +++ .../on_demand_connection_manager_test.cpp | 133 +++++++++++ .../ordering/on_demand_ordering_gate_test.cpp | 174 +++++++++++++++ .../on_demand_os_client_grpc_test.cpp | 135 ++++++++++++ .../on_demand_os_server_grpc_test.cpp | 114 ++++++++++ .../irohad/ordering/on_demand_os_test.cpp | 176 +++++++++++++++ .../module/irohad/ordering/ordering_mocks.hpp | 44 ++++ .../irohad/ordering/ordering_service_test.cpp | 9 +- test/module/libs/cache/CMakeLists.txt | 16 +- .../libs/cache/transaction_cache_test.cpp | 81 +++++++ .../backend_proto/proto_batch_test.cpp | 42 ++-- test/module/shared_model/interface_mocks.hpp | 41 +++- 96 files changed, 2963 insertions(+), 875 deletions(-) create mode 100644 irohad/consensus/yac/yac_types.hpp create mode 100644 irohad/ordering/impl/on_demand_connection_manager.cpp create mode 100644 irohad/ordering/impl/on_demand_connection_manager.hpp create mode 100644 irohad/ordering/impl/on_demand_ordering_gate.cpp create mode 100644 irohad/ordering/impl/on_demand_ordering_gate.hpp create mode 100644 irohad/ordering/impl/on_demand_ordering_service_impl.cpp create mode 100644 irohad/ordering/impl/on_demand_ordering_service_impl.hpp create mode 100644 irohad/ordering/impl/on_demand_os_client_grpc.cpp create mode 100644 irohad/ordering/impl/on_demand_os_client_grpc.hpp create mode 100644 irohad/ordering/impl/on_demand_os_server_grpc.cpp create mode 100644 irohad/ordering/impl/on_demand_os_server_grpc.hpp rename irohad/ordering/impl/{ordering_service_impl.cpp => single_peer_ordering_service.cpp} (94%) rename irohad/ordering/impl/{ordering_service_impl.hpp => single_peer_ordering_service.hpp} (95%) create mode 100644 irohad/ordering/on_demand_ordering_service.hpp create mode 100644 irohad/ordering/on_demand_os_transport.hpp create mode 100644 libs/cache/collection_set.hpp create mode 100644 shared_model/interfaces/iroha_internal/transaction_batch_factory.cpp create mode 100644 shared_model/interfaces/iroha_internal/transaction_batch_factory.hpp create mode 100644 test/framework/mock_stream.h create mode 100644 test/module/irohad/ordering/on_demand_connection_manager_test.cpp create mode 100644 test/module/irohad/ordering/on_demand_ordering_gate_test.cpp create mode 100644 test/module/irohad/ordering/on_demand_os_client_grpc_test.cpp create mode 100644 test/module/irohad/ordering/on_demand_os_server_grpc_test.cpp create mode 100644 test/module/irohad/ordering/on_demand_os_test.cpp create mode 100644 test/module/irohad/ordering/ordering_mocks.hpp create mode 100644 test/module/libs/cache/transaction_cache_test.cpp diff --git a/cmake/functions.cmake b/cmake/functions.cmake index 93f93523a4..64179cc28a 100644 --- a/cmake/functions.cmake +++ b/cmake/functions.cmake @@ -82,10 +82,16 @@ endfunction() function(compile_proto_only_grpc_to_cpp PROTO) string(REGEX REPLACE "\\.proto$" ".grpc.pb.h" GEN_GRPC_PB_HEADER ${PROTO}) string(REGEX REPLACE "\\.proto$" ".grpc.pb.cc" GEN_GRPC_PB ${PROTO}) + if(TESTING) + # Generate gRPC mock classes for services + set(GENERATE_MOCKS "generate_mock_code=true:") + string(REGEX REPLACE "\\.proto$" "_mock.grpc.pb.h" GEN_GRPC_PB_MOCK_HEADER ${PROTO}) + set(TEST_OUTPUT ${SCHEMA_OUT_DIR}/${GEN_GRPC_PB_MOCK_HEADER}) + endif(TESTING) add_custom_command( - OUTPUT ${SCHEMA_OUT_DIR}/${GEN_GRPC_PB_HEADER} ${SCHEMA_OUT_DIR}/${GEN_GRPC_PB} + OUTPUT ${SCHEMA_OUT_DIR}/${GEN_GRPC_PB_HEADER} ${SCHEMA_OUT_DIR}/${GEN_GRPC_PB} ${TEST_OUTPUT} COMMAND ${CMAKE_COMMAND} -E env LD_LIBRARY_PATH=${protobuf_LIBRARY_DIR}:$ENV{LD_LIBRARY_PATH} "${protoc_EXECUTABLE}" - ARGS -I${protobuf_INCLUDE_DIR} -I${CMAKE_CURRENT_SOURCE_DIR} ${ARGN} --grpc_out=${SCHEMA_OUT_DIR} --plugin=protoc-gen-grpc="${grpc_CPP_PLUGIN}" ${PROTO} + ARGS -I${protobuf_INCLUDE_DIR} -I${CMAKE_CURRENT_SOURCE_DIR} ${ARGN} --grpc_out=${GENERATE_MOCKS}${SCHEMA_OUT_DIR} --plugin=protoc-gen-grpc="${grpc_CPP_PLUGIN}" ${PROTO} DEPENDS grpc_cpp_plugin ${SCHEMA_PATH}/${PROTO} WORKING_DIRECTORY ${CMAKE_BINARY_DIR} ) diff --git a/iroha-cli/interactive/impl/interactive_common_cli.cpp b/iroha-cli/interactive/impl/interactive_common_cli.cpp index e2d0df4d47..6cf8c35335 100644 --- a/iroha-cli/interactive/impl/interactive_common_cli.cpp +++ b/iroha-cli/interactive/impl/interactive_common_cli.cpp @@ -143,7 +143,7 @@ namespace iroha_cli { params_description.value().end(), [¶ms](auto ¶m) { using namespace iroha; - promptString(param) | [&](auto &val) { + promptString(param) | [&](auto &&val) { if (not val.empty()) { // Update input cache param.cache = val; diff --git a/irohad/consensus/yac/cluster_order.hpp b/irohad/consensus/yac/cluster_order.hpp index 3b33cb51c7..d17db44c67 100644 --- a/irohad/consensus/yac/cluster_order.hpp +++ b/irohad/consensus/yac/cluster_order.hpp @@ -18,9 +18,11 @@ #ifndef IROHA_CLUSTER_ORDER_HPP #define IROHA_CLUSTER_ORDER_HPP -#include -#include #include +#include + +#include +#include "consensus/yac/yac_types.hpp" namespace shared_model { namespace interface { @@ -65,7 +67,7 @@ namespace iroha { std::vector> getPeers() const; - size_t getNumberOfPeers() const; + PeersNumberType getNumberOfPeers() const; virtual ~ClusterOrdering() = default; @@ -77,7 +79,7 @@ namespace iroha { std::vector> order); std::vector> order_; - uint32_t index_ = 0; + PeersNumberType index_ = 0; }; } // namespace yac } // namespace consensus diff --git a/irohad/consensus/yac/impl/supermajority_checker_impl.cpp b/irohad/consensus/yac/impl/supermajority_checker_impl.cpp index 20d0c05d34..0c6d47c7ff 100644 --- a/irohad/consensus/yac/impl/supermajority_checker_impl.cpp +++ b/irohad/consensus/yac/impl/supermajority_checker_impl.cpp @@ -33,8 +33,8 @@ namespace iroha { and peersSubset(signatures, peers); } - bool SupermajorityCheckerImpl::checkSize(uint64_t current, - uint64_t all) const { + bool SupermajorityCheckerImpl::checkSize(PeersNumberType current, + PeersNumberType all) const { if (current > all) { return false; } @@ -55,9 +55,9 @@ namespace iroha { })); } - bool SupermajorityCheckerImpl::hasReject(uint64_t frequent, - uint64_t voted, - uint64_t all) const { + bool SupermajorityCheckerImpl::hasReject(PeersNumberType frequent, + PeersNumberType voted, + PeersNumberType all) const { auto not_voted = all - voted; return not checkSize(frequent + not_voted, all); } diff --git a/irohad/consensus/yac/impl/supermajority_checker_impl.hpp b/irohad/consensus/yac/impl/supermajority_checker_impl.hpp index 894df51ac8..d86a37f80f 100644 --- a/irohad/consensus/yac/impl/supermajority_checker_impl.hpp +++ b/irohad/consensus/yac/impl/supermajority_checker_impl.hpp @@ -40,7 +40,8 @@ namespace iroha { const std::vector> &peers) const override; - virtual bool checkSize(uint64_t current, uint64_t all) const override; + virtual bool checkSize(PeersNumberType current, + PeersNumberType all) const override; virtual bool peersSubset( const shared_model::interface::types::SignatureRangeType @@ -48,9 +49,9 @@ namespace iroha { const std::vector> &peers) const override; - virtual bool hasReject(uint64_t frequent, - uint64_t voted, - uint64_t all) const override; + virtual bool hasReject(PeersNumberType frequent, + PeersNumberType voted, + PeersNumberType all) const override; }; } // namespace yac } // namespace consensus diff --git a/irohad/consensus/yac/impl/yac.cpp b/irohad/consensus/yac/impl/yac.cpp index 65ad0e53ee..1fe0f72b6c 100644 --- a/irohad/consensus/yac/impl/yac.cpp +++ b/irohad/consensus/yac/impl/yac.cpp @@ -46,11 +46,6 @@ namespace iroha { return result; } - template - static std::string cryptoError(const std::initializer_list &votes) { - return cryptoError>(votes); - } - std::shared_ptr Yac::create( YacVoteStorage vote_storage, std::shared_ptr network, @@ -83,41 +78,23 @@ namespace iroha { cluster_order_ = order; auto vote = crypto_->getVote(hash); + // TODO 10.06.2018 andrei: IR-1407 move YAC propagation strategy to a + // separate entity votingStep(vote); } - rxcpp::observable Yac::on_commit() { + rxcpp::observable Yac::onOutcome() { return notifier_.get_observable(); } // ------|Network notifications|------ - void Yac::on_vote(VoteMessage vote) { - std::lock_guard guard(mutex_); - if (crypto_->verify(vote)) { - applyVote(findPeer(vote), vote); - } else { - log_->warn(cryptoError({vote})); - } - } - - void Yac::on_commit(CommitMessage commit) { + void Yac::onState(std::vector state) { std::lock_guard guard(mutex_); - if (crypto_->verify(commit)) { - // Commit does not contain data about peer which sent the message - applyCommit(boost::none, commit); + if (crypto_->verify(state)) { + applyState(state); } else { - log_->warn(cryptoError(commit.votes)); - } - } - - void Yac::on_reject(RejectMessage reject) { - std::lock_guard guard(mutex_); - if (crypto_->verify(reject)) { - // Reject does not contain data about peer which sent the message - applyReject(boost::none, reject); - } else { - log_->warn(cryptoError(reject.votes)); + log_->warn(cryptoError(state)); } } @@ -133,7 +110,7 @@ namespace iroha { vote.hash.proposal_hash, vote.hash.block_hash); - network_->send_vote(cluster_order_.currentLeader(), vote); + network_->sendState(cluster_order_.currentLeader(), {vote}); cluster_order_.switchToNext(); if (cluster_order_.hasNext()) { timer_->invokeAfterDelay([this, vote] { this->votingStep(vote); }); @@ -157,137 +134,77 @@ namespace iroha { // ------|Apply data|------ - const char *kRejectMsg = "reject case"; - const char *kRejectOnHashMsg = "Reject case on hash {} achieved"; - - void Yac::applyCommit( - boost::optional> from, - const CommitMessage &commit) { + void Yac::applyState(const std::vector &state) { auto answer = - vote_storage_.store(commit, cluster_order_.getNumberOfPeers()); - answer | [&](const auto &answer) { - auto proposal_hash = getProposalHash(commit.votes).value(); - auto already_processed = - vote_storage_.getProcessingState(proposal_hash); - if (not already_processed) { - vote_storage_.markAsProcessedState(proposal_hash); - visit_in_place(answer, - [&](const CommitMessage &commit) { - notifier_.get_subscriber().on_next(commit); - }, - [&](const RejectMessage &reject) { - log_->warn(kRejectMsg); - // TODO 14/08/17 Muratov: work on reject case - // IR-497 - }); - } - this->closeRound(); - }; - } + vote_storage_.store(state, cluster_order_.getNumberOfPeers()); - void Yac::applyReject( - boost::optional> from, - const RejectMessage &reject) { - auto answer = - vote_storage_.store(reject, cluster_order_.getNumberOfPeers()); - answer | [&](const auto &answer) { - auto proposal_hash = getProposalHash(reject.votes).value(); - auto already_processed = - vote_storage_.getProcessingState(proposal_hash); + // TODO 10.06.2018 andrei: IR-1407 move YAC propagation strategy to a + // separate entity - if (not already_processed) { - vote_storage_.markAsProcessedState(proposal_hash); - visit_in_place(answer, - [&](const RejectMessage &reject) { - log_->warn(kRejectMsg); - // TODO 14/08/17 Muratov: work on reject case - // IR-497 - }, - [&](const CommitMessage &commit) { - this->propagateCommit(commit); - notifier_.get_subscriber().on_next(commit); - }); + answer | [&](const auto &answer) { + auto &proposal_hash = state.at(0).hash.proposal_hash; + + /* + * It is possible that a new peer with an outdated peers list may + * collect an outcome from a smaller number of peers which are + * included in set of `f` peers in the system. The new peer will not + * accept our message with valid supermajority because he cannot apply + * votes from unknown peers. + */ + if (state.size() > 1) { + // some peer has already collected commit/reject, so it is sent + if (vote_storage_.getProcessingState(proposal_hash) + == ProposalState::kNotSentNotProcessed) { + vote_storage_.nextProcessingState(proposal_hash); + log_->info( + "Received supermajority of votes for {}, skip propagation", + proposal_hash); + } } - this->closeRound(); - }; - } - - void Yac::applyVote( - boost::optional> from, - const VoteMessage &vote) { - if (from) { - log_->info("Apply vote: {} from ledger peer {}", - vote.hash.block_hash, - (*from)->address()); - } else { - log_->info("Apply vote: {} from unknown peer {}", - vote.hash.block_hash, - vote.signature->publicKey().hex()); - } - auto answer = - vote_storage_.store(vote, cluster_order_.getNumberOfPeers()); - - answer | [&](const auto &answer) { - auto &proposal_hash = vote.hash.proposal_hash; - auto already_processed = + auto processing_state = vote_storage_.getProcessingState(proposal_hash); - if (not already_processed) { - vote_storage_.markAsProcessedState(proposal_hash); - visit_in_place(answer, - [&](const CommitMessage &commit) { - // propagate for all - log_->info("Propagate commit {} to whole network", - vote.hash.block_hash); - this->propagateCommit(commit); - notifier_.get_subscriber().on_next(commit); - }, - [&](const RejectMessage &reject) { - // propagate reject for all - log_->info(kRejectOnHashMsg, proposal_hash); - this->propagateReject(reject); - }); - } else { - from | [&](const auto &from) { - visit_in_place(answer, - [&](const CommitMessage &commit) { - log_->info("Propagate commit {} directly to {}", - vote.hash.block_hash, - from->address()); - this->propagateCommitDirectly(*from, commit); - }, - [&](const RejectMessage &reject) { - log_->info(kRejectOnHashMsg, proposal_hash); - this->propagateRejectDirectly(*from, reject); - }); - }; + auto votes = [](const auto &state) { return state.votes; }; + + switch (processing_state) { + case ProposalState::kNotSentNotProcessed: + vote_storage_.nextProcessingState(proposal_hash); + log_->info("Propagate state {} to whole network", proposal_hash); + this->propagateState(visit_in_place(answer, votes)); + break; + case ProposalState::kSentNotProcessed: + vote_storage_.nextProcessingState(proposal_hash); + log_->info("Pass outcome for {} to pipeline", proposal_hash); + this->closeRound(); + notifier_.get_subscriber().on_next(answer); + break; + case ProposalState::kSentProcessed: + if (state.size() == 1) { + this->findPeer(state.at(0)) | [&](const auto &from) { + log_->info("Propagate state {} directly to {}", + proposal_hash, + from->address()); + this->propagateStateDirectly(*from, + visit_in_place(answer, votes)); + }; + } + break; } }; } // ------|Propagation|------ - void Yac::propagateCommit(const CommitMessage &msg) { - for (const auto &peer : cluster_order_.getPeers()) { - propagateCommitDirectly(*peer, msg); - } - } - - void Yac::propagateCommitDirectly(const shared_model::interface::Peer &to, - const CommitMessage &msg) { - network_->send_commit(to, msg); - } - - void Yac::propagateReject(const RejectMessage &msg) { + void Yac::propagateState(const std::vector &msg) { for (const auto &peer : cluster_order_.getPeers()) { - propagateRejectDirectly(*peer, msg); + propagateStateDirectly(*peer, msg); } } - void Yac::propagateRejectDirectly(const shared_model::interface::Peer &to, - const RejectMessage &msg) { - network_->send_reject(std::move(to), std::move(msg)); + void Yac::propagateStateDirectly(const shared_model::interface::Peer &to, + const std::vector &msg) { + network_->sendState(to, msg); } } // namespace yac diff --git a/irohad/consensus/yac/impl/yac_crypto_provider_impl.cpp b/irohad/consensus/yac/impl/yac_crypto_provider_impl.cpp index 12a8be83c6..889f39902b 100644 --- a/irohad/consensus/yac/impl/yac_crypto_provider_impl.cpp +++ b/irohad/consensus/yac/impl/yac_crypto_provider_impl.cpp @@ -29,27 +29,18 @@ namespace iroha { factory) : keypair_(keypair), factory_(std::move(factory)) {} - bool CryptoProviderImpl::verify(CommitMessage msg) { + bool CryptoProviderImpl::verify(const std::vector &msg) { return std::all_of( - std::begin(msg.votes), - std::end(msg.votes), - [this](const auto &vote) { return this->verify(vote); }); - } - - bool CryptoProviderImpl::verify(RejectMessage msg) { - return std::all_of( - std::begin(msg.votes), - std::end(msg.votes), - [this](const auto &vote) { return this->verify(vote); }); - } - - bool CryptoProviderImpl::verify(VoteMessage msg) { - auto serialized = - PbConverters::serializeVote(msg).hash().SerializeAsString(); - auto blob = shared_model::crypto::Blob(serialized); + std::begin(msg), std::end(msg), [](const auto &vote) { + auto serialized = + PbConverters::serializeVote(vote).hash().SerializeAsString(); + auto blob = shared_model::crypto::Blob(serialized); - return shared_model::crypto::CryptoVerifier<>::verify( - msg.signature->signedData(), blob, msg.signature->publicKey()); + return shared_model::crypto::CryptoVerifier<>::verify( + vote.signature->signedData(), + blob, + vote.signature->publicKey()); + }); } VoteMessage CryptoProviderImpl::getVote(YacHash hash) { diff --git a/irohad/consensus/yac/impl/yac_crypto_provider_impl.hpp b/irohad/consensus/yac/impl/yac_crypto_provider_impl.hpp index 41555fd88b..d410eed152 100644 --- a/irohad/consensus/yac/impl/yac_crypto_provider_impl.hpp +++ b/irohad/consensus/yac/impl/yac_crypto_provider_impl.hpp @@ -21,11 +21,7 @@ namespace iroha { std::shared_ptr factory); - bool verify(CommitMessage msg) override; - - bool verify(RejectMessage msg) override; - - bool verify(VoteMessage msg) override; + bool verify(const std::vector &msg) override; VoteMessage getVote(YacHash hash) override; diff --git a/irohad/consensus/yac/impl/yac_gate_impl.cpp b/irohad/consensus/yac/impl/yac_gate_impl.cpp index 34d8860ed6..6730fa16aa 100644 --- a/irohad/consensus/yac/impl/yac_gate_impl.cpp +++ b/irohad/consensus/yac/impl/yac_gate_impl.cpp @@ -73,7 +73,9 @@ namespace iroha { rxcpp::observable YacGateImpl::on_commit() { - return hash_gate_->on_commit().flat_map([this](auto commit_message) { + return hash_gate_->onOutcome().flat_map([this](auto message) { + // TODO 10.06.2018 andrei: IR-497 Work on reject case + auto commit_message = boost::get(message); // map commit to block if it is present or loaded from other peer return rxcpp::observable<>::create< shared_model::interface::BlockVariant>([this, commit_message]( diff --git a/irohad/consensus/yac/impl/yac_gate_impl.hpp b/irohad/consensus/yac/impl/yac_gate_impl.hpp index 86a654668d..8542d6e66a 100644 --- a/irohad/consensus/yac/impl/yac_gate_impl.hpp +++ b/irohad/consensus/yac/impl/yac_gate_impl.hpp @@ -18,11 +18,11 @@ #ifndef IROHA_YAC_GATE_IMPL_HPP #define IROHA_YAC_GATE_IMPL_HPP +#include "consensus/yac/yac_gate.hpp" + #include -#include #include "consensus/consensus_block_cache.hpp" -#include "consensus/yac/yac_gate.hpp" #include "consensus/yac/yac_hash_provider.hpp" #include "logger/logger.hpp" diff --git a/irohad/consensus/yac/storage/impl/yac_block_storage.cpp b/irohad/consensus/yac/storage/impl/yac_block_storage.cpp index 58762586db..c4d14d0e3d 100644 --- a/irohad/consensus/yac/storage/impl/yac_block_storage.cpp +++ b/irohad/consensus/yac/storage/impl/yac_block_storage.cpp @@ -26,7 +26,7 @@ namespace iroha { YacBlockStorage::YacBlockStorage( YacHash hash, - uint64_t peers_in_round, + PeersNumberType peers_in_round, std::shared_ptr supermajority_checker) : hash_(std::move(hash)), peers_in_round_(peers_in_round), diff --git a/irohad/consensus/yac/storage/impl/yac_proposal_storage.cpp b/irohad/consensus/yac/storage/impl/yac_proposal_storage.cpp index 2fb361ff95..3af5a5d6c9 100644 --- a/irohad/consensus/yac/storage/impl/yac_proposal_storage.cpp +++ b/irohad/consensus/yac/storage/impl/yac_proposal_storage.cpp @@ -40,18 +40,17 @@ namespace iroha { return iter; } // insert and return new - return block_storages_.emplace( - block_storages_.end(), - YacHash(proposal_hash, block_hash), - peers_in_round_, - supermajority_checker_); + return block_storages_.emplace(block_storages_.end(), + YacHash(proposal_hash, block_hash), + peers_in_round_, + supermajority_checker_); } // --------| public api |-------- YacProposalStorage::YacProposalStorage( ProposalHash hash, - uint64_t peers_in_round, + PeersNumberType peers_in_round, std::shared_ptr supermajority_checker) : current_state_(boost::none), hash_(std::move(hash)), diff --git a/irohad/consensus/yac/storage/impl/yac_vote_storage.cpp b/irohad/consensus/yac/storage/impl/yac_vote_storage.cpp index 33d369eb49..2967acd738 100644 --- a/irohad/consensus/yac/storage/impl/yac_vote_storage.cpp +++ b/irohad/consensus/yac/storage/impl/yac_vote_storage.cpp @@ -37,7 +37,7 @@ namespace iroha { } auto YacVoteStorage::findProposalStorage(const VoteMessage &msg, - uint64_t peers_in_round) { + PeersNumberType peers_in_round) { auto val = getProposalStorage(msg.hash.proposal_hash); if (val != proposal_storages_.end()) { return val; @@ -51,19 +51,10 @@ namespace iroha { // --------| public api |-------- - boost::optional YacVoteStorage::store(VoteMessage vote, - uint64_t peers_in_round) { - return findProposalStorage(vote, peers_in_round)->insert(vote); - } - - boost::optional YacVoteStorage::store(CommitMessage commit, - uint64_t peers_in_round) { - return insert_votes(commit.votes, peers_in_round); - } - - boost::optional YacVoteStorage::store(RejectMessage reject, - uint64_t peers_in_round) { - return insert_votes(reject.votes, peers_in_round); + boost::optional YacVoteStorage::store( + std::vector state, PeersNumberType peers_in_round) { + auto storage = findProposalStorage(state.at(0), peers_in_round); + return storage->insert(state); } bool YacVoteStorage::isHashCommitted(ProposalHash hash) { @@ -74,24 +65,23 @@ namespace iroha { return bool(iter->getState()); } - bool YacVoteStorage::getProcessingState(const ProposalHash &hash) { - return processing_state_.count(hash) != 0; + ProposalState YacVoteStorage::getProcessingState( + const ProposalHash &hash) { + return processing_state_[hash]; } - void YacVoteStorage::markAsProcessedState(const ProposalHash &hash) { - processing_state_.insert(hash); - } - - // --------| private api |-------- - - boost::optional YacVoteStorage::insert_votes( - std::vector &votes, uint64_t peers_in_round) { - if (not sameProposals(votes)) { - return boost::none; + void YacVoteStorage::nextProcessingState(const ProposalHash &hash) { + auto &val = processing_state_[hash]; + switch (val) { + case ProposalState::kNotSentNotProcessed: + val = ProposalState::kSentNotProcessed; + break; + case ProposalState::kSentNotProcessed: + val = ProposalState::kSentProcessed; + break; + case ProposalState::kSentProcessed: + break; } - - auto storage = findProposalStorage(votes.at(0), peers_in_round); - return storage->insert(votes); } } // namespace yac diff --git a/irohad/consensus/yac/storage/yac_block_storage.hpp b/irohad/consensus/yac/storage/yac_block_storage.hpp index 8d925b6636..a46156cd50 100644 --- a/irohad/consensus/yac/storage/yac_block_storage.hpp +++ b/irohad/consensus/yac/storage/yac_block_storage.hpp @@ -19,13 +19,14 @@ #define IROHA_YAC_BLOCK_VOTE_STORAGE_HPP #include -#include #include +#include #include "consensus/yac/impl/supermajority_checker_impl.hpp" #include "consensus/yac/messages.hpp" #include "consensus/yac/storage/storage_result.hpp" #include "consensus/yac/yac_hash_provider.hpp" +#include "consensus/yac/yac_types.hpp" #include "logger/logger.hpp" namespace iroha { @@ -46,7 +47,7 @@ namespace iroha { public: YacBlockStorage( YacHash hash, - uint64_t peers_in_round, + PeersNumberType peers_in_round, std::shared_ptr supermajority_checker = std::make_shared()); @@ -54,7 +55,7 @@ namespace iroha { * Try to insert vote to storage * @param msg - vote for insertion * @return actual state of storage, - * nullopt when storage doesn't has supermajority + * boost::none when storage doesn't have supermajority */ boost::optional insert(VoteMessage msg); @@ -62,7 +63,7 @@ namespace iroha { * Insert vector of votes to current storage * @param votes - bunch of votes for insertion * @return state of storage after insertion last vote, - * nullopt when storage doesn't has supermajority + * boost::none when storage doesn't have supermajority */ boost::optional insert(std::vector votes); @@ -120,7 +121,7 @@ namespace iroha { /** * Number of peers in current round */ - uint64_t peers_in_round_; + PeersNumberType peers_in_round_; /** * Provide functions to check supermajority diff --git a/irohad/consensus/yac/storage/yac_common.hpp b/irohad/consensus/yac/storage/yac_common.hpp index 30e22d14ab..be47baf2b6 100644 --- a/irohad/consensus/yac/storage/yac_common.hpp +++ b/irohad/consensus/yac/storage/yac_common.hpp @@ -42,7 +42,8 @@ namespace iroha { /** * Provide hash common for whole collection * @param votes - collection with votes - * @return hash, if collection has same proposal hash, otherwise nullopt + * @return hash, if collection has same proposal hash, + * otherwise boost::none */ boost::optional getProposalHash( const std::vector &votes); @@ -50,10 +51,13 @@ namespace iroha { /** * Get common hash from collection * @param votes - collection with votes - * @return hash, if collection elements have same hash, otherwise nullopt + * @return hash, if collection elements have same hash, + * otherwise boost::none */ boost::optional getHash(const std::vector &votes); + } // namespace yac } // namespace consensus } // namespace iroha + #endif // IROHA_YAC_COMMON_HPP diff --git a/irohad/consensus/yac/storage/yac_proposal_storage.hpp b/irohad/consensus/yac/storage/yac_proposal_storage.hpp index 2bc6431707..ad6330677e 100644 --- a/irohad/consensus/yac/storage/yac_proposal_storage.hpp +++ b/irohad/consensus/yac/storage/yac_proposal_storage.hpp @@ -19,13 +19,14 @@ #define IROHA_YAC_PROPOSAL_STORAGE_HPP #include -#include #include +#include #include "consensus/yac/impl/supermajority_checker_impl.hpp" #include "consensus/yac/storage/storage_result.hpp" #include "consensus/yac/storage/yac_block_storage.hpp" #include "consensus/yac/storage/yac_common.hpp" +#include "consensus/yac/yac_types.hpp" #include "logger/logger.hpp" namespace iroha { @@ -56,7 +57,7 @@ namespace iroha { YacProposalStorage( ProposalHash hash, - uint64_t peers_in_round, + PeersNumberType peers_in_round, std::shared_ptr supermajority_checker = std::make_shared()); @@ -64,7 +65,7 @@ namespace iroha { * Try to insert vote to storage * @param vote - object for insertion * @return result, that contains actual state of storage. - * Nullopt if not inserted, possible reasons - duplication, + * boost::none if not inserted, possible reasons - duplication, * wrong proposal hash. */ boost::optional insert(VoteMessage vote); @@ -138,7 +139,7 @@ namespace iroha { /** * Provide number of peers participated in current round */ - uint64_t peers_in_round_; + PeersNumberType peers_in_round_; /** * Provide functions to check supermajority diff --git a/irohad/consensus/yac/storage/yac_vote_storage.hpp b/irohad/consensus/yac/storage/yac_vote_storage.hpp index a54dcd3c9c..abae8cb3a7 100644 --- a/irohad/consensus/yac/storage/yac_vote_storage.hpp +++ b/irohad/consensus/yac/storage/yac_vote_storage.hpp @@ -19,19 +19,52 @@ #define IROHA_YAC_VOTE_STORAGE_HPP #include -#include -#include +#include #include +#include #include "consensus/yac/messages.hpp" // because messages passed by value #include "consensus/yac/storage/storage_result.hpp" // for Answer #include "consensus/yac/storage/yac_common.hpp" // for ProposalHash +#include "consensus/yac/yac_types.hpp" namespace iroha { namespace consensus { namespace yac { class YacProposalStorage; + /** + * Proposal outcome states for multicast propagation strategy + * + * Outcome is either CommitMessage, which guarantees that supermajority of + * votes for the proposal-block hashes is collected, or RejectMessage, + * which states that supermajority of votes for a block hash cannot be + * achieved + * + * kNotSentNotProcessed - outcome was not propagated in the network + * AND it was not passed to pipeline. Initial state after receiving an + * outcome from storage. Outcome with votes is propagated to the network + * in this state. + * + * kSentNotProcessed - outcome was propagated in the network + * AND it was not passed to pipeline. State can be set in two cases: + * 1. Outcome is received from the network. Some node has already achieved + * an outcome and has propagated it to the network, so the first state is + * skipped. + * 2. Outcome was propagated to the network + * Outcome is passed to pipeline in this state. + * + * kSentProcessed - outcome was propagated in the network + * AND it was passed to pipeline. Set after passing proposal to pipeline. + * This state is final. Receiving a network message in this state results + * in direct propagation of outcome to message sender. + */ + enum class ProposalState { + kNotSentNotProcessed, + kSentNotProcessed, + kSentProcessed + }; + /** * Class provide storage for votes and useful methods for it. */ @@ -55,44 +88,25 @@ namespace iroha { * @return - iter for required proposal storage */ auto findProposalStorage(const VoteMessage &msg, - uint64_t peers_in_round); + PeersNumberType peers_in_round); public: // --------| public api |-------- /** - * Insert vote in storage - * @param msg - current vote message + * Insert votes in storage + * @param state - current message with votes * @param peers_in_round - number of peers participated in round - * @return structure with result of inserting. Nullopt if mgs not valid. - */ - boost::optional store(VoteMessage msg, - uint64_t peers_in_round); - - /** - * Insert commit in storage - * @param commit - message with votes - * @param peers_in_round - number of peers in current consensus round * @return structure with result of inserting. - * Nullopt if commit not valid. + * boost::none if msg not valid. */ - boost::optional store(CommitMessage commit, - uint64_t peers_in_round); - - /** - * Insert reject message in storage - * @param reject - message with votes - * @param peers_in_round - number of peers in current consensus round - * @return structure with result of inserting. - * Nullopt if reject not valid. - */ - boost::optional store(RejectMessage reject, - uint64_t peers_in_round); + boost::optional store(std::vector state, + PeersNumberType peers_in_round); /** * Provide status about closing round with parameters hash * @param hash - target hash of round - * @return true, if rould closed + * @return true, if round closed */ bool isHashCommitted(ProposalHash hash); @@ -101,26 +115,19 @@ namespace iroha { * @param hash - target tag * @return value attached to parameter's hash. Default is false. */ - bool getProcessingState(const ProposalHash &hash); + ProposalState getProcessingState(const ProposalHash &hash); /** - * Mark hash as processed. + * Mark hash with following transition: + * kNotSentNotProcessed -> kSentNotProcessed + * kSentNotProcessed -> kSentProcessed + * kSentProcessed -> kSentProcessed + * @see ProposalState description for transition cases * @param hash - target tag */ - void markAsProcessedState(const ProposalHash &hash); + void nextProcessingState(const ProposalHash &hash); private: - // --------| private api |-------- - - /** - * Insert votes in storage - * @param votes - collection for insertion - * @param peers_in_round - number of peers in current round - * @return answer after insertion collection - */ - boost::optional insert_votes(std::vector &votes, - uint64_t peers_in_round); - // --------| fields |-------- /** @@ -132,10 +139,11 @@ namespace iroha { * Processing set provide user flags about processing some hashes. * If hash exists <=> processed */ - std::unordered_set processing_state_; + std::unordered_map processing_state_; }; } // namespace yac } // namespace consensus } // namespace iroha + #endif // IROHA_YAC_VOTE_STORAGE_HPP diff --git a/irohad/consensus/yac/supermajority_checker.hpp b/irohad/consensus/yac/supermajority_checker.hpp index 5226a98605..396e96eab8 100644 --- a/irohad/consensus/yac/supermajority_checker.hpp +++ b/irohad/consensus/yac/supermajority_checker.hpp @@ -19,6 +19,8 @@ #define IROHA_CONSENSUS_SUPERMAJORITY_CHECKER_HPP #include + +#include "consensus/yac/yac_types.hpp" #include "interfaces/common_objects/types.hpp" namespace shared_model { @@ -57,7 +59,8 @@ namespace iroha { * @param all number of peers * @return true if supermajority is possible or false otherwise */ - virtual bool checkSize(uint64_t current, uint64_t all) const = 0; + virtual bool checkSize(PeersNumberType current, + PeersNumberType all) const = 0; /** * Checks if signatures is a subset of signatures of peers @@ -80,9 +83,9 @@ namespace iroha { * @param all - number of peers in round * @return true, if reject */ - virtual bool hasReject(uint64_t frequent, - uint64_t voted, - uint64_t all) const = 0; + virtual bool hasReject(PeersNumberType frequent, + PeersNumberType voted, + PeersNumberType all) const = 0; }; } // namespace yac diff --git a/irohad/consensus/yac/transport/impl/network_impl.cpp b/irohad/consensus/yac/transport/impl/network_impl.cpp index 8ba043bf4d..d74c7fd3c2 100644 --- a/irohad/consensus/yac/transport/impl/network_impl.cpp +++ b/irohad/consensus/yac/transport/impl/network_impl.cpp @@ -20,12 +20,13 @@ #include #include -#include "yac.pb.h" #include "consensus/yac/messages.hpp" +#include "consensus/yac/storage/yac_common.hpp" #include "consensus/yac/transport/yac_pb_converters.hpp" #include "interfaces/common_objects/peer.hpp" #include "logger/logger.hpp" #include "network/impl/grpc_channel_builder.hpp" +#include "yac.pb.h" namespace iroha { namespace consensus { @@ -42,104 +43,44 @@ namespace iroha { handler_ = handler; } - void NetworkImpl::send_vote(const shared_model::interface::Peer &to, - VoteMessage vote) { - createPeerConnection(to); - - auto request = PbConverters::serializeVote(vote); - - async_call_->Call([&](auto context, auto cq) { - return peers_.at(to.address())->AsyncSendVote(context, request, cq); - }); - - async_call_->log_->info( - "Send vote {} to {}", vote.hash.block_hash, to.address()); - } - - void NetworkImpl::send_commit(const shared_model::interface::Peer &to, - const CommitMessage &commit) { + void NetworkImpl::sendState(const shared_model::interface::Peer &to, + const std::vector &state) { createPeerConnection(to); - proto::Commit request; - for (const auto &vote : commit.votes) { + proto::State request; + for (const auto &vote : state) { auto pb_vote = request.add_votes(); *pb_vote = PbConverters::serializeVote(vote); } async_call_->Call([&](auto context, auto cq) { - return peers_.at(to.address())->AsyncSendCommit(context, request, cq); + return peers_.at(to.address())->AsyncSendState(context, request, cq); }); - async_call_->log_->info("Send votes bundle[size={}] commit to {}", - commit.votes.size(), - to.address()); - } - - void NetworkImpl::send_reject(const shared_model::interface::Peer &to, - RejectMessage reject) { - createPeerConnection(to); - - proto::Reject request; - for (const auto &vote : reject.votes) { - auto pb_vote = request.add_votes(); - *pb_vote = PbConverters::serializeVote(vote); - } - - async_call_->Call([&](auto context, auto cq) { - return peers_.at(to.address())->AsyncSendReject(context, request, cq); - }); - - async_call_->log_->info("Send votes bundle[size={}] reject to {}", - reject.votes.size(), - to.address()); - } - - grpc::Status NetworkImpl::SendVote( - ::grpc::ServerContext *context, - const ::iroha::consensus::yac::proto::Vote *request, - ::google::protobuf::Empty *response) { - auto vote = *PbConverters::deserializeVote(*request); - async_call_->log_->info( - "Receive vote {} from {}", vote.hash.block_hash, context->peer()); - - handler_.lock()->on_vote(vote); - return grpc::Status::OK; + "Send votes bundle[size={}] to {}", state.size(), to.address()); } - grpc::Status NetworkImpl::SendCommit( + grpc::Status NetworkImpl::SendState( ::grpc::ServerContext *context, - const ::iroha::consensus::yac::proto::Commit *request, + const ::iroha::consensus::yac::proto::State *request, ::google::protobuf::Empty *response) { - CommitMessage commit(std::vector{}); + std::vector state; for (const auto &pb_vote : request->votes()) { auto vote = *PbConverters::deserializeVote(pb_vote); - commit.votes.push_back(vote); + state.push_back(vote); } - - async_call_->log_->info("Receive commit[size={}] from {}", - commit.votes.size(), - context->peer()); - - handler_.lock()->on_commit(commit); - return grpc::Status::OK; - } - - grpc::Status NetworkImpl::SendReject( - ::grpc::ServerContext *context, - const ::iroha::consensus::yac::proto::Reject *request, - ::google::protobuf::Empty *response) { - RejectMessage reject(std::vector{}); - for (const auto &pb_vote : request->votes()) { - auto vote = *PbConverters::deserializeVote(pb_vote); - reject.votes.push_back(vote); + if (not sameProposals(state)) { + async_call_->log_->info( + "Votes are stateless invalid: proposals are different, or empty " + "collection"); + return grpc::Status::CANCELLED; } - async_call_->log_->info("Receive reject[size={}] from {}", - reject.votes.size(), - context->peer()); + async_call_->log_->info( + "Receive votes[size={}] from {}", state.size(), context->peer()); - handler_.lock()->on_reject(reject); + handler_.lock()->onState(state); return grpc::Status::OK; } diff --git a/irohad/consensus/yac/transport/impl/network_impl.hpp b/irohad/consensus/yac/transport/impl/network_impl.hpp index 3efd4ee37f..44cf20b1d7 100644 --- a/irohad/consensus/yac/transport/impl/network_impl.hpp +++ b/irohad/consensus/yac/transport/impl/network_impl.hpp @@ -18,14 +18,16 @@ #ifndef IROHA_NETWORK_IMPL_HPP #define IROHA_NETWORK_IMPL_HPP +#include "consensus/yac/transport/yac_network_interface.hpp" // for YacNetwork +#include "yac.grpc.pb.h" + #include #include -#include "consensus/yac/transport/yac_network_interface.hpp" // for YacNetwork +#include "consensus/yac/messages.hpp" #include "interfaces/common_objects/types.hpp" #include "logger/logger.hpp" #include "network/impl/async_grpc_client.hpp" -#include "yac.grpc.pb.h" namespace iroha { namespace consensus { @@ -46,41 +48,18 @@ namespace iroha { async_call); void subscribe( std::shared_ptr handler) override; - void send_commit(const shared_model::interface::Peer &to, - const CommitMessage &commit) override; - void send_reject(const shared_model::interface::Peer &to, - RejectMessage reject) override; - void send_vote(const shared_model::interface::Peer &to, - VoteMessage vote) override; - - /** - * Receive vote from another peer; - * Naming is confusing, because this is rpc call that - * perform on another machine; - */ - grpc::Status SendVote( - ::grpc::ServerContext *context, - const ::iroha::consensus::yac::proto::Vote *request, - ::google::protobuf::Empty *response) override; - /** - * Receive commit from another peer; - * Naming is confusing, because this is rpc call that - * perform on another machine; - */ - grpc::Status SendCommit( - ::grpc::ServerContext *context, - const ::iroha::consensus::yac::proto::Commit *request, - ::google::protobuf::Empty *response) override; + void sendState(const shared_model::interface::Peer &to, + const std::vector &state) override; /** - * Receive reject from another peer; + * Receive votes from another peer; * Naming is confusing, because this is rpc call that * perform on another machine; */ - grpc::Status SendReject( + grpc::Status SendState( ::grpc::ServerContext *context, - const ::iroha::consensus::yac::proto::Reject *request, + const ::iroha::consensus::yac::proto::State *request, ::google::protobuf::Empty *response) override; private: @@ -95,7 +74,7 @@ namespace iroha { * Mapping of peer objects to connections */ std::unordered_map> + std::unique_ptr> peers_; /** diff --git a/irohad/consensus/yac/transport/yac_network_interface.hpp b/irohad/consensus/yac/transport/yac_network_interface.hpp index 59405e7317..f4de679485 100644 --- a/irohad/consensus/yac/transport/yac_network_interface.hpp +++ b/irohad/consensus/yac/transport/yac_network_interface.hpp @@ -19,40 +19,27 @@ #define IROHA_YAC_NETWORK_INTERFACE_HPP #include +#include namespace shared_model { namespace interface { class Peer; - } + } // namespace interface } // namespace shared_model namespace iroha { namespace consensus { namespace yac { - struct CommitMessage; - struct RejectMessage; struct VoteMessage; class YacNetworkNotifications { public: /** - * Callback on receiving commit message - * @param commit - provided message + * Callback on receiving collection of votes + * @param state - provided message */ - virtual void on_commit(CommitMessage commit) = 0; - - /** - * Callback on receiving reject message - * @param reject - provided message - */ - virtual void on_reject(RejectMessage reject) = 0; - - /** - * Callback on receiving vote message - * @param vote - provided message - */ - virtual void on_vote(VoteMessage vote) = 0; + virtual void onState(std::vector state) = 0; virtual ~YacNetworkNotifications() = default; }; @@ -63,28 +50,12 @@ namespace iroha { std::shared_ptr handler) = 0; /** - * Directly share commit message + * Directly share collection of votes * @param to - peer recipient - * @param commit - message for sending + * @param state - message for sending */ - virtual void send_commit(const shared_model::interface::Peer &to, - const CommitMessage &commit) = 0; - - /** - * Directly share reject message - * @param to - peer recipient - * @param reject - message for sending - */ - virtual void send_reject(const shared_model::interface::Peer &to, - RejectMessage reject) = 0; - - /** - * Directly share vote message - * @param to - peer recipient - * @param vote - message for sending - */ - virtual void send_vote(const shared_model::interface::Peer &to, - VoteMessage vote) = 0; + virtual void sendState(const shared_model::interface::Peer &to, + const std::vector &state) = 0; /** * Virtual destructor required for inheritance @@ -94,4 +65,5 @@ namespace iroha { } // namespace yac } // namespace consensus } // namespace iroha + #endif // IROHA_YAC_NETWORK_INTERFACE_HPP diff --git a/irohad/consensus/yac/yac.hpp b/irohad/consensus/yac/yac.hpp index b3d3e92cb6..79da02495d 100644 --- a/irohad/consensus/yac/yac.hpp +++ b/irohad/consensus/yac/yac.hpp @@ -58,17 +58,13 @@ namespace iroha { // ------|Hash gate|------ - virtual void vote(YacHash hash, ClusterOrdering order); + void vote(YacHash hash, ClusterOrdering order) override; - virtual rxcpp::observable on_commit(); + rxcpp::observable onOutcome() override; // ------|Network notifications|------ - virtual void on_commit(CommitMessage commit); - - virtual void on_reject(RejectMessage reject); - - virtual void on_vote(VoteMessage vote); + void onState(std::vector state) override; private: // ------|Private interface|------ @@ -87,45 +83,25 @@ namespace iroha { /** * Find corresponding peer in the ledger from vote message * @param vote message containing peer information - * @return peer if it is present in the ledger, nullopt otherwise + * @return peer if it is present in the ledger, boost::none otherwise */ boost::optional> findPeer(const VoteMessage &vote); // ------|Apply data|------ - - /** - * Methods take optional peer as argument since peer which sent the - * message could be missing from the ledger. This is the case when the - * top block in ledger does not correspond to consensus round number - */ - - void applyCommit( - boost::optional> - from, - const CommitMessage &commit); - void applyReject( - boost::optional> - from, - const RejectMessage &reject); - void applyVote(boost::optional< - std::shared_ptr> from, - const VoteMessage &vote); + void applyState(const std::vector &state); // ------|Propagation|------ - void propagateCommit(const CommitMessage &msg); - void propagateCommitDirectly(const shared_model::interface::Peer &to, - const CommitMessage &msg); - void propagateReject(const RejectMessage &msg); - void propagateRejectDirectly(const shared_model::interface::Peer &to, - const RejectMessage &msg); + void propagateState(const std::vector &msg); + void propagateStateDirectly(const shared_model::interface::Peer &to, + const std::vector &msg); // ------|Fields|------ YacVoteStorage vote_storage_; std::shared_ptr network_; std::shared_ptr crypto_; std::shared_ptr timer_; - rxcpp::subjects::subject notifier_; + rxcpp::subjects::subject notifier_; std::mutex mutex_; // ------|One round|------ diff --git a/irohad/consensus/yac/yac_crypto_provider.hpp b/irohad/consensus/yac/yac_crypto_provider.hpp index 344721a242..31f56a9363 100644 --- a/irohad/consensus/yac/yac_crypto_provider.hpp +++ b/irohad/consensus/yac/yac_crypto_provider.hpp @@ -18,14 +18,12 @@ #ifndef IROHA_YAC_CRYPTO_PROVIDER_HPP #define IROHA_YAC_CRYPTO_PROVIDER_HPP -#include "consensus/yac/yac_hash_provider.hpp" // for YacHash (passed by copy) +#include "consensus/yac/yac_hash_provider.hpp" // for YacHash (passed by copy) namespace iroha { namespace consensus { namespace yac { - struct CommitMessage; - struct RejectMessage; struct VoteMessage; class YacCryptoProvider { @@ -35,21 +33,7 @@ namespace iroha { * @param msg - for verification * @return true if signature correct */ - virtual bool verify(CommitMessage msg) = 0; - - /** - * Verify signatory of message - * @param msg - for verification - * @return true if signature correct - */ - virtual bool verify(RejectMessage msg) = 0; - - /** - * Verify signatory of message - * @param msg - for verification - * @return true if signature correct - */ - virtual bool verify(VoteMessage msg) = 0; + virtual bool verify(const std::vector &msg) = 0; /** * Generate vote for provided hash; diff --git a/irohad/consensus/yac/yac_gate.hpp b/irohad/consensus/yac/yac_gate.hpp index 41cdb651b3..c484566f00 100644 --- a/irohad/consensus/yac/yac_gate.hpp +++ b/irohad/consensus/yac/yac_gate.hpp @@ -18,8 +18,9 @@ #ifndef IROHA_YAC_GATE_HPP #define IROHA_YAC_GATE_HPP -#include "network/consensus_gate.hpp" #include +#include "consensus/yac/storage/storage_result.hpp" +#include "network/consensus_gate.hpp" namespace iroha { namespace consensus { @@ -27,7 +28,6 @@ namespace iroha { class YacHash; class ClusterOrdering; - struct CommitMessage; class YacGate : public network::ConsensusGate {}; @@ -43,10 +43,10 @@ namespace iroha { virtual void vote(YacHash hash, ClusterOrdering order) = 0; /** - * Observable with committed hashes in network + * Observable with consensus outcomes - commits and rejects - in network * @return observable for subscription */ - virtual rxcpp::observable on_commit() = 0; + virtual rxcpp::observable onOutcome() = 0; virtual ~HashGate() = default; }; diff --git a/irohad/consensus/yac/yac_types.hpp b/irohad/consensus/yac/yac_types.hpp new file mode 100644 index 0000000000..becf5f224d --- /dev/null +++ b/irohad/consensus/yac/yac_types.hpp @@ -0,0 +1,22 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef IROHA_YAC_TYPES_HPP +#define IROHA_YAC_TYPES_HPP + +#include + +namespace iroha { + namespace consensus { + namespace yac { + + /// Type for number of peers in round. + using PeersNumberType = size_t; + + } // namespace yac + } // namespace consensus +} // namespace iroha + +#endif // IROHA_YAC_TYPES_HPP diff --git a/irohad/main/impl/ordering_init.cpp b/irohad/main/impl/ordering_init.cpp index 06a8833227..e6bd426475 100644 --- a/irohad/main/impl/ordering_init.cpp +++ b/irohad/main/impl/ordering_init.cpp @@ -43,11 +43,10 @@ namespace iroha { size_t max_size, std::chrono::milliseconds delay_milliseconds, std::shared_ptr transport, - std::shared_ptr - persistent_state) { + std::shared_ptr persistent_state) { auto factory = std::make_unique>(); - return std::make_shared( + return std::make_shared( peer_query_factory, max_size, rxcpp::observable<>::interval(delay_milliseconds, @@ -61,8 +60,7 @@ namespace iroha { std::shared_ptr peer_query_factory, size_t max_size, std::chrono::milliseconds delay_milliseconds, - std::shared_ptr - persistent_state, + std::shared_ptr persistent_state, std::shared_ptr block_query_factory, std::shared_ptr> async_call) { diff --git a/irohad/main/impl/ordering_init.hpp b/irohad/main/impl/ordering_init.hpp index d501e2d300..c199e004a0 100644 --- a/irohad/main/impl/ordering_init.hpp +++ b/irohad/main/impl/ordering_init.hpp @@ -24,8 +24,8 @@ #include "logger/logger.hpp" #include "ordering/impl/ordering_gate_impl.hpp" #include "ordering/impl/ordering_gate_transport_grpc.hpp" -#include "ordering/impl/ordering_service_impl.hpp" #include "ordering/impl/ordering_service_transport_grpc.hpp" +#include "ordering/impl/single_peer_ordering_service.hpp" namespace iroha { @@ -93,7 +93,7 @@ namespace iroha { std::shared_ptr> async_call); - std::shared_ptr ordering_service; + std::shared_ptr ordering_service; std::shared_ptr ordering_gate; std::shared_ptr ordering_gate_transport; diff --git a/irohad/model/converters/impl/json_command_factory.cpp b/irohad/model/converters/impl/json_command_factory.cpp index 70fd48aadb..74cf2bf62f 100644 --- a/irohad/model/converters/impl/json_command_factory.cpp +++ b/irohad/model/converters/impl/json_command_factory.cpp @@ -44,8 +44,8 @@ namespace iroha { template <> struct Convert { template - boost::optional operator()(T &&x) { - auto des = makeFieldDeserializer(x); + boost::optional operator()(T &&x) const { + auto des = makeFieldDeserializer(std::forward(x)); auto address = des.String("address"); auto pubkey = des.String("peer_key"); diff --git a/irohad/model/converters/impl/json_query_factory.cpp b/irohad/model/converters/impl/json_query_factory.cpp index 024f06caf1..22ef346551 100644 --- a/irohad/model/converters/impl/json_query_factory.cpp +++ b/irohad/model/converters/impl/json_query_factory.cpp @@ -73,7 +73,7 @@ namespace iroha { optional_ptr JsonQueryFactory::deserialize( const std::string &query_json) { return stringToJson(query_json) | - [this](auto &json) { return this->deserialize(json); }; + [this](auto &&json) { return this->deserialize(json); }; } optional_ptr JsonQueryFactory::deserialize( diff --git a/irohad/model/converters/json_common.hpp b/irohad/model/converters/json_common.hpp index 6672df1988..f5170c8fbb 100644 --- a/irohad/model/converters/json_common.hpp +++ b/irohad/model/converters/json_common.hpp @@ -50,16 +50,16 @@ namespace iroha { * @return optional of output type from input value */ template - auto operator()(T &&x) { - return boost::optional(x); + auto operator()(T &&x) const { + return boost::optional(std::forward(x)); } }; template struct Convert> { template - auto operator()(T &&x) { - return hexstringToArray(x); + auto operator()(T &&x) const { + return hexstringToArray(std::forward(x)); } }; @@ -73,7 +73,7 @@ namespace iroha { */ template boost::optional deserializeField(const D &document, - const std::string &field) { + const std::string &field) { if (document.HasMember(field.c_str()) and document[field.c_str()].template Is()) { return document[field.c_str()].template Get(); @@ -233,8 +233,8 @@ namespace iroha { template <> struct Convert { template - auto operator()(T &&x) { - auto des = makeFieldDeserializer(x); + auto operator()(T &&x) const { + auto des = makeFieldDeserializer(std::forward(x)); return boost::make_optional(Signature()) | des.String(&Signature::pubkey, "pubkey") | des.String(&Signature::signature, "signature"); @@ -244,7 +244,7 @@ namespace iroha { template <> struct Convert { template - auto operator()(T &&x) { + auto operator()(T &&x) const { auto acc_signatures = [](auto init, auto &x) { return init | [&x](auto signatures) { return Convert()(x) | [&signatures](auto signature) { @@ -263,7 +263,7 @@ namespace iroha { template <> struct Convert { template - auto operator()(T &&x) { + auto operator()(T &&x) const { auto acc_hashes = [](auto init, auto &x) { return init | [&x](auto tx_hashes) -> boost::optional< diff --git a/irohad/multi_sig_transactions/transport/impl/mst_transport_grpc.cpp b/irohad/multi_sig_transactions/transport/impl/mst_transport_grpc.cpp index a8abfa59ce..50099e62f2 100644 --- a/irohad/multi_sig_transactions/transport/impl/mst_transport_grpc.cpp +++ b/irohad/multi_sig_transactions/transport/impl/mst_transport_grpc.cpp @@ -105,8 +105,9 @@ void MstTransportGrpc::subscribe( void MstTransportGrpc::sendState(const shared_model::interface::Peer &to, ConstRefState providing_state) { async_call_->log_->info("Propagate MstState to peer {}", to.address()); - auto client = transport::MstTransportGrpc::NewStub( - grpc::CreateChannel(to.address(), grpc::InsecureChannelCredentials())); + std::unique_ptr client = + transport::MstTransportGrpc::NewStub(grpc::CreateChannel( + to.address(), grpc::InsecureChannelCredentials())); transport::MstState protoState; auto peer = protoState.mutable_peer(); diff --git a/irohad/network/impl/async_grpc_client.hpp b/irohad/network/impl/async_grpc_client.hpp index c3e866ff0c..cbf14060f3 100644 --- a/irohad/network/impl/async_grpc_client.hpp +++ b/irohad/network/impl/async_grpc_client.hpp @@ -18,9 +18,12 @@ #ifndef IROHA_ASYNC_GRPC_CLIENT_HPP #define IROHA_ASYNC_GRPC_CLIENT_HPP +#include + #include #include -#include +#include +#include "logger/logger.hpp" namespace iroha { namespace network { @@ -72,7 +75,7 @@ namespace iroha { grpc::Status status; - std::unique_ptr> + std::unique_ptr> response_reader; }; diff --git a/irohad/network/ordering_gate.hpp b/irohad/network/ordering_gate.hpp index fc28f7eb51..0b76f72648 100644 --- a/irohad/network/ordering_gate.hpp +++ b/irohad/network/ordering_gate.hpp @@ -20,13 +20,13 @@ #include -#include "interfaces/iroha_internal/transaction_batch.hpp" #include "network/peer_communication_service.hpp" namespace shared_model { namespace interface { class Transaction; class Proposal; + class TransactionBatch; } // namespace interface } // namespace shared_model diff --git a/irohad/network/ordering_service.hpp b/irohad/network/ordering_service.hpp index 3d8778c62b..446486783c 100644 --- a/irohad/network/ordering_service.hpp +++ b/irohad/network/ordering_service.hpp @@ -24,11 +24,6 @@ namespace iroha { namespace network { class OrderingService : public network::OrderingServiceNotification { public: - /** - * Collect transactions from queue - * Passes the generated proposal to publishProposal - */ - virtual void generateProposal() = 0; /** * Transform model proposal to transport object and send to peers diff --git a/irohad/ordering/CMakeLists.txt b/irohad/ordering/CMakeLists.txt index d42263bcac..bdad9e903d 100644 --- a/irohad/ordering/CMakeLists.txt +++ b/irohad/ordering/CMakeLists.txt @@ -14,7 +14,7 @@ add_library(ordering_service impl/ordering_gate_impl.cpp - impl/ordering_service_impl.cpp + impl/single_peer_ordering_service.cpp impl/ordering_gate_transport_grpc.cpp impl/ordering_service_transport_grpc.cpp ) @@ -28,3 +28,44 @@ target_link_libraries(ordering_service ordering_grpc logger ) + +add_library(on_demand_ordering_service + impl/on_demand_ordering_service_impl.cpp + ) + +target_link_libraries(on_demand_ordering_service + tbb + shared_model_interfaces + shared_model_proto_backend + logger + ) + +add_library(on_demand_ordering_service_transport_grpc + impl/on_demand_os_server_grpc.cpp + impl/on_demand_os_client_grpc.cpp + ) + +target_link_libraries(on_demand_ordering_service_transport_grpc + shared_model_interfaces + shared_model_proto_backend + logger + ordering_grpc + ) + +add_library(on_demand_connection_manager + impl/on_demand_connection_manager.cpp + ) +target_link_libraries(on_demand_connection_manager + shared_model_interfaces + rxcpp + boost + ) + +add_library(on_demand_ordering_gate + impl/on_demand_ordering_gate.cpp + ) +target_link_libraries(on_demand_ordering_gate + shared_model_interfaces + rxcpp + boost + ) diff --git a/irohad/ordering/impl/on_demand_connection_manager.cpp b/irohad/ordering/impl/on_demand_connection_manager.cpp new file mode 100644 index 0000000000..026bec4a55 --- /dev/null +++ b/irohad/ordering/impl/on_demand_connection_manager.cpp @@ -0,0 +1,76 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "ordering/impl/on_demand_connection_manager.hpp" + +#include +#include "interfaces/iroha_internal/proposal.hpp" + +using namespace iroha::ordering; + +OnDemandConnectionManager::OnDemandConnectionManager( + std::shared_ptr factory, + CurrentPeers initial_peers, + rxcpp::observable peers) + : factory_(std::move(factory)), + subscription_(peers.subscribe([this](const auto &peers) { + // exclusive lock + std::lock_guard lock(mutex_); + + this->initializeConnections(peers); + })) { + // using start_with(initial_peers) results in deadlock + initializeConnections(initial_peers); +} + +void OnDemandConnectionManager::onTransactions(transport::Round round, + CollectionType transactions) { + // shared lock + std::shared_lock lock(mutex_); + + const PeerType types[] = {kCurrentRoundRejectConsumer, + kNextRoundRejectConsumer, + kNextRoundCommitConsumer}; + /* + * Transactions are always sent to the round after the next round (+2) + * There are 3 possibilities - next reject in the current round, first reject + * in the next round, and first commit in the round after the next round + * This can be visualised as a diagram, where: + * o - current round, x - next round, v - target round + * + * 0 1 2 + * 0 o x v + * 1 x v . + * 2 v . . + */ + const transport::Round rounds[] = { + {round.block_round, round.reject_round + 2}, + {round.block_round + 1, 2}, + {round.block_round + 2, 1}}; + + for (auto &&pair : boost::combine(types, rounds)) { + connections_.peers[boost::get<0>(pair)]->onTransactions(boost::get<1>(pair), + transactions); + } +} + +boost::optional +OnDemandConnectionManager::onRequestProposal(transport::Round round) { + // shared lock + std::shared_lock lock(mutex_); + + return connections_.peers[kIssuer]->onRequestProposal(round); +} + +void OnDemandConnectionManager::initializeConnections( + const CurrentPeers &peers) { + auto create_assign = [this](auto &ptr, auto &peer) { + ptr = factory_->create(*peer); + }; + + for (auto &&pair : boost::combine(connections_.peers, peers.peers)) { + create_assign(boost::get<0>(pair), boost::get<1>(pair)); + } +} diff --git a/irohad/ordering/impl/on_demand_connection_manager.hpp b/irohad/ordering/impl/on_demand_connection_manager.hpp new file mode 100644 index 0000000000..cf6c1a93d4 --- /dev/null +++ b/irohad/ordering/impl/on_demand_connection_manager.hpp @@ -0,0 +1,88 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef IROHA_ON_DEMAND_CONNECTION_MANAGER_HPP +#define IROHA_ON_DEMAND_CONNECTION_MANAGER_HPP + +#include "ordering/on_demand_os_transport.hpp" + +#include + +#include + +namespace iroha { + namespace ordering { + + /** + * Proxy class which redirects requests to appropriate peers + */ + class OnDemandConnectionManager : public transport::OdOsNotification { + public: + /** + * Responsibilities of individual peers from the peers array + * Transactions are sent to three ordering services: + * reject round for current block, reject round for next block, and + * commit for subsequent next round + * Proposal is requested from the current ordering service: issuer + */ + enum PeerType { + kCurrentRoundRejectConsumer = 0, + kNextRoundRejectConsumer, + kNextRoundCommitConsumer, + kIssuer, + kCount + }; + + /// Collection with value types which represent peers + template + using PeerCollectionType = std::array; + + /** + * Current peers to send transactions and request proposals + * @see PeerType for individual descriptions + */ + struct CurrentPeers { + PeerCollectionType> + peers; + }; + + OnDemandConnectionManager( + std::shared_ptr factory, + CurrentPeers initial_peers, + rxcpp::observable peers); + + void onTransactions(transport::Round round, + CollectionType transactions) override; + + boost::optional onRequestProposal( + transport::Round round) override; + + private: + /** + * Corresponding connections created by OdOsNotificationFactory + * @see PeerType for individual descriptions + */ + struct CurrentConnections { + PeerCollectionType> peers; + }; + + /** + * Initialize corresponding peers in connections_ using factory_ + * @param peers to initialize connections with + */ + void initializeConnections(const CurrentPeers &peers); + + std::shared_ptr factory_; + rxcpp::composite_subscription subscription_; + + CurrentConnections connections_; + + std::shared_timed_mutex mutex_; + }; + + } // namespace ordering +} // namespace iroha + +#endif // IROHA_ON_DEMAND_CONNECTION_MANAGER_HPP diff --git a/irohad/ordering/impl/on_demand_ordering_gate.cpp b/irohad/ordering/impl/on_demand_ordering_gate.cpp new file mode 100644 index 0000000000..dfb592fe84 --- /dev/null +++ b/irohad/ordering/impl/on_demand_ordering_gate.cpp @@ -0,0 +1,76 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "ordering/impl/on_demand_ordering_gate.hpp" + +#include "common/visitor.hpp" +#include "interfaces/iroha_internal/proposal.hpp" +#include "interfaces/iroha_internal/transaction_batch.hpp" + +using namespace iroha; +using namespace iroha::ordering; + +OnDemandOrderingGate::OnDemandOrderingGate( + std::shared_ptr ordering_service, + std::shared_ptr network_client, + rxcpp::observable events, + std::unique_ptr factory, + transport::Round initial_round) + : ordering_service_(std::move(ordering_service)), + network_client_(std::move(network_client)), + events_subscription_(events.subscribe([this](auto event) { + // exclusive lock + std::lock_guard lock(mutex_); + + visit_in_place(event, + [this](const BlockEvent &block) { + // block committed, increment block round + current_round_ = {block.height, 1}; + }, + [this](const EmptyEvent &empty) { + // no blocks committed, increment reject round + current_round_ = {current_round_.block_round, + current_round_.reject_round + 1}; + }); + + // notify our ordering service about new round + ordering_service_->onCollaborationOutcome(current_round_); + + // request proposal for the current round + auto proposal = network_client_->onRequestProposal(current_round_); + + // vote for the object received from the network + proposal_notifier_.get_subscriber().on_next( + std::move(proposal).value_or_eval([&] { + return proposal_factory_->unsafeCreateProposal( + current_round_.block_round, current_round_.reject_round, {}); + })); + })), + proposal_factory_(std::move(factory)), + current_round_(initial_round) {} + +void OnDemandOrderingGate::propagateTransaction( + std::shared_ptr transaction) + const { + throw std::logic_error("Method is deprecated. Use propagateBatch instead"); +} + +void OnDemandOrderingGate::propagateBatch( + const shared_model::interface::TransactionBatch &batch) const { + std::shared_lock lock(mutex_); + + network_client_->onTransactions(current_round_, batch.transactions()); +} + +rxcpp::observable> +OnDemandOrderingGate::on_proposal() { + return proposal_notifier_.get_observable(); +} + +void OnDemandOrderingGate::setPcs( + const iroha::network::PeerCommunicationService &pcs) { + throw std::logic_error( + "Method is deprecated. PCS observable should be set in ctor"); +} diff --git a/irohad/ordering/impl/on_demand_ordering_gate.hpp b/irohad/ordering/impl/on_demand_ordering_gate.hpp new file mode 100644 index 0000000000..7f58af07fb --- /dev/null +++ b/irohad/ordering/impl/on_demand_ordering_gate.hpp @@ -0,0 +1,81 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef IROHA_ON_DEMAND_ORDERING_GATE_HPP +#define IROHA_ON_DEMAND_ORDERING_GATE_HPP + +#include "network/ordering_gate.hpp" + +#include + +#include +#include +#include "interfaces/common_objects/types.hpp" +#include "interfaces/iroha_internal/proposal.hpp" +#include "interfaces/iroha_internal/unsafe_proposal_factory.hpp" +#include "ordering/on_demand_ordering_service.hpp" + +namespace iroha { + namespace ordering { + + /** + * Ordering gate which requests proposals from the ordering service + * votes for proposals, and passes committed proposals to the pipeline + */ + class OnDemandOrderingGate : public network::OrderingGate { + public: + /** + * Represents storage modification. Proposal round increment + */ + struct BlockEvent { + shared_model::interface::types::HeightType height; + }; + + /** + * Represents no storage modification. Reject round increment + */ + struct EmptyEvent {}; + + using BlockRoundEventType = boost::variant; + + OnDemandOrderingGate( + std::shared_ptr ordering_service, + std::shared_ptr network_client, + rxcpp::observable events, + std::unique_ptr + factory, + transport::Round initial_round); + + [[deprecated("Use propagateBatch")]] void propagateTransaction( + std::shared_ptr + transaction) const override; + + void propagateBatch(const shared_model::interface::TransactionBatch + &batch) const override; + + rxcpp::observable> + on_proposal() override; + + [[deprecated("Use ctor")]] void setPcs( + const iroha::network::PeerCommunicationService &pcs) override; + + private: + std::shared_ptr ordering_service_; + std::shared_ptr network_client_; + rxcpp::composite_subscription events_subscription_; + std::unique_ptr + proposal_factory_; + + transport::Round current_round_; + rxcpp::subjects::subject< + std::shared_ptr> + proposal_notifier_; + mutable std::shared_timed_mutex mutex_; + }; + + } // namespace ordering +} // namespace iroha + +#endif // IROHA_ON_DEMAND_ORDERING_GATE_HPP diff --git a/irohad/ordering/impl/on_demand_ordering_service_impl.cpp b/irohad/ordering/impl/on_demand_ordering_service_impl.cpp new file mode 100644 index 0000000000..fcacba995b --- /dev/null +++ b/irohad/ordering/impl/on_demand_ordering_service_impl.cpp @@ -0,0 +1,194 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "ordering/impl/on_demand_ordering_service_impl.hpp" + +#include + +#include +#include +#include "backend/protobuf/proposal.hpp" +#include "backend/protobuf/transaction.hpp" +#include "datetime/time.hpp" +#include "interfaces/iroha_internal/proposal.hpp" +#include "interfaces/transaction.hpp" + +using namespace iroha::ordering; + +/** + * First round after successful committing block + */ +const iroha::ordering::transport::RejectRoundType kFirstRound = 1; + +OnDemandOrderingServiceImpl::OnDemandOrderingServiceImpl( + size_t transaction_limit, + size_t number_of_proposals, + const transport::Round &initial_round) + : transaction_limit_(transaction_limit), + number_of_proposals_(number_of_proposals), + log_(logger::log("OnDemandOrderingServiceImpl")) { + onCollaborationOutcome(initial_round); +} + +// -------------------------| OnDemandOrderingService |------------------------- + +void OnDemandOrderingServiceImpl::onCollaborationOutcome( + transport::Round round) { + log_->info("onCollaborationOutcome => round[{}, {}]", + round.block_round, + round.reject_round); + // exclusive write lock + std::lock_guard guard(lock_); + log_->info("onCollaborationOutcome => write lock is acquired"); + + packNextProposals(round); + tryErase(); +} + +// ----------------------------| OdOsNotification |----------------------------- + +void OnDemandOrderingServiceImpl::onTransactions(transport::Round round, + CollectionType transactions) { + // read lock + std::shared_lock guard(lock_); + log_->info("onTransactions => collections size = {}, round[{}, {}]", + transactions.size(), + round.block_round, + round.reject_round); + + auto it = current_proposals_.find(round); + if (it != current_proposals_.end()) { + std::for_each(transactions.begin(), transactions.end(), [&it](auto &obj) { + it->second.push(std::move(obj)); + }); + log_->info("onTransactions => collection is inserted"); + } +} + +boost::optional +OnDemandOrderingServiceImpl::onRequestProposal(transport::Round round) { + // read lock + std::shared_lock guard(lock_); + auto proposal = proposal_map_.find(round); + if (proposal != proposal_map_.end()) { + return clone(*proposal->second); + } else { + return boost::none; + } +} + +// ---------------------------------| Private |--------------------------------- + +void OnDemandOrderingServiceImpl::packNextProposals( + const transport::Round &round) { + auto close_round = [this](transport::Round round) { + auto it = current_proposals_.find(round); + if (it != current_proposals_.end()) { + if (not it->second.empty()) { + proposal_map_.emplace(round, emitProposal(round)); + log_->info("packNextProposal: data has been fetched for round[{}, {}]", + round.block_round, + round.reject_round); + round_queue_.push(round); + } + current_proposals_.erase(it); + } + }; + + /* + * The possible cases can be visualised as a diagram, where: + * o - current round, x - next round, v - target round + * + * 0 1 2 + * 0 o x v + * 1 x v . + * 2 v . . + * + * Reject case: + * + * 0 1 2 3 + * 0 . o x v + * 1 x v . . + * 2 v . . . + * + * (0,1) - current round. Round (0,2) is closed for transactions. + * Round (0,3) is now receiving transactions. + * Rounds (1,) and (2,) do not change. + * + * Commit case: + * + * 0 1 2 + * 0 . . . + * 1 o x v + * 2 x v . + * 3 v . . + * + * (1,0) - current round. The diagram is similar to the initial case. + */ + + // close next reject round + close_round({round.block_round, round.reject_round + 1}); + + if (round.reject_round == kFirstRound) { + // new block round + close_round({round.block_round + 1, round.reject_round}); + + // remove current queues + current_proposals_.clear(); + // initialize the 3 diagonal rounds from the commit case diagram + for (uint32_t i = 0; i <= 2; ++i) { + current_proposals_[{round.block_round + i, round.reject_round + 2 - i}]; + } + } else { + // new reject round + current_proposals_[{round.block_round, round.reject_round + 2}]; + } +} + +OnDemandOrderingServiceImpl::ProposalType +OnDemandOrderingServiceImpl::emitProposal(const transport::Round &round) { + iroha::protocol::Proposal proto_proposal; + proto_proposal.set_height(round.block_round); + proto_proposal.set_created_time(iroha::time::now()); + log_->info("Mutable proposal generation, round[{}, {}]", + round.block_round, + round.reject_round); + + TransactionType current_tx; + using ProtoTxType = shared_model::proto::Transaction; + std::vector collection; + std::unordered_set inserted; + + // outer method should guarantee availability of at least one transaction in + // queue, also, code shouldn't fetch all transactions from queue. The rest + // will be lost. + auto ¤t_proposal = current_proposals_[round]; + while (current_proposal.try_pop(current_tx) + and collection.size() < transaction_limit_ + and inserted.insert(current_tx->hash().hex()).second) { + collection.push_back(std::move(current_tx)); + } + log_->info("Number of transactions in proposal = {}", collection.size()); + auto proto_txes = collection | boost::adaptors::transformed([](auto &tx) { + return static_cast(*tx); + }); + boost::for_each(proto_txes, [&proto_proposal](auto &&proto_tx) { + *(proto_proposal.add_transactions()) = std::move(proto_tx.getTransport()); + }); + + return std::make_unique( + std::move(proto_proposal)); +} + +void OnDemandOrderingServiceImpl::tryErase() { + if (round_queue_.size() >= number_of_proposals_) { + auto &round = round_queue_.front(); + proposal_map_.erase(round); + log_->info("tryErase: erased round[{}, {}]", + round.block_round, + round.reject_round); + round_queue_.pop(); + } +} diff --git a/irohad/ordering/impl/on_demand_ordering_service_impl.hpp b/irohad/ordering/impl/on_demand_ordering_service_impl.hpp new file mode 100644 index 0000000000..b690ac4ed8 --- /dev/null +++ b/irohad/ordering/impl/on_demand_ordering_service_impl.hpp @@ -0,0 +1,116 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef IROHA_ON_DEMAND_ORDERING_SERVICE_IMPL_HPP +#define IROHA_ON_DEMAND_ORDERING_SERVICE_IMPL_HPP + +#include "ordering/on_demand_ordering_service.hpp" + +#include +#include +#include + +#include + +#include "logger/logger.hpp" + +namespace iroha { + namespace ordering { + class OnDemandOrderingServiceImpl : public OnDemandOrderingService { + public: + /** + * Create on_demand ordering service with following options: + * @param transaction_limit - number of maximum transactions in a one + * proposal + * @param number_of_proposals - number of stored proposals, older will be + * removed. Default value is 3 + * @param initial_round - first round of agreement. + * Default value is {2, 1} since genesis block height is 1 + */ + explicit OnDemandOrderingServiceImpl( + size_t transaction_limit, + size_t number_of_proposals, + const transport::Round &initial_round); + + explicit OnDemandOrderingServiceImpl(size_t transaction_limit) + : OnDemandOrderingServiceImpl(transaction_limit, 3, {2, 1}) {} + + // --------------------- | OnDemandOrderingService |_--------------------- + + void onCollaborationOutcome(transport::Round round) override; + + // ----------------------- | OdOsNotification | -------------------------- + + void onTransactions(transport::Round, + CollectionType transactions) override; + + boost::optional onRequestProposal( + transport::Round round) override; + + private: + /** + * Packs new proposals and creates new rounds + * Note: method is not thread-safe + */ + void packNextProposals(const transport::Round &round); + + /** + * Removes last elements if it is required + * Method removes the oldest commit or chain of the oldest rejects + * Note: method is not thread-safe + */ + void tryErase(); + + /** + * @return packed proposal from the given round queue + * Note: method is not thread-safe + */ + ProposalType emitProposal(const transport::Round &round); + + /** + * Max number of transaction in one proposal + */ + size_t transaction_limit_; + + /** + * Max number of available proposals in one OS + */ + size_t number_of_proposals_; + + /** + * Queue which holds all rounds in linear order + */ + std::queue round_queue_; + + /** + * Map of available proposals + */ + std::unordered_map + proposal_map_; + + /** + * Proposals for current rounds + */ + std::unordered_map, + transport::RoundTypeHasher> + current_proposals_; + + /** + * Read write mutex for public methods + */ + std::shared_timed_mutex lock_; + + /** + * Logger instance + */ + logger::Logger log_; + }; + } // namespace ordering +} // namespace iroha + +#endif // IROHA_ON_DEMAND_ORDERING_SERVICE_IMPL_HPP diff --git a/irohad/ordering/impl/on_demand_os_client_grpc.cpp b/irohad/ordering/impl/on_demand_os_client_grpc.cpp new file mode 100644 index 0000000000..111e056d35 --- /dev/null +++ b/irohad/ordering/impl/on_demand_os_client_grpc.cpp @@ -0,0 +1,79 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "ordering/impl/on_demand_os_client_grpc.hpp" + +#include "backend/protobuf/proposal.hpp" +#include "network/impl/grpc_channel_builder.hpp" + +using namespace iroha::ordering::transport; + +OnDemandOsClientGrpc::OnDemandOsClientGrpc( + std::unique_ptr stub, + std::shared_ptr> + async_call, + std::function time_provider, + std::chrono::milliseconds proposal_request_timeout) + : log_(logger::log("OnDemandOsClientGrpc")), + stub_(std::move(stub)), + async_call_(std::move(async_call)), + time_provider_(std::move(time_provider)), + proposal_request_timeout_(proposal_request_timeout) {} + +void OnDemandOsClientGrpc::onTransactions(transport::Round round, + CollectionType transactions) { + proto::TransactionsRequest request; + request.mutable_round()->set_block_round(round.block_round); + request.mutable_round()->set_reject_round(round.reject_round); + for (auto &transaction : transactions) { + *request.add_transactions() = std::move( + static_cast(transaction.get()) + ->getTransport()); + } + + log_->debug("Propagating: '{}'", request.DebugString()); + + async_call_->Call([&](auto context, auto cq) { + return stub_->AsyncSendTransactions(context, request, cq); + }); +} + +boost::optional +OnDemandOsClientGrpc::onRequestProposal(transport::Round round) { + grpc::ClientContext context; + context.set_deadline(time_provider_() + proposal_request_timeout_); + proto::ProposalRequest request; + request.mutable_round()->set_block_round(round.block_round); + request.mutable_round()->set_reject_round(round.reject_round); + proto::ProposalResponse response; + auto status = stub_->RequestProposal(&context, request, &response); + if (not status.ok()) { + log_->warn("RPC failed: {}", status.error_message()); + return boost::none; + } + if (not response.has_proposal()) { + return boost::none; + } + return ProposalType{std::make_unique( + std::move(response.proposal()))}; +} + +OnDemandOsClientGrpcFactory::OnDemandOsClientGrpcFactory( + std::shared_ptr> + async_call, + std::function time_provider, + OnDemandOsClientGrpc::TimeoutType proposal_request_timeout) + : async_call_(std::move(async_call)), + time_provider_(time_provider), + proposal_request_timeout_(proposal_request_timeout) {} + +std::unique_ptr OnDemandOsClientGrpcFactory::create( + const shared_model::interface::Peer &to) { + return std::make_unique( + network::createClient(to.address()), + async_call_, + time_provider_, + proposal_request_timeout_); +} diff --git a/irohad/ordering/impl/on_demand_os_client_grpc.hpp b/irohad/ordering/impl/on_demand_os_client_grpc.hpp new file mode 100644 index 0000000000..d35682964f --- /dev/null +++ b/irohad/ordering/impl/on_demand_os_client_grpc.hpp @@ -0,0 +1,80 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef IROHA_ON_DEMAND_OS_TRANSPORT_SERVER_GRPC_HPP +#define IROHA_ON_DEMAND_OS_TRANSPORT_SERVER_GRPC_HPP + +#include "ordering/on_demand_os_transport.hpp" + +#include "network/impl/async_grpc_client.hpp" +#include "ordering.grpc.pb.h" + +namespace iroha { + namespace ordering { + namespace transport { + + /** + * gRPC client for on demand ordering service + */ + class OnDemandOsClientGrpc : public OdOsNotification { + public: + using TimepointType = std::chrono::system_clock::time_point; + using TimeoutType = std::chrono::milliseconds; + + /** + * Constructor is left public because testing required passing a mock + * stub interface + */ + OnDemandOsClientGrpc( + std::unique_ptr stub, + std::shared_ptr> + async_call, + std::function time_provider, + std::chrono::milliseconds proposal_request_timeout); + + void onTransactions(transport::Round round, + CollectionType transactions) override; + + boost::optional onRequestProposal( + transport::Round round) override; + + private: + logger::Logger log_; + std::unique_ptr stub_; + std::shared_ptr> + async_call_; + std::function time_provider_; + std::chrono::milliseconds proposal_request_timeout_; + }; + + class OnDemandOsClientGrpcFactory : public OdOsNotificationFactory { + public: + OnDemandOsClientGrpcFactory( + std::shared_ptr> + async_call, + std::function time_provider, + OnDemandOsClientGrpc::TimeoutType proposal_request_timeout); + + /** + * Create connection with insecure gRPC channel defined by + * network::createClient method + * @see network/impl/grpc_channel_builder.hpp + * This factory method can be used in production code + */ + std::unique_ptr create( + const shared_model::interface::Peer &to) override; + + private: + std::shared_ptr> + async_call_; + std::function time_provider_; + std::chrono::milliseconds proposal_request_timeout_; + }; + + } // namespace transport + } // namespace ordering +} // namespace iroha + +#endif // IROHA_ON_DEMAND_OS_TRANSPORT_SERVER_GRPC_HPP diff --git a/irohad/ordering/impl/on_demand_os_server_grpc.cpp b/irohad/ordering/impl/on_demand_os_server_grpc.cpp new file mode 100644 index 0000000000..18ed8a1369 --- /dev/null +++ b/irohad/ordering/impl/on_demand_os_server_grpc.cpp @@ -0,0 +1,42 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "ordering/impl/on_demand_os_server_grpc.hpp" + +#include "backend/protobuf/proposal.hpp" + +using namespace iroha::ordering::transport; + +OnDemandOsServerGrpc::OnDemandOsServerGrpc( + std::shared_ptr ordering_service) + : ordering_service_(ordering_service) {} + +grpc::Status OnDemandOsServerGrpc::SendTransactions( + ::grpc::ServerContext *context, + const proto::TransactionsRequest *request, + ::google::protobuf::Empty *response) { + Round round{request->round().block_round(), request->round().reject_round()}; + OdOsNotification::CollectionType transactions; + for (const auto &transaction : request->transactions()) { + transactions.push_back(std::make_unique( + iroha::protocol::Transaction(transaction))); + } + ordering_service_->onTransactions(round, std::move(transactions)); + return ::grpc::Status::OK; +} + +grpc::Status OnDemandOsServerGrpc::RequestProposal( + ::grpc::ServerContext *context, + const proto::ProposalRequest *request, + proto::ProposalResponse *response) { + ordering_service_->onRequestProposal( + {request->round().block_round(), request->round().reject_round()}) + | [&](auto &&proposal) { + *response->mutable_proposal() = std::move( + static_cast(proposal.get()) + ->getTransport()); + }; + return ::grpc::Status::OK; +} diff --git a/irohad/ordering/impl/on_demand_os_server_grpc.hpp b/irohad/ordering/impl/on_demand_os_server_grpc.hpp new file mode 100644 index 0000000000..fcbeebf536 --- /dev/null +++ b/irohad/ordering/impl/on_demand_os_server_grpc.hpp @@ -0,0 +1,43 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef IROHA_ON_DEMAND_OS_TRANSPORT_SERVER_GRPC_HPP +#define IROHA_ON_DEMAND_OS_TRANSPORT_SERVER_GRPC_HPP + +#include "ordering/on_demand_os_transport.hpp" + +#include "ordering.grpc.pb.h" + +namespace iroha { + namespace ordering { + namespace transport { + + /** + * gRPC server for on demand ordering service + */ + class OnDemandOsServerGrpc : public proto::OnDemandOrdering::Service { + public: + explicit OnDemandOsServerGrpc( + std::shared_ptr ordering_service); + + grpc::Status SendTransactions( + ::grpc::ServerContext *context, + const proto::TransactionsRequest *request, + ::google::protobuf::Empty *response) override; + + grpc::Status RequestProposal( + ::grpc::ServerContext *context, + const proto::ProposalRequest *request, + proto::ProposalResponse *response) override; + + private: + std::shared_ptr ordering_service_; + }; + + } // namespace transport + } // namespace ordering +} // namespace iroha + +#endif // IROHA_ON_DEMAND_OS_TRANSPORT_SERVER_GRPC_HPP diff --git a/irohad/ordering/impl/ordering_gate_impl.cpp b/irohad/ordering/impl/ordering_gate_impl.cpp index 45a2aa3d35..0760c42eb5 100644 --- a/irohad/ordering/impl/ordering_gate_impl.cpp +++ b/irohad/ordering/impl/ordering_gate_impl.cpp @@ -22,6 +22,7 @@ #include "interfaces/iroha_internal/block.hpp" #include "interfaces/iroha_internal/proposal.hpp" +#include "interfaces/iroha_internal/transaction_batch.hpp" #include "interfaces/transaction.hpp" namespace iroha { diff --git a/irohad/ordering/impl/ordering_gate_transport_grpc.hpp b/irohad/ordering/impl/ordering_gate_transport_grpc.hpp index 230a4f322c..9630b7dc82 100644 --- a/irohad/ordering/impl/ordering_gate_transport_grpc.hpp +++ b/irohad/ordering/impl/ordering_gate_transport_grpc.hpp @@ -60,7 +60,8 @@ namespace iroha { private: std::weak_ptr subscriber_; - std::unique_ptr client_; + std::unique_ptr + client_; std::shared_ptr> async_call_; std::unique_ptrlog_->error("No subscriber"); } else { - auto batch_result = - shared_model::interface::TransactionBatch::createTransactionBatch< + auto batch_result = shared_model::interface::TransactionBatchFactory:: + createTransactionBatch< shared_model::validation::DefaultSignedTransactionValidator>( std::make_shared( iroha::protocol::Transaction(*request))); @@ -65,8 +66,8 @@ grpc::Status OrderingServiceTransportGrpc::onBatch( return std::make_shared(tx); }); - auto batch_result = - shared_model::interface::TransactionBatch::createTransactionBatch( + auto batch_result = shared_model::interface::TransactionBatchFactory:: + createTransactionBatch( txs, shared_model::validation::DefaultSignedTransactionsValidator()); batch_result.match( @@ -87,8 +88,9 @@ void OrderingServiceTransportGrpc::publishProposal( std::unique_ptr proposal, const std::vector &peers) { async_call_->log_->info("OrderingServiceTransportGrpc::publishProposal"); - std::unordered_map> + std::unordered_map< + std::string, + std::unique_ptr> peers_map; for (const auto &peer : peers) { peers_map[peer] = diff --git a/irohad/ordering/impl/ordering_service_impl.cpp b/irohad/ordering/impl/single_peer_ordering_service.cpp similarity index 94% rename from irohad/ordering/impl/ordering_service_impl.cpp rename to irohad/ordering/impl/single_peer_ordering_service.cpp index 9647ce2d4d..8f15dbe0fc 100644 --- a/irohad/ordering/impl/ordering_service_impl.cpp +++ b/irohad/ordering/impl/single_peer_ordering_service.cpp @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -#include "ordering/impl/ordering_service_impl.hpp" +#include "ordering/impl/single_peer_ordering_service.hpp" #include #include @@ -16,7 +16,7 @@ namespace iroha { namespace ordering { - OrderingServiceImpl::OrderingServiceImpl( + SinglePeerOrderingService::SinglePeerOrderingService( std::shared_ptr peer_query_factory, size_t max_size, rxcpp::observable proposal_timeout, @@ -68,7 +68,7 @@ namespace iroha { } } - void OrderingServiceImpl::onBatch( + void SinglePeerOrderingService::onBatch( shared_model::interface::TransactionBatch &&batch) { std::shared_lock batch_prop_lock( batch_prop_mutex_); @@ -84,7 +84,7 @@ namespace iroha { transactions_.get_subscriber().on_next(ProposalEvent::kBatchEvent); } - void OrderingServiceImpl::generateProposal() { + void SinglePeerOrderingService::generateProposal() { std::lock_guard lock(batch_prop_mutex_); log_->info("Start proposal generation"); std::vector> txs; @@ -125,7 +125,7 @@ namespace iroha { }); } - void OrderingServiceImpl::publishProposal( + void SinglePeerOrderingService::publishProposal( std::unique_ptr proposal) { auto peers = peer_query_factory_->createPeerQuery() | [](const auto &query) { return query->getLedgerPeers(); }; @@ -141,7 +141,7 @@ namespace iroha { } } - OrderingServiceImpl::~OrderingServiceImpl() { + SinglePeerOrderingService::~SinglePeerOrderingService() { handle_.unsubscribe(); } } // namespace ordering diff --git a/irohad/ordering/impl/ordering_service_impl.hpp b/irohad/ordering/impl/single_peer_ordering_service.hpp similarity index 95% rename from irohad/ordering/impl/ordering_service_impl.hpp rename to irohad/ordering/impl/single_peer_ordering_service.hpp index f0c6f7c2dc..87fc1b78b9 100644 --- a/irohad/ordering/impl/ordering_service_impl.hpp +++ b/irohad/ordering/impl/single_peer_ordering_service.hpp @@ -34,7 +34,7 @@ namespace iroha { * concurrent queue * Sends proposal by given timer interval and proposal size */ - class OrderingServiceImpl : public network::OrderingService { + class SinglePeerOrderingService : public network::OrderingService { public: using TimeoutType = long; /** @@ -48,7 +48,7 @@ namespace iroha { * @param factory is used to generate proposals * @param is_async whether proposals are generated in a separate thread */ - OrderingServiceImpl( + SinglePeerOrderingService( std::shared_ptr peer_query_factory, size_t max_size, rxcpp::observable proposal_timeout, @@ -64,7 +64,7 @@ namespace iroha { */ void onBatch(shared_model::interface::TransactionBatch &&batch) override; - ~OrderingServiceImpl() override; + ~SinglePeerOrderingService() override; protected: /** @@ -84,7 +84,7 @@ namespace iroha { * Collect transactions from queue * Passes the generated proposal to publishProposal */ - void generateProposal() override; + void generateProposal(); std::shared_ptr peer_query_factory_; diff --git a/irohad/ordering/on_demand_ordering_service.hpp b/irohad/ordering/on_demand_ordering_service.hpp new file mode 100644 index 0000000000..aa3fc71de8 --- /dev/null +++ b/irohad/ordering/on_demand_ordering_service.hpp @@ -0,0 +1,29 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef IROHA_ON_DEMAND_ORDERING_SERVICE_HPP +#define IROHA_ON_DEMAND_ORDERING_SERVICE_HPP + +#include "ordering/on_demand_os_transport.hpp" + +namespace iroha { + namespace ordering { + + /** + * Ordering Service aka OS which can share proposals by request + */ + class OnDemandOrderingService : public transport::OdOsNotification { + public: + /** + * Method which should be invoked on outcome of collaboration for round + * @param round - proposal round which has started + */ + virtual void onCollaborationOutcome(transport::Round round) = 0; + }; + + } // namespace ordering +} // namespace iroha + +#endif // IROHA_ON_DEMAND_ORDERING_SERVICE_HPP diff --git a/irohad/ordering/on_demand_os_transport.hpp b/irohad/ordering/on_demand_os_transport.hpp new file mode 100644 index 0000000000..c9d6311318 --- /dev/null +++ b/irohad/ordering/on_demand_os_transport.hpp @@ -0,0 +1,133 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef IROHA_ON_DEMAND_OS_TRANSPORT_HPP +#define IROHA_ON_DEMAND_OS_TRANSPORT_HPP + +#include +#include +#include +#include +#include + +#include +#include + +namespace shared_model { + namespace interface { + class Transaction; + class Proposal; + class Peer; + } // namespace interface +} // namespace shared_model + +namespace iroha { + namespace ordering { + namespace transport { + + /** + * Type of round indexing by blocks + */ + using BlockRoundType = uint64_t; + + /** + * Type of round indexing by reject before new block commit + */ + using RejectRoundType = uint32_t; + + /** + * Type of proposal round + */ + struct Round { + BlockRoundType block_round; + RejectRoundType reject_round; + + bool operator<(const Round &rhs) const { + return std::tie(block_round, reject_round) + < std::tie(rhs.block_round, rhs.reject_round); + } + + bool operator==(const Round &rhs) const { + return std::tie(block_round, reject_round) + == std::tie(rhs.block_round, rhs.reject_round); + } + }; + + /** + * Class provides hash function for Round + */ + class RoundTypeHasher { + public: + std::size_t operator()(const Round &val) const { + size_t seed = 0; + boost::hash_combine(seed, val.block_round); + boost::hash_combine(seed, val.reject_round); + return seed; + } + }; + + /** + * Notification interface of on demand ordering service. + */ + class OdOsNotification { + public: + /** + * Type of stored proposals + */ + using ProposalType = std::unique_ptr; + + /** + * Type of stored transactions + */ + using TransactionType = + std::shared_ptr; + + /** + * Type of inserted collections + */ + using CollectionType = std::vector; + + /** + * Callback on receiving transactions + * @param round - expected proposal round + * @param transactions - vector of passed transactions + */ + virtual void onTransactions(Round round, + CollectionType transactions) = 0; + + /** + * Callback on request about proposal + * @param round - number of collaboration round. + * Calculated as block_height + 1 + * @return proposal for requested round + */ + virtual boost::optional onRequestProposal( + Round round) = 0; + + virtual ~OdOsNotification() = default; + }; + + /** + * Factory for creating communication interface to a specific peer + */ + class OdOsNotificationFactory { + public: + /** + * Create corresponding OdOsNotification interface for peer + * Returned pointer is guaranteed to be not equal to nullptr + * @param peer - peer to connect + * @return connection represented with OdOsNotification interface + */ + virtual std::unique_ptr create( + const shared_model::interface::Peer &to) = 0; + + virtual ~OdOsNotificationFactory() = default; + }; + + } // namespace transport + } // namespace ordering +} // namespace iroha + +#endif // IROHA_ON_DEMAND_OS_TRANSPORT_HPP diff --git a/irohad/torii/impl/command_service.cpp b/irohad/torii/impl/command_service.cpp index 46bf1da02c..d2839cd060 100644 --- a/irohad/torii/impl/command_service.cpp +++ b/irohad/torii/impl/command_service.cpp @@ -29,6 +29,7 @@ #include "common/timeout.hpp" #include "common/types.hpp" #include "cryptography/default_hash_provider.hpp" +#include "interfaces/iroha_internal/transaction_batch_factory.hpp" #include "validators/default_validator.hpp" namespace torii { diff --git a/libs/cache/collection_set.hpp b/libs/cache/collection_set.hpp new file mode 100644 index 0000000000..f42e34fcfd --- /dev/null +++ b/libs/cache/collection_set.hpp @@ -0,0 +1,74 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef IROHA_COLLECTION_SET_HPP +#define IROHA_COLLECTION_SET_HPP + +#include +#include + +namespace iroha { + namespace set { + + /** + * Class provides concurrent implementation of collection which operates + * with collections on insert and remove + * + * @tparam Key - type of holding values + * @tparam Hash - hash representation of Key values + * @tparam KeyEqual - equality type + */ + template , + typename KeyEqual = std::equal_to> + class CollectionSet { + public: + CollectionSet() = default; + + using value_type = Key; + + /** + * Merge our and passed collection + * @tparam Collection - type of passed collection + * @param collection - elements to insert + */ + template + void insertValues(Collection &&collection) { + std::lock_guard lock(mutex_); + set_.insert(collection.begin(), collection.end()); + } + + /** + * Remove all elements which are occured in the passed collection + * @tparam Collection - type of passed collection + * @param collection - elements to remove + */ + template + void removeValues(Collection &&collection) { + std::lock_guard lock(mutex_); + for (auto &&val : collection) { + set_.erase(val); + } + } + + /** + * Blocking walk through the collection + * @tparam Callable - type of functor + * @param callable - functor for invocation on each element + */ + template + void forEach(Callable &&callable) const { + std::shared_lock lock(mutex_); + std::for_each(set_.begin(), set_.end(), callable); + } + + private: + std::unordered_set set_; + mutable std::shared_timed_mutex mutex_; + }; + } // namespace set +} // namespace iroha + +#endif // IROHA_COLLECTION_SET_HPP diff --git a/libs/common/types.hpp b/libs/common/types.hpp index 76ff25355c..8b8f031998 100644 --- a/libs/common/types.hpp +++ b/libs/common/types.hpp @@ -158,6 +158,13 @@ namespace iroha { * boost::optional d = f() * | g; * + * std::forward should be used in any reference of arguments because + * operator bool, operator*, and operator() of arguments can have + * different implementation with ref-qualifiers + * + * Trailing return type checks that result of applying function to + * unwrapped value results in non-void type + * * @tparam T - monadic type * @tparam Transform - transform function type * @param t - monadic value @@ -166,11 +173,13 @@ namespace iroha { * @return monadic value, which can be of another type */ template - auto operator|(T t, Transform f) -> - typename std::enable_if::value, - decltype(f(*t))>::type { - if (t) { - return f(*t); + auto operator|(T &&t, Transform &&f) -> std::enable_if_t< + not std::is_same< + decltype(std::forward(f)(*std::forward(t))), + void>::value, + decltype(std::forward(f)(*std::forward(t)))> { + if (std::forward(t)) { + return std::forward(f)(*std::forward(t)); } return {}; } @@ -186,18 +195,25 @@ namespace iroha { * * f() | g; * + * std::forward should be used in any reference of arguments because + * operator bool, operator*, and operator() of arguments can have + * different implementation with ref-qualifiers + * + * Trailing return type checks that result of applying function to + * unwrapped value results in void type + * * @tparam T - monadic type * @tparam Transform - transform function type * @param t - monadic value * @param f - function, which takes dereferenced value, and returns * wrapped value - * @return monadic value, which can be of another type */ template - auto operator|(T t, Transform f) -> typename std::enable_if< - std::is_same::value>::type { - if (t) { - f(*t); + auto operator|(T &&t, Transform &&f) -> std::enable_if_t< + std::is_same(f)(*std::forward(t))), + void>::value> { + if (std::forward(t)) { + std::forward(f)(*std::forward(t)); } } diff --git a/libs/common/visitor.hpp b/libs/common/visitor.hpp index 4fd84d36f9..366d87f9ff 100644 --- a/libs/common/visitor.hpp +++ b/libs/common/visitor.hpp @@ -87,8 +87,11 @@ namespace iroha { * @param lambdas */ template - constexpr decltype(auto) visit_in_place(TVariant &&variant, TVisitors &&... visitors) { - return boost::apply_visitor(make_visitor(visitors...), std::forward(variant)); + constexpr decltype(auto) visit_in_place(TVariant &&variant, + TVisitors &&... visitors) { + return boost::apply_visitor( + make_visitor(std::forward(visitors)...), + std::forward(variant)); } } // namespace iroha diff --git a/schema/ordering.proto b/schema/ordering.proto index c8a3aac100..8cee4f688a 100644 --- a/schema/ordering.proto +++ b/schema/ordering.proto @@ -14,3 +14,28 @@ service OrderingServiceTransportGrpc { rpc onTransaction (iroha.protocol.Transaction) returns (google.protobuf.Empty); rpc onBatch (iroha.protocol.TxList) returns (google.protobuf.Empty); } + +message ProposalRound { + uint64 block_round = 1; + uint32 reject_round = 2; +} + +message TransactionsRequest { + ProposalRound round = 1; + repeated protocol.Transaction transactions = 2; +} + +message ProposalRequest { + ProposalRound round = 1; +} + +message ProposalResponse { + oneof optional_proposal { + protocol.Proposal proposal = 1; + } +} + +service OnDemandOrdering { + rpc SendTransactions(TransactionsRequest) returns (google.protobuf.Empty); + rpc RequestProposal(ProposalRequest) returns (ProposalResponse); +} diff --git a/schema/yac.proto b/schema/yac.proto index 7b96eaf6a2..6d5335d6bb 100644 --- a/schema/yac.proto +++ b/schema/yac.proto @@ -19,16 +19,10 @@ message Vote { Signature signature = 2; } -message Commit { - repeated Vote votes = 1; -} - -message Reject { +message State { repeated Vote votes = 1; } service Yac { - rpc SendVote (Vote) returns (google.protobuf.Empty); - rpc SendCommit (Commit) returns (google.protobuf.Empty); - rpc SendReject (Reject) returns (google.protobuf.Empty); + rpc SendState (State) returns (google.protobuf.Empty); } diff --git a/shared_model/backend/protobuf/impl/proposal.cpp b/shared_model/backend/protobuf/impl/proposal.cpp index 53ddd5c71b..6fa542a1a5 100644 --- a/shared_model/backend/protobuf/impl/proposal.cpp +++ b/shared_model/backend/protobuf/impl/proposal.cpp @@ -14,7 +14,10 @@ namespace shared_model { Proposal &Proposal::operator=(Proposal &&o) noexcept { proto_ = std::move(o.proto_); + + hash_.invalidate(); transactions_.invalidate(); + blob_.invalidate(); return *this; } @@ -31,5 +34,9 @@ namespace shared_model { return proto_.height(); } + const interface::types::BlobType &Proposal::blob() const { + return *blob_; + } + } // namespace proto } // namespace shared_model diff --git a/shared_model/backend/protobuf/proposal.hpp b/shared_model/backend/protobuf/proposal.hpp index e16d0e4719..830803c18e 100644 --- a/shared_model/backend/protobuf/proposal.hpp +++ b/shared_model/backend/protobuf/proposal.hpp @@ -33,6 +33,8 @@ namespace shared_model { interface::types::HeightType height() const override; + const interface::types::BlobType &blob() const override; + private: template using Lazy = detail::LazyInitializer; @@ -42,6 +44,9 @@ namespace shared_model { proto_.mutable_transactions()->begin(), proto_.mutable_transactions()->end()); }}; + + Lazy blob_{ + [this] { return makeBlob(proto_); }}; }; } // namespace proto } // namespace shared_model diff --git a/shared_model/interfaces/CMakeLists.txt b/shared_model/interfaces/CMakeLists.txt index 73d4cb6215..41ac747dea 100644 --- a/shared_model/interfaces/CMakeLists.txt +++ b/shared_model/interfaces/CMakeLists.txt @@ -57,6 +57,7 @@ if (IROHA_ROOT_PROJECT) iroha_internal/block_variant.cpp iroha_internal/transaction_sequence.cpp iroha_internal/transaction_batch.cpp + iroha_internal/transaction_batch_factory.cpp ) endif () diff --git a/shared_model/interfaces/iroha_internal/proposal.hpp b/shared_model/interfaces/iroha_internal/proposal.hpp index 405156eec2..7291a0b61c 100644 --- a/shared_model/interfaces/iroha_internal/proposal.hpp +++ b/shared_model/interfaces/iroha_internal/proposal.hpp @@ -18,9 +18,11 @@ #ifndef IROHA_SHARED_MODEL_PROPOSAL_HPP #define IROHA_SHARED_MODEL_PROPOSAL_HPP +#include "cryptography/default_hash_provider.hpp" #include "interfaces/base/model_primitive.hpp" #include "interfaces/common_objects/types.hpp" #include "interfaces/transaction.hpp" +#include "utils/lazy_initializer.hpp" namespace shared_model { namespace interface { @@ -47,16 +49,28 @@ namespace shared_model { and createdTime() == rhs.createdTime(); } + virtual const types::BlobType &blob() const = 0; + + const types::HashType &hash() const { + return *hash_; + } + std::string toString() const override { return detail::PrettyStringBuilder() .init("Proposal") .append("height", std::to_string(height())) .append("transactions") - .appendAll( - transactions(), - [](auto &transaction) { return transaction.toString(); }) + .appendAll(transactions(), + [](auto &transaction) { return transaction.toString(); }) .finalize(); } + + protected: + template + using Lazy = detail::LazyInitializer; + + const Lazy hash_{ + [this] { return crypto::DefaultHashProvider::makeHash(blob()); }}; }; } // namespace interface diff --git a/shared_model/interfaces/iroha_internal/transaction_batch.cpp b/shared_model/interfaces/iroha_internal/transaction_batch.cpp index 56e5683423..27df7cb777 100644 --- a/shared_model/interfaces/iroha_internal/transaction_batch.cpp +++ b/shared_model/interfaces/iroha_internal/transaction_batch.cpp @@ -7,42 +7,12 @@ #include -#include "interfaces/iroha_internal/transaction_batch_template_definitions.hpp" +#include #include "utils/string_builder.hpp" -#include "validators/default_validator.hpp" -#include "validators/field_validator.hpp" -#include "validators/transaction_validator.hpp" -#include "validators/transactions_collection/batch_order_validator.hpp" namespace shared_model { namespace interface { - template iroha::expected::Result - TransactionBatch::createTransactionBatch( - const types::SharedTxsCollectionType &transactions, - const validation::DefaultUnsignedTransactionsValidator &validator, - const validation::FieldValidator &field_validator); - - template iroha::expected::Result - TransactionBatch::createTransactionBatch( - const types::SharedTxsCollectionType &transactions, - const validation::DefaultSignedTransactionsValidator &validator, - const validation::FieldValidator &field_validator); - - template iroha::expected::Result - TransactionBatch::createTransactionBatch( - std::shared_ptr transaction, - const validation::DefaultUnsignedTransactionValidator - &transaction_validator, - const validation::FieldValidator &field_validator); - - template iroha::expected::Result - TransactionBatch::createTransactionBatch( - std::shared_ptr transaction, - const validation::DefaultSignedTransactionValidator - &transaction_validator, - const validation::FieldValidator &field_validator); - const types::SharedTxsCollectionType &TransactionBatch::transactions() const { return transactions_; diff --git a/shared_model/interfaces/iroha_internal/transaction_batch.hpp b/shared_model/interfaces/iroha_internal/transaction_batch.hpp index 04c5041e7e..86bf19f82e 100644 --- a/shared_model/interfaces/iroha_internal/transaction_batch.hpp +++ b/shared_model/interfaces/iroha_internal/transaction_batch.hpp @@ -6,10 +6,7 @@ #ifndef IROHA_TRANSACTION_BATCH_HPP #define IROHA_TRANSACTION_BATCH_HPP -#include "common/result.hpp" #include "interfaces/common_objects/transaction_sequence_common.hpp" -#include "validators/field_validator.hpp" -#include "validators/transactions_collection/transactions_collection_validator.hpp" namespace shared_model { namespace interface { @@ -20,42 +17,6 @@ namespace shared_model { TransactionBatch(const TransactionBatch &) = default; TransactionBatch(TransactionBatch &&) = default; - /** - * Create transaction batch out of collection of transactions - * @tparam TransactionValidator validates every single transaction - * @tparam OrderValidator validates order of transactions - * @param transactions collection of transactions, should be from the same - * batch - * @param validator transactions collection validator with provided - * transaction validator and order validator - * @return valid batch of transactions - */ - template - static iroha::expected::Result - createTransactionBatch(const types::SharedTxsCollectionType &transactions, - const validation::TransactionsCollectionValidator< - TransactionValidator> &validator, - const FieldValidator & = FieldValidator()); - - /** - * Creates transaction batch from single transaction - * @tparam TransactionValidator validates every single transaction - * @param transaction is transaction being validated and used to create - * batch - * @param transaction_validator transaction validation logic - * @return batch with single transaction - * @note transactions in such batches may not have batch meta information - */ - template - static iroha::expected::Result - createTransactionBatch( - std::shared_ptr transaction, - const TransactionValidator &transaction_validator = - TransactionValidator(), - const FieldValidator &field_validator = FieldValidator()); - explicit TransactionBatch( const types::SharedTxsCollectionType &transactions) : transactions_(transactions) {} diff --git a/shared_model/interfaces/iroha_internal/transaction_batch_factory.cpp b/shared_model/interfaces/iroha_internal/transaction_batch_factory.cpp new file mode 100644 index 0000000000..f2c9487523 --- /dev/null +++ b/shared_model/interfaces/iroha_internal/transaction_batch_factory.cpp @@ -0,0 +1,41 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "interfaces/iroha_internal/transaction_batch_factory.hpp" + +#include "interfaces/iroha_internal/transaction_batch_template_definitions.hpp" +#include "validators/default_validator.hpp" + +namespace shared_model { + namespace interface { + + template iroha::expected::Result + TransactionBatchFactory::createTransactionBatch( + const types::SharedTxsCollectionType &transactions, + const validation::DefaultUnsignedTransactionsValidator &validator, + const validation::FieldValidator &field_validator); + + template iroha::expected::Result + TransactionBatchFactory::createTransactionBatch( + const types::SharedTxsCollectionType &transactions, + const validation::DefaultSignedTransactionsValidator &validator, + const validation::FieldValidator &field_validator); + + template iroha::expected::Result + TransactionBatchFactory::createTransactionBatch( + std::shared_ptr transaction, + const validation::DefaultUnsignedTransactionValidator + &transaction_validator, + const validation::FieldValidator &field_validator); + + template iroha::expected::Result + TransactionBatchFactory::createTransactionBatch( + std::shared_ptr transaction, + const validation::DefaultSignedTransactionValidator + &transaction_validator, + const validation::FieldValidator &field_validator); + + } // namespace interface +} // namespace shared_model diff --git a/shared_model/interfaces/iroha_internal/transaction_batch_factory.hpp b/shared_model/interfaces/iroha_internal/transaction_batch_factory.hpp new file mode 100644 index 0000000000..1bcb64854b --- /dev/null +++ b/shared_model/interfaces/iroha_internal/transaction_batch_factory.hpp @@ -0,0 +1,63 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef IROHA_TRANSACTION_BATCH_FACTORY_HPP +#define IROHA_TRANSACTION_BATCH_FACTORY_HPP + +#include "common/result.hpp" +#include "interfaces/common_objects/transaction_sequence_common.hpp" +#include "interfaces/iroha_internal/transaction_batch.hpp" +#include "validators/field_validator.hpp" +#include "validators/transactions_collection/transactions_collection_validator.hpp" + +namespace shared_model { + namespace interface { + + /** + * Provides methods that create transaction batch from a single transaction, + * or a collection of transactions. Field validator is used by default + */ + class TransactionBatchFactory { + public: + /** + * Create transaction batch out of collection of transactions + * @tparam TransactionValidator validates every single transaction + * @tparam OrderValidator validates order of transactions + * @param transactions collection of transactions, should be from the same + * batch + * @param validator transactions collection validator with provided + * transaction validator and order validator + * @return valid batch of transactions + */ + template + static iroha::expected::Result + createTransactionBatch(const types::SharedTxsCollectionType &transactions, + const validation::TransactionsCollectionValidator< + TransactionValidator> &validator, + const FieldValidator & = FieldValidator()); + + /** + * Creates transaction batch from single transaction + * @tparam TransactionValidator validates every single transaction + * @param transaction is transaction being validated and used to create + * batch + * @param transaction_validator transaction validation logic + * @return batch with single transaction + * @note transactions in such batches may not have batch meta information + */ + template + static iroha::expected::Result + createTransactionBatch( + std::shared_ptr transaction, + const TransactionValidator &transaction_validator = + TransactionValidator(), + const FieldValidator &field_validator = FieldValidator()); + }; + } // namespace interface +} // namespace shared_model + +#endif // IROHA_TRANSACTION_BATCH_FACTORY_HPP diff --git a/shared_model/interfaces/iroha_internal/transaction_batch_template_definitions.hpp b/shared_model/interfaces/iroha_internal/transaction_batch_template_definitions.hpp index 9d3b123b8b..ccfa8c66a7 100644 --- a/shared_model/interfaces/iroha_internal/transaction_batch_template_definitions.hpp +++ b/shared_model/interfaces/iroha_internal/transaction_batch_template_definitions.hpp @@ -6,7 +6,7 @@ #ifndef IROHA_TRANSACTION_BATCH_TEMPLATE_DEFINITIONS_HPP #define IROHA_TRANSACTION_BATCH_TEMPLATE_DEFINITIONS_HPP -#include "interfaces/iroha_internal/transaction_batch.hpp" +#include "interfaces/iroha_internal/transaction_batch_factory.hpp" namespace shared_model { namespace interface { @@ -40,7 +40,7 @@ namespace shared_model { template iroha::expected::Result - TransactionBatch::createTransactionBatch( + TransactionBatchFactory::createTransactionBatch( const types::SharedTxsCollectionType &transactions, const validation::TransactionsCollectionValidator &validator, @@ -82,7 +82,7 @@ namespace shared_model { template iroha::expected::Result - TransactionBatch::createTransactionBatch( + TransactionBatchFactory::createTransactionBatch( std::shared_ptr transaction, const TransactionValidator &transaction_validator, const FieldValidator &field_validator) { diff --git a/shared_model/interfaces/iroha_internal/transaction_sequence.cpp b/shared_model/interfaces/iroha_internal/transaction_sequence.cpp index 374d0c9971..6028c8e9e4 100644 --- a/shared_model/interfaces/iroha_internal/transaction_sequence.cpp +++ b/shared_model/interfaces/iroha_internal/transaction_sequence.cpp @@ -6,6 +6,7 @@ #include "interfaces/iroha_internal/transaction_sequence.hpp" #include "interfaces/iroha_internal/transaction_batch.hpp" +#include "interfaces/iroha_internal/transaction_batch_factory.hpp" #include "validators/default_validator.hpp" namespace shared_model { @@ -43,7 +44,7 @@ namespace shared_model { auto batch_hash = TransactionBatch::calculateReducedBatchHash(hashes); extracted_batches[batch_hash].push_back(tx); } else { - TransactionBatch::createTransactionBatch( tx, transaction_validator, field_validator) .match(insert_batch, [&tx, &result](const auto &err) { @@ -56,7 +57,7 @@ namespace shared_model { } for (const auto &it : extracted_batches) { - TransactionBatch::createTransactionBatch(it.second, validator) + TransactionBatchFactory::createTransactionBatch(it.second, validator) .match(insert_batch, [&it, &result](const auto &err) { result.addReason(std::make_pair( it.first.toString(), std::vector{err.error})); diff --git a/shared_model/interfaces/iroha_internal/transaction_sequence.hpp b/shared_model/interfaces/iroha_internal/transaction_sequence.hpp index c0442b0c64..299ce70a49 100644 --- a/shared_model/interfaces/iroha_internal/transaction_sequence.hpp +++ b/shared_model/interfaces/iroha_internal/transaction_sequence.hpp @@ -9,6 +9,7 @@ #include "common/result.hpp" #include "interfaces/common_objects/transaction_sequence_common.hpp" #include "interfaces/iroha_internal/transaction_batch.hpp" +#include "validators/field_validator.hpp" #include "validators/transactions_collection/transactions_collection_validator.hpp" namespace shared_model { diff --git a/test/framework/batch_helper.hpp b/test/framework/batch_helper.hpp index 0d52643e09..ac1268c206 100644 --- a/test/framework/batch_helper.hpp +++ b/test/framework/batch_helper.hpp @@ -11,6 +11,7 @@ #include "framework/result_fixture.hpp" #include "interfaces/iroha_internal/transaction_batch_template_definitions.hpp" #include "interfaces/iroha_internal/transaction_batch.hpp" +#include "interfaces/iroha_internal/transaction_batch_factory.hpp" #include "module/shared_model/builders/protobuf/test_transaction_builder.hpp" #include "module/shared_model/validators/validators.hpp" #include "validators/transactions_collection/batch_order_validator.hpp" @@ -193,9 +194,8 @@ namespace framework { auto txs = createBatchOneSignTransactions(transaction_fields, created_time); - auto result_batch = - shared_model::interface::TransactionBatch::createTransactionBatch( - txs, TxsValidator()); + auto result_batch = shared_model::interface::TransactionBatchFactory:: + createTransactionBatch(txs, TxsValidator()); return framework::expected::val(result_batch).value().value; } @@ -207,19 +207,21 @@ namespace framework { */ inline auto createBatchFromSingleTransaction( std::shared_ptr tx) { - return shared_model::interface::TransactionBatch::createTransactionBatch( + return shared_model::interface::TransactionBatchFactory:: + createTransactionBatch( tx, shared_model::validation::DefaultSignedTransactionValidator()) - .match( - [](const iroha::expected::Value< - shared_model::interface::TransactionBatch> &value) { - return value.value; - }, - [](const auto &err) -> shared_model::interface::TransactionBatch { - throw std::runtime_error( - err.error - + "Error transformation from transaction to batch"); - }); + .match( + [](const iroha::expected::Value< + shared_model::interface::TransactionBatch> &value) { + return value.value; + }, + [](const auto &err) + -> shared_model::interface::TransactionBatch { + throw std::runtime_error( + err.error + + "Error transformation from transaction to batch"); + }); } /** diff --git a/test/framework/mock_stream.h b/test/framework/mock_stream.h new file mode 100644 index 0000000000..1eead52a8e --- /dev/null +++ b/test/framework/mock_stream.h @@ -0,0 +1,145 @@ +/* + * + * Copyright 2017 gRPC authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#ifndef GRPCPP_TEST_MOCK_STREAM_H +#define GRPCPP_TEST_MOCK_STREAM_H + +#include +#include +#include +#include +#include + +namespace grpc { + namespace testing { + + template + class MockClientReader : public ClientReaderInterface { + public: + MockClientReader() = default; + + /// ClientStreamingInterface + MOCK_METHOD0_T(Finish, Status()); + + /// ReaderInterface + MOCK_METHOD1_T(NextMessageSize, bool(uint32_t *)); + MOCK_METHOD1_T(Read, bool(R *)); + + /// ClientReaderInterface + MOCK_METHOD0_T(WaitForInitialMetadata, void()); + }; + + template + class MockClientWriter : public ClientWriterInterface { + public: + MockClientWriter() = default; + + /// ClientStreamingInterface + MOCK_METHOD0_T(Finish, Status()); + + /// WriterInterface + MOCK_METHOD2_T(Write, bool(const W &, const WriteOptions)); + + /// ClientWriterInterface + MOCK_METHOD0_T(WritesDone, bool()); + }; + + template + class MockClientReaderWriter : public ClientReaderWriterInterface { + public: + MockClientReaderWriter() = default; + + /// ClientStreamingInterface + MOCK_METHOD0_T(Finish, Status()); + + /// ReaderInterface + MOCK_METHOD1_T(NextMessageSize, bool(uint32_t *)); + MOCK_METHOD1_T(Read, bool(R *)); + + /// WriterInterface + MOCK_METHOD2_T(Write, bool(const W &, const WriteOptions)); + + /// ClientReaderWriterInterface + MOCK_METHOD0_T(WaitForInitialMetadata, void()); + MOCK_METHOD0_T(WritesDone, bool()); + }; + + template + class MockClientAsyncResponseReader + : public ClientAsyncResponseReaderInterface { + public: + MockClientAsyncResponseReader() = default; + + MOCK_METHOD0_T(StartCall, void()); + MOCK_METHOD1_T(ReadInitialMetadata, void(void *)); + MOCK_METHOD3_T(Finish, void(R *, Status *, void *)); + }; + + template + class MockClientAsyncReader : public ClientAsyncReaderInterface { + public: + MockClientAsyncReader() = default; + + /// ClientAsyncStreamingInterface + MOCK_METHOD1_T(ReadInitialMetadata, void(void *)); + MOCK_METHOD2_T(Finish, void(Status *, void *)); + + /// AsyncReaderInterface + MOCK_METHOD2_T(Read, void(R *, void *)); + }; + + template + class MockClientAsyncWriter : public ClientAsyncWriterInterface { + public: + MockClientAsyncWriter() = default; + + /// ClientAsyncStreamingInterface + MOCK_METHOD1_T(ReadInitialMetadata, void(void *)); + MOCK_METHOD2_T(Finish, void(Status *, void *)); + + /// AsyncWriterInterface + MOCK_METHOD2_T(Write, void(const W &, void *)); + + /// ClientAsyncWriterInterface + MOCK_METHOD1_T(WritesDone, void(void *)); + }; + + template + class MockClientAsyncReaderWriter + : public ClientAsyncReaderWriterInterface { + public: + MockClientAsyncReaderWriter() = default; + + /// ClientAsyncStreamingInterface + MOCK_METHOD1_T(ReadInitialMetadata, void(void *)); + MOCK_METHOD2_T(Finish, void(Status *, void *)); + + /// AsyncWriterInterface + MOCK_METHOD2_T(Write, void(const W &, void *)); + + /// AsyncReaderInterface + MOCK_METHOD2_T(Read, void(R *, void *)); + + /// ClientAsyncReaderWriterInterface + MOCK_METHOD1_T(WritesDone, void(void *)); + }; + + } // namespace testing +} // namespace grpc + +#endif // GRPCPP_TEST_MOCK_STREAM_H diff --git a/test/integration/consensus/consensus_sunny_day.cpp b/test/integration/consensus/consensus_sunny_day.cpp index 488035288d..27f68dfdc8 100644 --- a/test/integration/consensus/consensus_sunny_day.cpp +++ b/test/integration/consensus/consensus_sunny_day.cpp @@ -25,6 +25,7 @@ #include "module/irohad/consensus/yac/yac_mocks.hpp" #include "module/shared_model/builders/protobuf/test_signature_builder.hpp" +using ::testing::_; using ::testing::An; using ::testing::InvokeWithoutArgs; using ::testing::Return; @@ -127,21 +128,20 @@ std::shared_ptr ConsensusSunnyDayTest::my_peer; std::vector> ConsensusSunnyDayTest::default_peers; +/** + * @given num_peers peers with initialized YAC + * @when peers vote for same hash + * @then commit is achieved + */ TEST_F(ConsensusSunnyDayTest, SunnyDayTest) { std::condition_variable cv; - auto wrapper = make_test_subscriber(yac->on_commit(), 1); - wrapper.subscribe( - [](auto hash) { std::cout << "^_^ COMMITTED!!!" << std::endl; }); - - EXPECT_CALL(*crypto, verify(An())) - .Times(1) - .WillRepeatedly(DoAll(InvokeWithoutArgs([&cv] { - // wake up after commit is received from the - // network so that it is safe to shutdown - cv.notify_one(); - }), - Return(true))); - EXPECT_CALL(*crypto, verify(An())).WillRepeatedly(Return(true)); + auto wrapper = make_test_subscriber(yac->onOutcome(), 1); + wrapper.subscribe([&cv](auto hash) { + std::cout << "^_^ COMMITTED!!!" << std::endl; + cv.notify_one(); + }); + + EXPECT_CALL(*crypto, verify(_)).WillRepeatedly(Return(true)); // Wait for other peers to start std::this_thread::sleep_for(std::chrono::milliseconds(delay_before)); diff --git a/test/integration/transport/ordering_gate_service_test.cpp b/test/integration/transport/ordering_gate_service_test.cpp index 5a4f348121..ad9133e824 100644 --- a/test/integration/transport/ordering_gate_service_test.cpp +++ b/test/integration/transport/ordering_gate_service_test.cpp @@ -28,8 +28,8 @@ #include "module/shared_model/builders/protobuf/test_transaction_builder.hpp" #include "ordering/impl/ordering_gate_impl.hpp" #include "ordering/impl/ordering_gate_transport_grpc.hpp" -#include "ordering/impl/ordering_service_impl.hpp" #include "ordering/impl/ordering_service_transport_grpc.hpp" +#include "ordering/impl/single_peer_ordering_service.hpp" using namespace iroha; using namespace iroha::ordering; @@ -75,13 +75,13 @@ class OrderingGateServiceTest : public ::testing::Test { } void initOs(size_t max_proposal) { - service = - std::make_shared(pqfactory, - max_proposal, - proposal_timeout.get_observable(), - service_transport, - persistent_state_factory, - std::move(factory_)); + service = std::make_shared( + pqfactory, + max_proposal, + proposal_timeout.get_observable(), + service_transport, + persistent_state_factory, + std::move(factory_)); service_transport->subscribe(service); } @@ -195,7 +195,7 @@ class OrderingGateServiceTest : public ::testing::Test { private: std::shared_ptr gate; - std::shared_ptr service; + std::shared_ptr service; std::shared_ptr> async_call_; @@ -203,7 +203,8 @@ class OrderingGateServiceTest : public ::testing::Test { /// commits for Ordering Service std::shared_ptr pcs_; rxcpp::subjects::subject commit_subject_; - rxcpp::subjects::subject proposal_timeout; + rxcpp::subjects::subject + proposal_timeout; std::condition_variable cv; std::mutex m; diff --git a/test/module/irohad/consensus/yac/network_test.cpp b/test/module/irohad/consensus/yac/network_test.cpp index 74ed3ed6e6..45aecdb08a 100644 --- a/test/module/irohad/consensus/yac/network_test.cpp +++ b/test/module/irohad/consensus/yac/network_test.cpp @@ -82,12 +82,12 @@ namespace iroha { * @then vote handled */ TEST_F(YacNetworkTest, MessageHandledWhenMessageSent) { - EXPECT_CALL(*notifications, on_vote(message)) + EXPECT_CALL(*notifications, onState(std::vector{message})) .Times(1) .WillRepeatedly( InvokeWithoutArgs(&cv, &std::condition_variable::notify_one)); - network->send_vote(*peer, message); + network->sendState(*peer, {message}); // wait for response reader thread std::unique_lock lock(mtx); diff --git a/test/module/irohad/consensus/yac/yac_block_storage_test.cpp b/test/module/irohad/consensus/yac/yac_block_storage_test.cpp index 9855eff7e7..8c01722027 100644 --- a/test/module/irohad/consensus/yac/yac_block_storage_test.cpp +++ b/test/module/irohad/consensus/yac/yac_block_storage_test.cpp @@ -18,8 +18,8 @@ #include #include -#include "consensus/yac/storage/yac_proposal_storage.hpp" #include "consensus/yac/storage/yac_block_storage.hpp" +#include "consensus/yac/storage/yac_proposal_storage.hpp" #include "consensus/yac/storage/yac_vote_storage.hpp" #include "logger/logger.hpp" #include "module/irohad/consensus/yac/yac_mocks.hpp" @@ -31,7 +31,7 @@ static logger::Logger log_ = logger::testLog("YacBlockStorage"); class YacBlockStorageTest : public ::testing::Test { public: YacHash hash; - uint64_t number_of_peers; + PeersNumberType number_of_peers; YacBlockStorage storage = YacBlockStorage(YacHash("proposal", "commit"), 4); std::vector valid_votes; diff --git a/test/module/irohad/consensus/yac/yac_crypto_provider_test.cpp b/test/module/irohad/consensus/yac/yac_crypto_provider_test.cpp index 34ac5e9306..f63f3dc542 100644 --- a/test/module/irohad/consensus/yac/yac_crypto_provider_test.cpp +++ b/test/module/irohad/consensus/yac/yac_crypto_provider_test.cpp @@ -50,7 +50,7 @@ namespace iroha { auto vote = crypto_provider->getVote(hash); - ASSERT_TRUE(crypto_provider->verify(vote)); + ASSERT_TRUE(crypto_provider->verify({vote})); } TEST_F(YacCryptoProviderTest, InvalidWhenMessageChanged) { @@ -66,7 +66,7 @@ namespace iroha { vote.hash.block_hash = "hash changed"; - ASSERT_FALSE(crypto_provider->verify(vote)); + ASSERT_FALSE(crypto_provider->verify({vote})); } } // namespace yac diff --git a/test/module/irohad/consensus/yac/yac_gate_test.cpp b/test/module/irohad/consensus/yac/yac_gate_test.cpp index dbfe012d84..57fc39ebb6 100644 --- a/test/module/irohad/consensus/yac/yac_gate_test.cpp +++ b/test/module/irohad/consensus/yac/yac_gate_test.cpp @@ -64,7 +64,7 @@ class YacGateTest : public ::testing::Test { message.hash = expected_hash; message.signature = clone(signature); commit_message = CommitMessage({message}); - expected_commit = rxcpp::observable<>::just(commit_message); + expected_commit = rxcpp::observable<>::just(Answer(commit_message)); hash_gate = make_unique(); peer_orderer = make_unique(); @@ -87,7 +87,7 @@ class YacGateTest : public ::testing::Test { std::shared_ptr expected_block; VoteMessage message; CommitMessage commit_message; - rxcpp::observable expected_commit; + rxcpp::observable expected_commit; unique_ptr hash_gate; unique_ptr peer_orderer; @@ -115,7 +115,7 @@ TEST_F(YacGateTest, YacGateSubscriptionTest) { // yac consensus EXPECT_CALL(*hash_gate, vote(expected_hash, _)).Times(1); - EXPECT_CALL(*hash_gate, on_commit()).WillOnce(Return(expected_commit)); + EXPECT_CALL(*hash_gate, onOutcome()).WillOnce(Return(expected_commit)); // generate order of peers EXPECT_CALL(*peer_orderer, getOrdering(_)) @@ -169,7 +169,7 @@ TEST_F(YacGateTest, YacGateSubscribtionTestFailCase) { // yac consensus EXPECT_CALL(*hash_gate, vote(_, _)).Times(0); - EXPECT_CALL(*hash_gate, on_commit()).Times(0); + EXPECT_CALL(*hash_gate, onOutcome()).Times(0); // generate order of peers EXPECT_CALL(*peer_orderer, getOrdering(_)).WillOnce(Return(boost::none)); @@ -233,10 +233,10 @@ TEST_F(YacGateTest, LoadBlockWhenDifferentCommit) { message.hash = YacHash("actual_proposal", "actual_block"); message.signature = clone(signature); commit_message = CommitMessage({message}); - expected_commit = rxcpp::observable<>::just(commit_message); + expected_commit = rxcpp::observable<>::just(Answer(commit_message)); // yac consensus - EXPECT_CALL(*hash_gate, on_commit()).WillOnce(Return(expected_commit)); + EXPECT_CALL(*hash_gate, onOutcome()).WillOnce(Return(expected_commit)); // convert yac hash to model hash EXPECT_CALL(*hash_provider, toModelHash(message.hash)) @@ -313,10 +313,10 @@ TEST_F(YacGateTest, LoadBlockWhenDifferentCommitFailFirst) { message.hash = expected_hash; commit_message = CommitMessage({message}); - expected_commit = rxcpp::observable<>::just(commit_message); + expected_commit = rxcpp::observable<>::just(Answer(commit_message)); // yac consensus - EXPECT_CALL(*hash_gate, on_commit()).WillOnce(Return(expected_commit)); + EXPECT_CALL(*hash_gate, onOutcome()).WillOnce(Return(expected_commit)); // convert yac hash to model hash EXPECT_CALL(*hash_provider, toModelHash(expected_hash)) diff --git a/test/module/irohad/consensus/yac/yac_mocks.hpp b/test/module/irohad/consensus/yac/yac_mocks.hpp index 1e03205e2d..97b6557a4d 100644 --- a/test/module/irohad/consensus/yac/yac_mocks.hpp +++ b/test/module/irohad/consensus/yac/yac_mocks.hpp @@ -78,9 +78,7 @@ namespace iroha { class MockYacCryptoProvider : public YacCryptoProvider { public: - MOCK_METHOD1(verify, bool(CommitMessage)); - MOCK_METHOD1(verify, bool(RejectMessage)); - MOCK_METHOD1(verify, bool(VoteMessage)); + MOCK_METHOD1(verify, bool(const std::vector &)); VoteMessage getVote(YacHash hash) override { VoteMessage vote; @@ -126,14 +124,9 @@ namespace iroha { notification.reset(); } - MOCK_METHOD2(send_commit, + MOCK_METHOD2(sendState, void(const shared_model::interface::Peer &, - const CommitMessage &)); - MOCK_METHOD2(send_reject, - void(const shared_model::interface::Peer &, - RejectMessage)); - MOCK_METHOD2(send_vote, - void(const shared_model::interface::Peer &, VoteMessage)); + const std::vector &)); MockYacNetwork() = default; @@ -161,7 +154,7 @@ namespace iroha { public: MOCK_METHOD2(vote, void(YacHash, ClusterOrdering)); - MOCK_METHOD0(on_commit, rxcpp::observable()); + MOCK_METHOD0(onOutcome, rxcpp::observable()); MockHashGate() = default; @@ -214,9 +207,7 @@ namespace iroha { class MockYacNetworkNotifications : public YacNetworkNotifications { public: - MOCK_METHOD1(on_commit, void(CommitMessage)); - MOCK_METHOD1(on_reject, void(RejectMessage)); - MOCK_METHOD1(on_vote, void(VoteMessage)); + MOCK_METHOD1(onState, void(std::vector)); }; class MockSupermajorityChecker : public SupermajorityChecker { @@ -227,7 +218,7 @@ namespace iroha { &signatures, const std::vector< std::shared_ptr> &peers)); - MOCK_CONST_METHOD2(checkSize, bool(uint64_t current, uint64_t all)); + MOCK_CONST_METHOD2(checkSize, bool(PeersNumberType, PeersNumberType)); MOCK_CONST_METHOD2( peersSubset, bool(const shared_model::interface::types::SignatureRangeType @@ -235,7 +226,7 @@ namespace iroha { const std::vector< std::shared_ptr> &peers)); MOCK_CONST_METHOD3( - hasReject, bool(uint64_t frequent, uint64_t voted, uint64_t all)); + hasReject, bool(PeersNumberType, PeersNumberType, PeersNumberType)); }; class YacTest : public ::testing::Test { diff --git a/test/module/irohad/consensus/yac/yac_proposal_storage_test.cpp b/test/module/irohad/consensus/yac/yac_proposal_storage_test.cpp index 7845e71700..0576c1e9f2 100644 --- a/test/module/irohad/consensus/yac/yac_proposal_storage_test.cpp +++ b/test/module/irohad/consensus/yac/yac_proposal_storage_test.cpp @@ -29,7 +29,7 @@ static logger::Logger log_ = logger::testLog("YacProposalStorage"); class YacProposalStorageTest : public ::testing::Test { public: YacHash hash; - uint64_t number_of_peers; + PeersNumberType number_of_peers; YacProposalStorage storage = YacProposalStorage("proposal", 4); std::vector valid_votes; diff --git a/test/module/irohad/consensus/yac/yac_rainy_day_test.cpp b/test/module/irohad/consensus/yac/yac_rainy_day_test.cpp index 28d1a87929..cec43e464b 100644 --- a/test/module/irohad/consensus/yac/yac_rainy_day_test.cpp +++ b/test/module/irohad/consensus/yac/yac_rainy_day_test.cpp @@ -43,25 +43,21 @@ TEST_F(YacTest, InvalidCaseWhenNotReceiveSupermajority) { initYac(my_order.value()); - EXPECT_CALL(*network, send_commit(_, _)).Times(0); - EXPECT_CALL(*network, send_reject(_, _)).Times(my_peers.size()); - EXPECT_CALL(*network, send_vote(_, _)).Times(my_peers.size()); + EXPECT_CALL(*network, sendState(_, _)).Times(2 * my_peers.size()); EXPECT_CALL(*timer, deny()).Times(0); - EXPECT_CALL(*crypto, verify(An())).Times(0); - EXPECT_CALL(*crypto, verify(An())).Times(0); - EXPECT_CALL(*crypto, verify(An())).WillRepeatedly(Return(true)); + EXPECT_CALL(*crypto, verify(_)).WillRepeatedly(Return(true)); YacHash hash1("proposal_hash", "block_hash"); YacHash hash2("proposal_hash", "block_hash2"); yac->vote(hash1, my_order.value()); for (auto i = 0; i < 2; ++i) { - yac->on_vote(create_vote(hash1, std::to_string(i))); + yac->onState({create_vote(hash1, std::to_string(i))}); }; for (auto i = 2; i < 4; ++i) { - yac->on_vote(create_vote(hash2, std::to_string(i))); + yac->onState({create_vote(hash2, std::to_string(i))}); }; } @@ -81,23 +77,20 @@ TEST_F(YacTest, InvalidCaseWhenDoesNotVerify) { initYac(my_order.value()); - EXPECT_CALL(*network, send_reject(_, _)).Times(0); + EXPECT_CALL(*network, sendState(_, _)).Times(0); EXPECT_CALL(*timer, deny()).Times(0); - EXPECT_CALL(*crypto, verify(An())).Times(0); - EXPECT_CALL(*crypto, verify(An())) - .WillRepeatedly(Return(false)); - EXPECT_CALL(*crypto, verify(An())).WillRepeatedly(Return(false)); + EXPECT_CALL(*crypto, verify(_)).WillRepeatedly(Return(false)); YacHash hash1("proposal_hash", "block_hash"); YacHash hash2("proposal_hash", "block_hash2"); for (auto i = 0; i < 2; ++i) { - yac->on_vote(create_vote(hash1, std::to_string(i))); + yac->onState({create_vote(hash1, std::to_string(i))}); }; for (auto i = 2; i < 4; ++i) { - yac->on_vote(create_vote(hash2, std::to_string(i))); + yac->onState({create_vote(hash2, std::to_string(i))}); }; } @@ -120,18 +113,14 @@ TEST_F(YacTest, ValidCaseWhenReceiveOnVoteAfterReject) { initYac(my_order.value()); - EXPECT_CALL(*network, send_commit(_, _)).Times(0); - EXPECT_CALL(*network, send_reject(_, _)) + EXPECT_CALL(*network, sendState(_, _)) .Times(my_peers.size() + 1); // $(peers.size()) sendings done during // multicast + 1 for single peer, who votes // after reject happened - EXPECT_CALL(*network, send_vote(_, _)).Times(0); EXPECT_CALL(*timer, deny()).Times(1); - EXPECT_CALL(*crypto, verify(An())).Times(0); - EXPECT_CALL(*crypto, verify(An())).WillOnce(Return(true)); - EXPECT_CALL(*crypto, verify(An())).WillRepeatedly(Return(true)); + EXPECT_CALL(*crypto, verify(_)).WillRepeatedly(Return(true)); YacHash hash1("proposal_hash", "block_hash"); YacHash hash2("proposal_hash", "block_hash2"); @@ -149,11 +138,11 @@ TEST_F(YacTest, ValidCaseWhenReceiveOnVoteAfterReject) { }; for (const auto &vote : votes) { - yac->on_vote(vote); + yac->onState({vote}); } - yac->on_reject(RejectMessage(votes)); + yac->onState(votes); auto peer = my_order->getPeers().back(); auto pubkey = shared_model::crypto::toBinaryString(peer->pubkey()); - yac->on_vote(create_vote(hash1, pubkey)); + yac->onState({create_vote(hash1, pubkey)}); } diff --git a/test/module/irohad/consensus/yac/yac_simple_cold_case_test.cpp b/test/module/irohad/consensus/yac/yac_simple_cold_case_test.cpp index c889266f26..62a2b89727 100644 --- a/test/module/irohad/consensus/yac/yac_simple_cold_case_test.cpp +++ b/test/module/irohad/consensus/yac/yac_simple_cold_case_test.cpp @@ -43,9 +43,7 @@ using namespace std; TEST_F(YacTest, YacWhenVoting) { cout << "----------|YacWhenAchieveOneVote|----------" << endl; - EXPECT_CALL(*network, send_commit(_, _)).Times(0); - EXPECT_CALL(*network, send_reject(_, _)).Times(0); - EXPECT_CALL(*network, send_vote(_, _)).Times(default_peers.size()); + EXPECT_CALL(*network, sendState(_, _)).Times(default_peers.size()); YacHash my_hash("my_proposal_hash", "my_block_hash"); @@ -62,23 +60,17 @@ TEST_F(YacTest, YacWhenColdStartAndAchieveOneVote) { cout << "----------|Coldstart - receive one vote|----------" << endl; // verify that commit not emitted - auto wrapper = make_test_subscriber(yac->on_commit(), 0); + auto wrapper = make_test_subscriber(yac->onOutcome(), 0); wrapper.subscribe(); - EXPECT_CALL(*network, send_commit(_, _)).Times(0); - EXPECT_CALL(*network, send_reject(_, _)).Times(0); - EXPECT_CALL(*network, send_vote(_, _)).Times(0); + EXPECT_CALL(*network, sendState(_, _)).Times(0); - EXPECT_CALL(*crypto, verify(An())).Times(0); - EXPECT_CALL(*crypto, verify(An())).Times(0); - EXPECT_CALL(*crypto, verify(An())) - .Times(1) - .WillRepeatedly(Return(true)); + EXPECT_CALL(*crypto, verify(_)).Times(1).WillRepeatedly(Return(true)); YacHash received_hash("my_proposal", "my_block"); auto peer = default_peers.at(0); // assume that our peer receive message - network->notification->on_vote(crypto->getVote(received_hash)); + network->notification->onState({crypto->getVote(received_hash)}); ASSERT_TRUE(wrapper.validate()); } @@ -93,48 +85,42 @@ TEST_F(YacTest, YacWhenColdStartAndAchieveSupermajorityOfVotes) { << endl; // verify that commit not emitted - auto wrapper = make_test_subscriber(yac->on_commit(), 0); + auto wrapper = make_test_subscriber(yac->onOutcome(), 0); wrapper.subscribe(); - EXPECT_CALL(*network, send_commit(_, _)).Times(0); - EXPECT_CALL(*network, send_reject(_, _)).Times(0); - EXPECT_CALL(*network, send_vote(_, _)).Times(0); + EXPECT_CALL(*network, sendState(_, _)).Times(0); - EXPECT_CALL(*crypto, verify(An())).Times(0); - EXPECT_CALL(*crypto, verify(An())).Times(0); - EXPECT_CALL(*crypto, verify(An())) + EXPECT_CALL(*crypto, verify(_)) .Times(default_peers.size()) .WillRepeatedly(Return(true)); YacHash received_hash("my_proposal", "my_block"); for (size_t i = 0; i < default_peers.size(); ++i) { - network->notification->on_vote(crypto->getVote(received_hash)); + network->notification->onState({crypto->getVote(received_hash)}); } ASSERT_TRUE(wrapper.validate()); } /** - * Test provide scenario - * when yac cold started and achieve commit + * @given initialized YAC with empty storage + * @when receive commit message + * @then commit is not broadcasted + * AND commit is emitted to observable */ TEST_F(YacTest, YacWhenColdStartAndAchieveCommitMessage) { - cout << "----------|Start => receive commit|----------" << endl; YacHash propagated_hash("my_proposal", "my_block"); // verify that commit emitted - auto wrapper = make_test_subscriber(yac->on_commit(), 1); + auto wrapper = make_test_subscriber(yac->onOutcome(), 1); wrapper.subscribe([propagated_hash](auto commit_hash) { - ASSERT_EQ(propagated_hash, commit_hash.votes.at(0).hash); + ASSERT_EQ(propagated_hash, + boost::get(commit_hash).votes.at(0).hash); }); - EXPECT_CALL(*network, send_commit(_, _)).Times(0); - EXPECT_CALL(*network, send_reject(_, _)).Times(0); - EXPECT_CALL(*network, send_vote(_, _)).Times(0); + EXPECT_CALL(*network, sendState(_, _)).Times(0); - EXPECT_CALL(*crypto, verify(An())).WillOnce(Return(true)); - EXPECT_CALL(*crypto, verify(An())).Times(0); - EXPECT_CALL(*crypto, verify(An())).Times(0); + EXPECT_CALL(*crypto, verify(_)).WillOnce(Return(true)); EXPECT_CALL(*timer, deny()).Times(AtLeast(1)); @@ -143,7 +129,7 @@ TEST_F(YacTest, YacWhenColdStartAndAchieveCommitMessage) { for (size_t i = 0; i < default_peers.size(); ++i) { msg.votes.push_back(create_vote(propagated_hash, std::to_string(i))); } - network->notification->on_commit(msg); + network->notification->onState(msg.votes); ASSERT_TRUE(wrapper.validate()); } @@ -154,23 +140,23 @@ TEST_F(YacTest, YacWhenColdStartAndAchieveCommitMessage) { * @then commit is sent to the network before notifying subscribers */ TEST_F(YacTest, PropagateCommitBeforeNotifyingSubscribersApplyVote) { - EXPECT_CALL(*crypto, verify(An())) + EXPECT_CALL(*crypto, verify(_)) .Times(default_peers.size()) .WillRepeatedly(Return(true)); - std::vector messages; - EXPECT_CALL(*network, send_commit(_, _)) + std::vector> messages; + EXPECT_CALL(*network, sendState(_, _)) .Times(default_peers.size()) .WillRepeatedly(Invoke( [&](const auto &, const auto &msg) { messages.push_back(msg); })); - yac->on_commit().subscribe([&](auto msg) { + yac->onOutcome().subscribe([&](auto msg) { // verify that commits are already sent to the network ASSERT_EQ(default_peers.size(), messages.size()); - messages.push_back(msg); + messages.push_back(boost::get(msg).votes); }); for (size_t i = 0; i < default_peers.size(); ++i) { - yac->on_vote(create_vote(YacHash{}, std::to_string(i))); + yac->onState({create_vote(YacHash{}, std::to_string(i))}); } // verify that on_commit subscribers are notified @@ -179,31 +165,43 @@ TEST_F(YacTest, PropagateCommitBeforeNotifyingSubscribersApplyVote) { /** * @given initialized YAC - * @when receive reject message which triggers commit - * @then commit is sent to the network before notifying subscribers + * @when receive 2 * f votes for one hash + * AND receive reject message which triggers commit + * @then commit is NOT propagated in the network + * AND it is passed to pipeline */ TEST_F(YacTest, PropagateCommitBeforeNotifyingSubscribersApplyReject) { - EXPECT_CALL(*crypto, verify(An())).WillOnce(Return(true)); + EXPECT_CALL(*crypto, verify(_)).WillRepeatedly(Return(true)); EXPECT_CALL(*timer, deny()).Times(AtLeast(1)); - std::vector messages; - EXPECT_CALL(*network, send_commit(_, _)) - .Times(default_peers.size()) + std::vector> messages; + EXPECT_CALL(*network, sendState(_, _)) + .Times(0) .WillRepeatedly(Invoke( [&](const auto &, const auto &msg) { messages.push_back(msg); })); - yac->on_commit().subscribe([&](auto msg) { + yac->onOutcome().subscribe([&](auto msg) { // verify that commits are already sent to the network - ASSERT_EQ(default_peers.size(), messages.size()); - messages.push_back(msg); + ASSERT_EQ(0, messages.size()); + messages.push_back(boost::get(msg).votes); }); - RejectMessage reject({}); - for (size_t i = 0; i < default_peers.size(); ++i) { - reject.votes.push_back(create_vote(YacHash{}, std::to_string(i))); + std::vector commit; + + auto f = (default_peers.size() - 1) / 3; + for (size_t i = 0; i < 2 * f; ++i) { + auto vote = create_vote(YacHash{}, std::to_string(i)); + yac->onState({vote}); + commit.push_back(vote); } - yac->on_reject(reject); + auto vote = create_vote(YacHash{}, std::to_string(2 * f + 1)); + RejectMessage reject( + {vote, create_vote(YacHash("", "my_block"), std::to_string(2 * f + 2))}); + commit.push_back(vote); + + yac->onState(reject.votes); + yac->onState(commit); // verify that on_commit subscribers are notified - ASSERT_EQ(default_peers.size() + 1, messages.size()); + ASSERT_EQ(1, messages.size()); } diff --git a/test/module/irohad/consensus/yac/yac_sunny_day_test.cpp b/test/module/irohad/consensus/yac/yac_sunny_day_test.cpp index c79e93e1fb..79124b5e24 100644 --- a/test/module/irohad/consensus/yac/yac_sunny_day_test.cpp +++ b/test/module/irohad/consensus/yac/yac_sunny_day_test.cpp @@ -43,15 +43,11 @@ TEST_F(YacTest, ValidCaseWhenReceiveSupermajority) { initYac(my_order.value()); - EXPECT_CALL(*network, send_commit(_, _)).Times(my_peers.size()); - EXPECT_CALL(*network, send_reject(_, _)).Times(0); - EXPECT_CALL(*network, send_vote(_, _)).Times(my_peers.size()); + EXPECT_CALL(*network, sendState(_, _)).Times(2 * my_peers.size()); EXPECT_CALL(*timer, deny()).Times(0); - EXPECT_CALL(*crypto, verify(An())).Times(0); - EXPECT_CALL(*crypto, verify(An())).Times(0); - EXPECT_CALL(*crypto, verify(An())).WillRepeatedly(Return(true)); + EXPECT_CALL(*crypto, verify(_)).WillRepeatedly(Return(true)); YacHash my_hash("proposal_hash", "block_hash"); yac->vote(my_hash, my_order.value()); @@ -59,7 +55,7 @@ TEST_F(YacTest, ValidCaseWhenReceiveSupermajority) { for (auto i = 0; i < 3; ++i) { auto peer = my_peers.at(i); auto pubkey = shared_model::crypto::toBinaryString(peer->pubkey()); - yac->on_vote(create_vote(my_hash, pubkey)); + yac->onState({create_vote(my_hash, pubkey)}); }; } @@ -74,20 +70,16 @@ TEST_F(YacTest, ValidCaseWhenReceiveCommit) { initYac(my_order.value()); YacHash my_hash("proposal_hash", "block_hash"); - auto wrapper = make_test_subscriber(yac->on_commit(), 1); - wrapper.subscribe( - [my_hash](auto val) { ASSERT_EQ(my_hash, val.votes.at(0).hash); }); + auto wrapper = make_test_subscriber(yac->onOutcome(), 1); + wrapper.subscribe([my_hash](auto val) { + ASSERT_EQ(my_hash, boost::get(val).votes.at(0).hash); + }); - EXPECT_CALL(*network, send_commit(_, _)).Times(0); - EXPECT_CALL(*network, send_reject(_, _)).Times(0); - EXPECT_CALL(*network, send_vote(_, _)).Times(my_peers.size()); + EXPECT_CALL(*network, sendState(_, _)).Times(my_peers.size()); EXPECT_CALL(*timer, deny()).Times(AtLeast(1)); - EXPECT_CALL(*crypto, verify(An())) - .WillRepeatedly(Return(true)); - EXPECT_CALL(*crypto, verify(An())).Times(0); - EXPECT_CALL(*crypto, verify(An())).WillRepeatedly(Return(true)); + EXPECT_CALL(*crypto, verify(_)).WillRepeatedly(Return(true)); yac->vote(my_hash, my_order.value()); @@ -96,10 +88,18 @@ TEST_F(YacTest, ValidCaseWhenReceiveCommit) { for (auto i = 0; i < 4; ++i) { votes.push_back(create_vote(my_hash, std::to_string(i))); }; - yac->on_commit(CommitMessage(votes)); + yac->onState(votes); ASSERT_TRUE(wrapper.validate()); } +/** + * @given initialized YAC with empty state + * @when vote for hash + * AND receive commit for voted hash + * AND receive second commit for voted hash + * @then commit is emitted once + * AND timer is denied once + */ TEST_F(YacTest, ValidCaseWhenReceiveCommitTwice) { auto my_peers = decltype(default_peers)( {default_peers.begin(), default_peers.begin() + 4}); @@ -108,23 +108,19 @@ TEST_F(YacTest, ValidCaseWhenReceiveCommitTwice) { auto my_order = ClusterOrdering::create(my_peers); ASSERT_TRUE(my_order); - EXPECT_CALL(*timer, deny()).Times(2); + EXPECT_CALL(*timer, deny()).Times(1); initYac(my_order.value()); YacHash my_hash("proposal_hash", "block_hash"); - auto wrapper = make_test_subscriber(yac->on_commit(), 1); - wrapper.subscribe( - [my_hash](auto val) { ASSERT_EQ(my_hash, val.votes.at(0).hash); }); + auto wrapper = make_test_subscriber(yac->onOutcome(), 1); + wrapper.subscribe([my_hash](auto val) { + ASSERT_EQ(my_hash, boost::get(val).votes.at(0).hash); + }); - EXPECT_CALL(*network, send_commit(_, _)).Times(0); - EXPECT_CALL(*network, send_reject(_, _)).Times(0); - EXPECT_CALL(*network, send_vote(_, _)).Times(my_peers.size()); + EXPECT_CALL(*network, sendState(_, _)).Times(my_peers.size()); - EXPECT_CALL(*crypto, verify(An())) - .WillRepeatedly(Return(true)); - EXPECT_CALL(*crypto, verify(An())).Times(0); - EXPECT_CALL(*crypto, verify(An())).WillRepeatedly(Return(true)); + EXPECT_CALL(*crypto, verify(_)).WillRepeatedly(Return(true)); yac->vote(my_hash, my_order.value()); @@ -134,13 +130,13 @@ TEST_F(YacTest, ValidCaseWhenReceiveCommitTwice) { for (auto i = 0; i < 3; ++i) { votes.push_back(create_vote(my_hash, std::to_string(i))); }; - yac->on_commit(CommitMessage(votes)); + yac->onState(votes); // second commit for (auto i = 1; i < 4; ++i) { votes.push_back(create_vote(my_hash, std::to_string(i))); }; - yac->on_commit(CommitMessage(votes)); + yac->onState(votes); ASSERT_TRUE(wrapper.validate()); } @@ -154,35 +150,28 @@ TEST_F(YacTest, ValidCaseWhenSoloConsensus) { initYac(my_order.value()); - EXPECT_CALL(*network, send_commit(_, _)).Times(my_peers.size()); - EXPECT_CALL(*network, send_reject(_, _)).Times(0); - EXPECT_CALL(*network, send_vote(_, _)).Times(my_peers.size()); + EXPECT_CALL(*network, sendState(_, _)).Times(2 * my_peers.size()); EXPECT_CALL(*timer, deny()).Times(AtLeast(1)); - EXPECT_CALL(*crypto, verify(An())) - .Times(1) - .WillRepeatedly(Return(true)); - EXPECT_CALL(*crypto, verify(An())).Times(0); - EXPECT_CALL(*crypto, verify(An())) - .Times(1) - .WillRepeatedly(Return(true)); + EXPECT_CALL(*crypto, verify(_)).Times(2).WillRepeatedly(Return(true)); YacHash my_hash("proposal_hash", "block_hash"); - auto wrapper = make_test_subscriber(yac->on_commit(), 1); - wrapper.subscribe( - [my_hash](auto val) { ASSERT_EQ(my_hash, val.votes.at(0).hash); }); + auto wrapper = make_test_subscriber(yac->onOutcome(), 1); + wrapper.subscribe([my_hash](auto val) { + ASSERT_EQ(my_hash, boost::get(val).votes.at(0).hash); + }); yac->vote(my_hash, my_order.value()); auto vote_message = create_vote(my_hash, std::to_string(0)); - yac->on_vote(vote_message); + yac->onState({vote_message}); auto commit_message = CommitMessage({vote_message}); - yac->on_commit(commit_message); + yac->onState(commit_message.votes); ASSERT_TRUE(wrapper.validate()); } @@ -197,17 +186,11 @@ TEST_F(YacTest, ValidCaseWhenVoteAfterCommit) { initYac(my_order.value()); - EXPECT_CALL(*network, send_commit(_, _)).Times(0); - EXPECT_CALL(*network, send_reject(_, _)).Times(0); - EXPECT_CALL(*network, send_vote(_, _)).Times(0); + EXPECT_CALL(*network, sendState(_, _)).Times(0); EXPECT_CALL(*timer, deny()).Times(AtLeast(1)); - EXPECT_CALL(*crypto, verify(An())) - .Times(1) - .WillRepeatedly(Return(true)); - EXPECT_CALL(*crypto, verify(An())).Times(0); - EXPECT_CALL(*crypto, verify(An())).Times(0); + EXPECT_CALL(*crypto, verify(_)).Times(1).WillRepeatedly(Return(true)); YacHash my_hash("proposal_hash", "block_hash"); @@ -216,7 +199,7 @@ TEST_F(YacTest, ValidCaseWhenVoteAfterCommit) { for (auto i = 0; i < 3; ++i) { votes.push_back(create_vote(my_hash, std::to_string(i))); }; - yac->on_commit(CommitMessage(votes)); + yac->onState(votes); yac->vote(my_hash, my_order.value()); } diff --git a/test/module/irohad/consensus/yac/yac_unknown_peer_test.cpp b/test/module/irohad/consensus/yac/yac_unknown_peer_test.cpp index fdf8a80da5..69a02d8336 100644 --- a/test/module/irohad/consensus/yac/yac_unknown_peer_test.cpp +++ b/test/module/irohad/consensus/yac/yac_unknown_peer_test.cpp @@ -35,18 +35,12 @@ using namespace std; */ TEST_F(YacTest, UnknownVoteBeforeCommit) { // verify that commit not emitted - auto wrapper = make_test_subscriber(yac->on_commit(), 0); + auto wrapper = make_test_subscriber(yac->onOutcome(), 0); wrapper.subscribe(); - EXPECT_CALL(*network, send_commit(_, _)).Times(0); - EXPECT_CALL(*network, send_reject(_, _)).Times(0); - EXPECT_CALL(*network, send_vote(_, _)).Times(0); + EXPECT_CALL(*network, sendState(_, _)).Times(0); - EXPECT_CALL(*crypto, verify(An())).Times(0); - EXPECT_CALL(*crypto, verify(An())).Times(0); - EXPECT_CALL(*crypto, verify(An())) - .Times(1) - .WillRepeatedly(Return(true)); + EXPECT_CALL(*crypto, verify(_)).Times(1).WillRepeatedly(Return(true)); VoteMessage vote; vote.hash = YacHash("my_proposal", "my_block"); @@ -54,7 +48,7 @@ TEST_F(YacTest, UnknownVoteBeforeCommit) { vote.signature = createSig(unknown); // assume that our peer receive message - network->notification->on_vote(vote); + network->notification->onState({vote}); ASSERT_TRUE(wrapper.validate()); } @@ -75,15 +69,11 @@ TEST_F(YacTest, UnknownVoteAfterCommit) { initYac(my_order.value()); - EXPECT_CALL(*network, send_commit(_, _)).Times(0); - EXPECT_CALL(*network, send_reject(_, _)).Times(0); - EXPECT_CALL(*network, send_vote(_, _)).Times(0); + EXPECT_CALL(*network, sendState(_, _)).Times(0); EXPECT_CALL(*timer, deny()).Times(AtLeast(1)); - EXPECT_CALL(*crypto, verify(An())).WillOnce(Return(true)); - EXPECT_CALL(*crypto, verify(An())).Times(0); - EXPECT_CALL(*crypto, verify(An())).WillOnce(Return(true)); + EXPECT_CALL(*crypto, verify(_)).Times(2).WillRepeatedly(Return(true)); YacHash my_hash("proposal_hash", "block_hash"); @@ -92,11 +82,11 @@ TEST_F(YacTest, UnknownVoteAfterCommit) { for (auto i = 0; i < 3; ++i) { votes.push_back(create_vote(my_hash, std::to_string(i))); }; - yac->on_commit(CommitMessage(votes)); + yac->onState(votes); VoteMessage vote; vote.hash = my_hash; std::string unknown = "unknown"; vote.signature = createSig(unknown); - yac->on_vote(vote); + yac->onState({vote}); } diff --git a/test/module/irohad/network/network_mocks.hpp b/test/module/irohad/network/network_mocks.hpp index bbc28dcde7..ae18cbe62c 100644 --- a/test/module/irohad/network/network_mocks.hpp +++ b/test/module/irohad/network/network_mocks.hpp @@ -95,6 +95,7 @@ namespace iroha { MOCK_METHOD0(on_commit, rxcpp::observable()); }; + } // namespace network } // namespace iroha diff --git a/test/module/irohad/ordering/CMakeLists.txt b/test/module/irohad/ordering/CMakeLists.txt index 44b5b51bb1..b35bef6aa3 100644 --- a/test/module/irohad/ordering/CMakeLists.txt +++ b/test/module/irohad/ordering/CMakeLists.txt @@ -25,3 +25,29 @@ target_link_libraries(ordering_gate_test shared_model_cryptography_model shared_model_stateless_validation ) + +addtest(on_demand_os_test on_demand_os_test.cpp) +target_link_libraries(on_demand_os_test + on_demand_ordering_service + shared_model_default_builders + ) + +addtest(on_demand_os_client_grpc_test on_demand_os_client_grpc_test.cpp) +target_link_libraries(on_demand_os_client_grpc_test + on_demand_ordering_service_transport_grpc + ) + +addtest(on_demand_os_server_grpc_test on_demand_os_server_grpc_test.cpp) +target_link_libraries(on_demand_os_server_grpc_test + on_demand_ordering_service_transport_grpc + ) + +addtest(on_demand_connection_manager_test on_demand_connection_manager_test.cpp) +target_link_libraries(on_demand_connection_manager_test + on_demand_connection_manager + ) + +addtest(on_demand_ordering_gate_test on_demand_ordering_gate_test.cpp) +target_link_libraries(on_demand_ordering_gate_test + on_demand_ordering_gate + ) diff --git a/test/module/irohad/ordering/on_demand_connection_manager_test.cpp b/test/module/irohad/ordering/on_demand_connection_manager_test.cpp new file mode 100644 index 0000000000..942b9c4192 --- /dev/null +++ b/test/module/irohad/ordering/on_demand_connection_manager_test.cpp @@ -0,0 +1,133 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "ordering/impl/on_demand_connection_manager.hpp" + +#include +#include +#include "interfaces/iroha_internal/proposal.hpp" +#include "module/irohad/ordering/ordering_mocks.hpp" +#include "module/shared_model/interface_mocks.hpp" + +using namespace iroha::ordering; +using namespace iroha::ordering::transport; + +using ::testing::ByMove; +using ::testing::Ref; +using ::testing::Return; + +/** + * Create unique_ptr with MockOdOsNotification, save to var, and return it + */ +ACTION_P(CreateAndSave, var) { + auto result = std::make_unique(); + *var = result.get(); + return std::unique_ptr(std::move(result)); +} + +struct OnDemandConnectionManagerTest : public ::testing::Test { + void SetUp() override { + factory = std::make_shared(); + + auto set = [this](auto &field, auto &ptr) { + field = std::make_shared(); + + EXPECT_CALL(*factory, create(Ref(*field))) + .WillRepeatedly(CreateAndSave(&ptr)); + }; + + for (auto &&pair : boost::combine(cpeers.peers, connections)) { + set(boost::get<0>(pair), boost::get<1>(pair)); + } + + manager = std::make_shared( + factory, cpeers, peers.get_observable()); + } + + OnDemandConnectionManager::CurrentPeers cpeers; + OnDemandConnectionManager::PeerCollectionType + connections; + + rxcpp::subjects::subject peers; + std::shared_ptr factory; + std::shared_ptr manager; +}; + +/** + * @given OnDemandConnectionManager + * @when peers observable is triggered + * @then new peers are requested from factory + */ +TEST_F(OnDemandConnectionManagerTest, FactoryUsed) { + for (auto &peer : connections) { + ASSERT_NE(peer, nullptr); + } +} + +/** + * @given initialized OnDemandConnectionManager + * @when onTransactions is called + * @then peers get data for propagation + */ +TEST_F(OnDemandConnectionManagerTest, onTransactions) { + OdOsNotification::CollectionType collection; + Round round{1, 2}; + const OnDemandConnectionManager::PeerType types[] = { + OnDemandConnectionManager::kCurrentRoundRejectConsumer, + OnDemandConnectionManager::kNextRoundRejectConsumer, + OnDemandConnectionManager::kNextRoundCommitConsumer}; + const transport::Round rounds[] = { + {round.block_round, round.reject_round + 2}, + {round.block_round + 1, 2}, + {round.block_round + 2, 1}}; + for (auto &&pair : boost::combine(types, rounds)) { + EXPECT_CALL(*connections[boost::get<0>(pair)], + onTransactions(boost::get<1>(pair), collection)) + .Times(1); + } + + manager->onTransactions(round, collection); +} + +/** + * @given initialized OnDemandConnectionManager + * @when onRequestProposal is called + * AND proposal is returned + * @then peer is triggered + * AND return data is forwarded + */ +TEST_F(OnDemandConnectionManagerTest, onRequestProposal) { + Round round; + boost::optional oproposal = + OnDemandConnectionManager::ProposalType{}; + auto proposal = oproposal.value().get(); + EXPECT_CALL(*connections[OnDemandConnectionManager::kIssuer], + onRequestProposal(round)) + .WillOnce(Return(ByMove(std::move(oproposal)))); + + auto result = manager->onRequestProposal(round); + + ASSERT_TRUE(result); + ASSERT_EQ(result.value().get(), proposal); +} + +/** + * @given initialized OnDemandConnectionManager + * @when onRequestProposal is called + * AND no proposal is returned + * @then peer is triggered + * AND return data is forwarded + */ +TEST_F(OnDemandConnectionManagerTest, onRequestProposalNone) { + Round round; + boost::optional oproposal; + EXPECT_CALL(*connections[OnDemandConnectionManager::kIssuer], + onRequestProposal(round)) + .WillOnce(Return(ByMove(std::move(oproposal)))); + + auto result = manager->onRequestProposal(round); + + ASSERT_FALSE(result); +} diff --git a/test/module/irohad/ordering/on_demand_ordering_gate_test.cpp b/test/module/irohad/ordering/on_demand_ordering_gate_test.cpp new file mode 100644 index 0000000000..f02d5029a9 --- /dev/null +++ b/test/module/irohad/ordering/on_demand_ordering_gate_test.cpp @@ -0,0 +1,174 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "ordering/impl/on_demand_ordering_gate.hpp" + +#include +#include "framework/test_subscriber.hpp" +#include "interfaces/iroha_internal/transaction_batch.hpp" +#include "module/irohad/ordering/ordering_mocks.hpp" +#include "module/shared_model/interface_mocks.hpp" + +using namespace iroha::ordering; +using namespace iroha::ordering::transport; +using namespace iroha::network; +using namespace framework::test_subscriber; + +using ::testing::_; +using ::testing::ByMove; +using ::testing::Return; +using ::testing::Truly; + +struct OnDemandOrderingGateTest : public ::testing::Test { + void SetUp() override { + ordering_service = std::make_shared(); + notification = std::make_shared(); + auto ufactory = std::make_unique(); + factory = ufactory.get(); + ordering_gate = + std::make_shared(ordering_service, + notification, + rounds.get_observable(), + std::move(ufactory), + initial_round); + } + + rxcpp::subjects::subject rounds; + std::shared_ptr ordering_service; + std::shared_ptr notification; + MockUnsafeProposalFactory *factory; + std::shared_ptr ordering_gate; + + const Round initial_round = {2, 1}; +}; + +/** + * @given initialized ordering gate + * @when a batch is received + * @then it is passed to the ordering service + */ +TEST_F(OnDemandOrderingGateTest, propagateBatch) { + OdOsNotification::CollectionType collection; + shared_model::interface::TransactionBatch batch(collection); + + EXPECT_CALL(*notification, onTransactions(initial_round, collection)) + .Times(1); + + ordering_gate->propagateBatch(batch); +} + +/** + * @given initialized ordering gate + * @when a block round event with height is received from the PCS + * AND a proposal is successfully retrieved from the network + * @then new proposal round based on the received height is initiated + */ +TEST_F(OnDemandOrderingGateTest, BlockEvent) { + OnDemandOrderingGate::BlockEvent event{3}; + Round round{event.height, 1}; + + boost::optional oproposal(nullptr); + auto proposal = oproposal.value().get(); + + EXPECT_CALL(*ordering_service, onCollaborationOutcome(round)).Times(1); + EXPECT_CALL(*notification, onRequestProposal(round)) + .WillOnce(Return(ByMove(std::move(oproposal)))); + EXPECT_CALL(*factory, unsafeCreateProposal(_, _, _)).Times(0); + + auto gate_wrapper = + make_test_subscriber(ordering_gate->on_proposal(), 1); + gate_wrapper.subscribe([&](auto val) { ASSERT_EQ(val.get(), proposal); }); + + rounds.get_subscriber().on_next(event); + + ASSERT_TRUE(gate_wrapper.validate()); +} + +/** + * @given initialized ordering gate + * @when an empty block round event is received from the PCS + * AND a proposal is successfully retrieved from the network + * @then new proposal round based on the received height is initiated + */ +TEST_F(OnDemandOrderingGateTest, EmptyEvent) { + Round round{initial_round.block_round, initial_round.reject_round + 1}; + + boost::optional oproposal(nullptr); + auto proposal = oproposal.value().get(); + + EXPECT_CALL(*ordering_service, onCollaborationOutcome(round)).Times(1); + EXPECT_CALL(*notification, onRequestProposal(round)) + .WillOnce(Return(ByMove(std::move(oproposal)))); + EXPECT_CALL(*factory, unsafeCreateProposal(_, _, _)).Times(0); + + auto gate_wrapper = + make_test_subscriber(ordering_gate->on_proposal(), 1); + gate_wrapper.subscribe([&](auto val) { ASSERT_EQ(val.get(), proposal); }); + + rounds.get_subscriber().on_next(OnDemandOrderingGate::EmptyEvent{}); + + ASSERT_TRUE(gate_wrapper.validate()); +} + +/** + * @given initialized ordering gate + * @when a block round event with height is received from the PCS + * AND a proposal is not retrieved from the network + * @then new empty proposal round based on the received height is initiated + */ +TEST_F(OnDemandOrderingGateTest, BlockEventNoProposal) { + OnDemandOrderingGate::BlockEvent event{3}; + Round round{event.height, 1}; + + boost::optional oproposal; + + EXPECT_CALL(*ordering_service, onCollaborationOutcome(round)).Times(1); + EXPECT_CALL(*notification, onRequestProposal(round)) + .WillOnce(Return(ByMove(std::move(oproposal)))); + + OdOsNotification::ProposalType uproposal; + auto proposal = uproposal.get(); + + EXPECT_CALL(*factory, unsafeCreateProposal(_, _, _)) + .WillOnce(Return(ByMove(std::move(uproposal)))); + + auto gate_wrapper = + make_test_subscriber(ordering_gate->on_proposal(), 1); + gate_wrapper.subscribe([&](auto val) { ASSERT_EQ(val.get(), proposal); }); + + rounds.get_subscriber().on_next(event); + + ASSERT_TRUE(gate_wrapper.validate()); +} + +/** + * @given initialized ordering gate + * @when an empty block round event is received from the PCS + * AND a proposal is not retrieved from the network + * @then new empty proposal round based on the received height is initiated + */ +TEST_F(OnDemandOrderingGateTest, EmptyEventNoProposal) { + Round round{initial_round.block_round, initial_round.reject_round + 1}; + + boost::optional oproposal; + + EXPECT_CALL(*ordering_service, onCollaborationOutcome(round)).Times(1); + EXPECT_CALL(*notification, onRequestProposal(round)) + .WillOnce(Return(ByMove(std::move(oproposal)))); + + OdOsNotification::ProposalType uproposal; + auto proposal = uproposal.get(); + + EXPECT_CALL(*factory, unsafeCreateProposal(_, _, _)) + .WillOnce(Return(ByMove(std::move(uproposal)))); + + auto gate_wrapper = + make_test_subscriber(ordering_gate->on_proposal(), 1); + gate_wrapper.subscribe([&](auto val) { ASSERT_EQ(val.get(), proposal); }); + + rounds.get_subscriber().on_next(OnDemandOrderingGate::EmptyEvent{}); + + ASSERT_TRUE(gate_wrapper.validate()); +} diff --git a/test/module/irohad/ordering/on_demand_os_client_grpc_test.cpp b/test/module/irohad/ordering/on_demand_os_client_grpc_test.cpp new file mode 100644 index 0000000000..8f96abcef5 --- /dev/null +++ b/test/module/irohad/ordering/on_demand_os_client_grpc_test.cpp @@ -0,0 +1,135 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "ordering/impl/on_demand_os_client_grpc.hpp" + +#include +#include "backend/protobuf/transaction.hpp" +#include "framework/mock_stream.h" +#include "interfaces/iroha_internal/proposal.hpp" +#include "ordering_mock.grpc.pb.h" + +using namespace iroha; +using namespace iroha::ordering; +using namespace iroha::ordering::transport; + +using grpc::testing::MockClientAsyncResponseReader; +using ::testing::_; +using ::testing::DoAll; +using ::testing::Return; +using ::testing::SaveArg; +using ::testing::SetArgPointee; + +struct OnDemandOsClientGrpcTest : public ::testing::Test { + void SetUp() override { + auto ustub = std::make_unique(); + stub = ustub.get(); + async_call = + std::make_shared>(); + client = std::make_shared( + std::move(ustub), async_call, [&] { return timepoint; }, timeout); + } + + proto::MockOnDemandOrderingStub *stub; + std::shared_ptr> async_call; + OnDemandOsClientGrpc::TimepointType timepoint; + std::chrono::milliseconds timeout{1}; + std::shared_ptr client; + Round round{1, 2}; +}; + +/** + * @given client + * @when onTransactions is called + * @then data is correctly serialized and sent + */ +TEST_F(OnDemandOsClientGrpcTest, onTransactions) { + proto::TransactionsRequest request; + auto r = std::make_unique< + MockClientAsyncResponseReader>(); + EXPECT_CALL(*stub, AsyncSendTransactionsRaw(_, _, _)) + .WillOnce(DoAll(SaveArg<1>(&request), Return(r.get()))); + + OdOsNotification::CollectionType collection; + auto creator = "test"; + protocol::Transaction tx; + tx.mutable_payload()->mutable_reduced_payload()->set_creator_account_id( + creator); + collection.push_back(std::make_unique(tx)); + client->onTransactions(round, std::move(collection)); + + ASSERT_EQ(request.round().block_round(), round.block_round); + ASSERT_EQ(request.round().reject_round(), round.reject_round); + ASSERT_EQ(request.transactions() + .Get(0) + .payload() + .reduced_payload() + .creator_account_id(), + creator); +} + +/** + * Separate action required because ClientContext is non-copyable + */ +ACTION_P(SaveClientContextDeadline, deadline) { + *deadline = arg0->deadline(); +} + +/** + * @given client + * @when onRequestProposal is called + * AND proposal returned + * @then data is correctly serialized and sent + * AND reply is correctly deserialized + */ +TEST_F(OnDemandOsClientGrpcTest, onRequestProposal) { + std::chrono::system_clock::time_point deadline; + proto::ProposalRequest request; + auto creator = "test"; + proto::ProposalResponse response; + response.mutable_proposal() + ->add_transactions() + ->mutable_payload() + ->mutable_reduced_payload() + ->set_creator_account_id(creator); + EXPECT_CALL(*stub, RequestProposal(_, _, _)) + .WillOnce(DoAll(SaveClientContextDeadline(&deadline), + SaveArg<1>(&request), + SetArgPointee<2>(response), + Return(grpc::Status::OK))); + + auto proposal = client->onRequestProposal(round); + + ASSERT_EQ(timepoint + timeout, deadline); + ASSERT_EQ(request.round().block_round(), round.block_round); + ASSERT_EQ(request.round().reject_round(), round.reject_round); + ASSERT_TRUE(proposal); + ASSERT_EQ(proposal.value()->transactions()[0].creatorAccountId(), creator); +} + +/** + * @given client + * @when onRequestProposal is called + * AND no proposal returned + * @then data is correctly serialized and sent + * AND reply is correctly deserialized + */ +TEST_F(OnDemandOsClientGrpcTest, onRequestProposalNone) { + std::chrono::system_clock::time_point deadline; + proto::ProposalRequest request; + proto::ProposalResponse response; + EXPECT_CALL(*stub, RequestProposal(_, _, _)) + .WillOnce(DoAll(SaveClientContextDeadline(&deadline), + SaveArg<1>(&request), + SetArgPointee<2>(response), + Return(grpc::Status::OK))); + + auto proposal = client->onRequestProposal(round); + + ASSERT_EQ(timepoint + timeout, deadline); + ASSERT_EQ(request.round().block_round(), round.block_round); + ASSERT_EQ(request.round().reject_round(), round.reject_round); + ASSERT_FALSE(proposal); +} diff --git a/test/module/irohad/ordering/on_demand_os_server_grpc_test.cpp b/test/module/irohad/ordering/on_demand_os_server_grpc_test.cpp new file mode 100644 index 0000000000..ff5e1b0a69 --- /dev/null +++ b/test/module/irohad/ordering/on_demand_os_server_grpc_test.cpp @@ -0,0 +1,114 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "ordering/impl/on_demand_os_server_grpc.hpp" + +#include +#include "backend/protobuf/proposal.hpp" +#include "module/irohad/ordering/ordering_mocks.hpp" + +using namespace iroha; +using namespace iroha::ordering; +using namespace iroha::ordering::transport; + +using ::testing::_; +using ::testing::ByMove; +using ::testing::Return; + +struct OnDemandOsServerGrpcTest : public ::testing::Test { + void SetUp() override { + notification = std::make_shared(); + server = std::make_shared(notification); + } + + std::shared_ptr notification; + std::shared_ptr server; + Round round{1, 2}; +}; + +/** + * Separate action required because CollectionType is non-copyable + */ +ACTION_P(SaveArg1Move, var) { + *var = std::move(arg1); +} + +/** + * @given server + * @when collection is received from the network + * @then it is correctly deserialized and passed + */ +TEST_F(OnDemandOsServerGrpcTest, SendTransactions) { + OdOsNotification::CollectionType collection; + auto creator = "test"; + + EXPECT_CALL(*notification, onTransactions(round, _)) + .WillOnce(SaveArg1Move(&collection)); + proto::TransactionsRequest request; + request.mutable_round()->set_block_round(round.block_round); + request.mutable_round()->set_reject_round(round.reject_round); + request.add_transactions() + ->mutable_payload() + ->mutable_reduced_payload() + ->set_creator_account_id(creator); + + server->SendTransactions(nullptr, &request, nullptr); + + ASSERT_EQ(collection.at(0)->creatorAccountId(), creator); +} + +/** + * @given server + * @when proposal is requested + * AND proposal returned + * @then it is correctly serialized + */ +TEST_F(OnDemandOsServerGrpcTest, RequestProposal) { + auto creator = "test"; + proto::ProposalRequest request; + request.mutable_round()->set_block_round(round.block_round); + request.mutable_round()->set_reject_round(round.reject_round); + proto::ProposalResponse response; + protocol::Proposal proposal; + proposal.add_transactions() + ->mutable_payload() + ->mutable_reduced_payload() + ->set_creator_account_id(creator); + + std::unique_ptr iproposal( + std::make_unique(proposal)); + EXPECT_CALL(*notification, onRequestProposal(round)) + .WillOnce(Return(ByMove(std::move(iproposal)))); + + server->RequestProposal(nullptr, &request, &response); + + ASSERT_TRUE(response.has_proposal()); + ASSERT_EQ(response.proposal() + .transactions() + .Get(0) + .payload() + .reduced_payload() + .creator_account_id(), + creator); +} + +/** + * @given server + * @when proposal is requested + * AND no proposal returned + * @then the result is correctly serialized + */ +TEST_F(OnDemandOsServerGrpcTest, RequestProposalNone) { + proto::ProposalRequest request; + request.mutable_round()->set_block_round(round.block_round); + request.mutable_round()->set_reject_round(round.reject_round); + proto::ProposalResponse response; + EXPECT_CALL(*notification, onRequestProposal(round)) + .WillOnce(Return(ByMove(std::move(boost::none)))); + + server->RequestProposal(nullptr, &request, &response); + + ASSERT_FALSE(response.has_proposal()); +} diff --git a/test/module/irohad/ordering/on_demand_os_test.cpp b/test/module/irohad/ordering/on_demand_os_test.cpp new file mode 100644 index 0000000000..d3e9ce55b0 --- /dev/null +++ b/test/module/irohad/ordering/on_demand_os_test.cpp @@ -0,0 +1,176 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "ordering/impl/on_demand_ordering_service_impl.hpp" + +#include +#include + +#include +#include "builders/protobuf/transaction.hpp" +#include "datetime/time.hpp" + +using namespace iroha; +using namespace iroha::ordering; +using namespace iroha::ordering::transport; + +class OnDemandOsTest : public ::testing::Test { + public: + std::shared_ptr os; + const uint64_t transaction_limit = 20; + const uint32_t proposal_limit = 5; + const Round initial_round = {2, 1}, target_round = {4, 1}, + commit_round = {3, 1}, reject_round = {2, 2}; + + void SetUp() override { + os = std::make_shared( + transaction_limit, proposal_limit, initial_round); + } + + /** + * Generate transactions with provided range + * @param os - ordering service for insertion + * @param range - pair of [from, to) + */ + void generateTransactionsAndInsert(Round round, + std::pair range) { + auto now = iroha::time::now(); + OnDemandOrderingService::CollectionType collection; + for (auto i = range.first; i < range.second; ++i) { + collection.push_back(std::make_unique( + shared_model::proto::TransactionBuilder() + .createdTime(now + i) + .creatorAccountId("foo@bar") + .createAsset("asset", "domain", 1) + .quorum(1) + .build() + .signAndAddSignature( + shared_model::crypto::DefaultCryptoAlgorithmType:: + generateKeypair()) + .finish())); + } + os->onTransactions(round, std::move(collection)); + } +}; + +/** + * @given initialized on-demand OS + * @when don't send transactions + * AND initiate next round + * @then check that previous round doesn't have proposal + */ +TEST_F(OnDemandOsTest, EmptyRound) { + ASSERT_FALSE(os->onRequestProposal(initial_round)); + + os->onCollaborationOutcome(commit_round); + + ASSERT_FALSE(os->onRequestProposal(initial_round)); +} + +/** + * @given initialized on-demand OS + * @when send number of transactions less that limit + * AND initiate next round + * @then check that previous round has all transaction + */ +TEST_F(OnDemandOsTest, NormalRound) { + generateTransactionsAndInsert(target_round, {1, 2}); + + os->onCollaborationOutcome(commit_round); + + ASSERT_TRUE(os->onRequestProposal(target_round)); +} + +/** + * @given initialized on-demand OS + * @when send number of transactions greater that limit + * AND initiate next round + * @then check that previous round has only limit of transactions + * AND the rest of transactions isn't appeared in next after next round + */ +TEST_F(OnDemandOsTest, OverflowRound) { + generateTransactionsAndInsert(target_round, {1, transaction_limit * 2}); + + os->onCollaborationOutcome(commit_round); + + ASSERT_TRUE(os->onRequestProposal(target_round)); + ASSERT_EQ(transaction_limit, + (*os->onRequestProposal(target_round))->transactions().size()); +} + +/** + * @given initialized on-demand OS + * @when send transactions from different threads + * AND initiate next round + * @then check that all transactions are appeared in proposal + */ +TEST_F(OnDemandOsTest, DISABLED_ConcurrentInsert) { + auto large_tx_limit = 10000u; + os = std::make_shared( + large_tx_limit, proposal_limit, initial_round); + + auto call = [this](auto bounds) { + for (auto i = bounds.first; i < bounds.second; ++i) { + this->generateTransactionsAndInsert(target_round, {i, i + 1}); + } + }; + + std::thread one(call, std::make_pair(0u, large_tx_limit / 2)); + std::thread two(call, std::make_pair(large_tx_limit / 2, large_tx_limit)); + one.join(); + two.join(); + os->onCollaborationOutcome(commit_round); + ASSERT_EQ(large_tx_limit, + os->onRequestProposal(target_round).get()->transactions().size()); +} + +/** + * @given initialized on-demand OS + * @when insert proposal_limit rounds twice + * @then on second rounds check that old proposals are expired + */ +TEST_F(OnDemandOsTest, Erase) { + for (auto i = commit_round.block_round; + i < commit_round.block_round + proposal_limit; + ++i) { + generateTransactionsAndInsert({i + 1, commit_round.reject_round}, {1, 2}); + os->onCollaborationOutcome({i, commit_round.reject_round}); + ASSERT_TRUE(os->onRequestProposal({i + 1, commit_round.reject_round})); + } + + for (BlockRoundType i = commit_round.block_round + proposal_limit; + i < commit_round.block_round + 2 * proposal_limit; + ++i) { + generateTransactionsAndInsert({i + 1, commit_round.reject_round}, {1, 2}); + os->onCollaborationOutcome({i, commit_round.reject_round}); + ASSERT_FALSE(os->onRequestProposal( + {i + 1 - proposal_limit, commit_round.reject_round})); + } +} + +/** + * @given initialized on-demand OS + * @when insert proposal_limit rounds twice + * AND outcome is reject + * @then on second rounds check that old proposals are expired + */ +TEST_F(OnDemandOsTest, EraseReject) { + for (auto i = reject_round.reject_round; + i < reject_round.reject_round + proposal_limit; + ++i) { + generateTransactionsAndInsert({reject_round.block_round, i + 1}, {1, 2}); + os->onCollaborationOutcome({reject_round.block_round, i}); + ASSERT_TRUE(os->onRequestProposal({reject_round.block_round, i + 1})); + } + + for (RejectRoundType i = reject_round.reject_round + proposal_limit; + i < reject_round.reject_round + 2 * proposal_limit; + ++i) { + generateTransactionsAndInsert({reject_round.block_round, i + 1}, {1, 2}); + os->onCollaborationOutcome({reject_round.block_round, i}); + ASSERT_FALSE(os->onRequestProposal( + {reject_round.block_round, i + 1 - proposal_limit})); + } +} diff --git a/test/module/irohad/ordering/ordering_mocks.hpp b/test/module/irohad/ordering/ordering_mocks.hpp new file mode 100644 index 0000000000..8dcea6dc57 --- /dev/null +++ b/test/module/irohad/ordering/ordering_mocks.hpp @@ -0,0 +1,44 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef IROHA_ORDERING_MOCKS_HPP +#define IROHA_ORDERING_MOCKS_HPP + +#include + +#include "ordering/on_demand_ordering_service.hpp" +#include "ordering/on_demand_os_transport.hpp" + +namespace iroha { + namespace ordering { + namespace transport { + + struct MockOdOsNotification : public OdOsNotification { + MOCK_METHOD2(onTransactions, void(Round, CollectionType)); + + MOCK_METHOD1(onRequestProposal, boost::optional(Round)); + }; + + struct MockOdOsNotificationFactory : public OdOsNotificationFactory { + MOCK_METHOD1(create, + std::unique_ptr( + const shared_model::interface::Peer &)); + }; + + } // namespace transport + + struct MockOnDemandOrderingService : public OnDemandOrderingService { + MOCK_METHOD2(onTransactions, void(transport::Round, CollectionType)); + + MOCK_METHOD1(onRequestProposal, + boost::optional(transport::Round)); + + MOCK_METHOD1(onCollaborationOutcome, void(transport::Round)); + }; + + } // namespace ordering +} // namespace iroha + +#endif // IROHA_ORDERING_MOCKS_HPP diff --git a/test/module/irohad/ordering/ordering_service_test.cpp b/test/module/irohad/ordering/ordering_service_test.cpp index fdf4b2d65c..710dfa2979 100644 --- a/test/module/irohad/ordering/ordering_service_test.cpp +++ b/test/module/irohad/ordering/ordering_service_test.cpp @@ -15,8 +15,8 @@ #include "module/irohad/ordering/mock_ordering_service_persistent_state.hpp" #include "module/shared_model/builders/protobuf/common_objects/proto_peer_builder.hpp" #include "module/shared_model/builders/protobuf/test_proposal_builder.hpp" -#include "ordering/impl/ordering_service_impl.hpp" #include "ordering/impl/ordering_service_transport_grpc.hpp" +#include "ordering/impl/single_peer_ordering_service.hpp" using namespace iroha; using namespace iroha::ordering; @@ -81,7 +81,7 @@ class OrderingServiceTest : public ::testing::Test { } auto initOs(size_t max_proposal) { - return std::make_shared( + return std::make_shared( pqfactory, max_proposal, proposal_timeout.get_observable(), @@ -105,7 +105,8 @@ class OrderingServiceTest : public ::testing::Test { std::shared_ptr wsv; std::shared_ptr pqfactory; std::unique_ptr factory; - rxcpp::subjects::subject proposal_timeout; + rxcpp::subjects::subject + proposal_timeout; }; /** @@ -281,7 +282,7 @@ TEST_F(OrderingServiceTest, GenerateProposalDestructor) { { EXPECT_CALL(*fake_transport, publishProposalProxy(_, _)).Times(AtLeast(1)); - OrderingServiceImpl ordering_service( + SinglePeerOrderingService ordering_service( pqfactory, max_proposal, rxcpp::observable<>::interval(commit_delay, diff --git a/test/module/libs/cache/CMakeLists.txt b/test/module/libs/cache/CMakeLists.txt index 40879455bd..70875a3f12 100644 --- a/test/module/libs/cache/CMakeLists.txt +++ b/test/module/libs/cache/CMakeLists.txt @@ -12,9 +12,17 @@ # See the License for the specific language governing permissions and # limitations under the License. -addtest(cache_test cache_test.cpp) +addtest(cache_test + cache_test.cpp + ) target_link_libraries(cache_test - torii_service - ) + torii_service + ) -addtest(single_pointer_cache_test single_pointer_cache_test.cpp) +addtest(single_pointer_cache_test + single_pointer_cache_test.cpp + ) + +addtest(transaction_cache_test + transaction_cache_test.cpp + ) diff --git a/test/module/libs/cache/transaction_cache_test.cpp b/test/module/libs/cache/transaction_cache_test.cpp new file mode 100644 index 0000000000..7a5f580444 --- /dev/null +++ b/test/module/libs/cache/transaction_cache_test.cpp @@ -0,0 +1,81 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include + +#include "cache/collection_set.hpp" + +using namespace iroha::set; + +class TransactionCacheTest : public testing::Test { + public: + uint32_t number_of_calls = 0; + std::shared_ptr> set; + + void SetUp() override { + number_of_calls = 0; + set = std::make_shared>(); + } +}; + +/** + * @given empty set + * @when check that empty set doesn't contain elements + * AND insert some collection + * @then check that elements are appeared + */ +TEST_F(TransactionCacheTest, insert) { + set->forEach([this](const auto &val) { number_of_calls++; }); + ASSERT_EQ(0, number_of_calls); + + set->insertValues(std::vector({1, 2})); + + set->forEach([this](const auto &val) { number_of_calls++; }); + ASSERT_EQ(2, number_of_calls); +} + +/** + * @given empty set + * @when insert some collection + * AND insert duplicated elements + * @then check that duplicates are not appeared + */ +TEST_F(TransactionCacheTest, insertDuplicates) { + set->insertValues(std::vector({1, 2})); + set->insertValues(std::vector({1, 3})); + + set->forEach([this](const auto &val) { number_of_calls++; }); + ASSERT_EQ(3, number_of_calls); +} + +/** + * @given empty set + * @when insert some collection + * AND remove another collection with same and different elements + * @then check that duplicates and removed elements are not appeared + */ +TEST_F(TransactionCacheTest, remove) { + set->insertValues(std::vector({1, 2, 3})); + set->removeValues(std::vector({1, 3, 4})); + set->forEach([this](const auto &val) { number_of_calls++; }); + ASSERT_EQ(1, number_of_calls); +} + +/** + * @given set with existed state + * @when insert the set to target collection + * AND call forEach and push all elements to out set + * @then check is first and out sets are the same + */ +TEST_F(TransactionCacheTest, checkElements) { + std::unordered_set first = {1, 2, 3}; + set->insertValues(first); + + std::unordered_set permutation; + set->forEach([&permutation](const auto &val) { permutation.insert(val); }); + std::is_permutation(first.begin(), first.end(), permutation.begin()); +} diff --git a/test/module/shared_model/backend_proto/proto_batch_test.cpp b/test/module/shared_model/backend_proto/proto_batch_test.cpp index 192b4703c8..39665da21f 100644 --- a/test/module/shared_model/backend_proto/proto_batch_test.cpp +++ b/test/module/shared_model/backend_proto/proto_batch_test.cpp @@ -9,6 +9,7 @@ #include "framework/batch_helper.hpp" #include "framework/result_fixture.hpp" #include "interfaces/iroha_internal/transaction_batch.hpp" +#include "interfaces/iroha_internal/transaction_batch_factory.hpp" #include "validators/field_validator.hpp" #include "validators/transaction_validator.hpp" #include "validators/transactions_collection/batch_order_validator.hpp" @@ -61,8 +62,9 @@ TEST(TransactionBatchTest, CreateTransactionBatchWhenValid) { BatchTypeAndCreatorPair{interface::types::BatchType::ATOMIC, "b@domain"}}); - auto transaction_batch = interface::TransactionBatch::createTransactionBatch( - txs, validation::DefaultUnsignedTransactionsValidator()); + auto transaction_batch = + interface::TransactionBatchFactory::createTransactionBatch( + txs, validation::DefaultUnsignedTransactionsValidator()); ASSERT_TRUE(framework::expected::val(transaction_batch)) << framework::expected::err(transaction_batch).value().error; } @@ -82,8 +84,9 @@ TEST(TransactionBatchTest, CreateTransactionBatchWhenDifferentBatchType) { auto txs = framework::batch::createUnsignedBatchTransactions( std::vector{tx1_fields, tx2_fields}); - auto transaction_batch = interface::TransactionBatch::createTransactionBatch( - txs, validation::DefaultUnsignedTransactionsValidator()); + auto transaction_batch = + interface::TransactionBatchFactory::createTransactionBatch( + txs, validation::DefaultUnsignedTransactionsValidator()); ASSERT_TRUE(framework::expected::err(transaction_batch)); } @@ -98,8 +101,9 @@ TEST(TransactionBatchTest, CreateBatchWithValidAndInvalidTx) { interface::types::BatchType::ATOMIC, std::vector{"valid@name", "invalid#@name"}); - auto transaction_batch = interface::TransactionBatch::createTransactionBatch( - txs, validation::DefaultUnsignedTransactionsValidator()); + auto transaction_batch = + interface::TransactionBatchFactory::createTransactionBatch( + txs, validation::DefaultUnsignedTransactionsValidator()); ASSERT_TRUE(framework::expected::err(transaction_batch)); } @@ -117,8 +121,9 @@ TEST(TransactionBatchTest, CreateSingleTxBatchWhenValid) { crypto::DefaultCryptoAlgorithmType::sign(tx1->payload(), keypair); tx1->addSignature(signed_blob, keypair.publicKey()); - auto transaction_batch = interface::TransactionBatch::createTransactionBatch( - tx1, transaction_validator); + auto transaction_batch = + interface::TransactionBatchFactory::createTransactionBatch( + tx1, transaction_validator); ASSERT_TRUE(framework::expected::val(transaction_batch)) << framework::expected::err(transaction_batch).value().error; @@ -134,8 +139,9 @@ TEST(TransactionBatchTest, CreateSingleTxBatchWhenInvalid) { auto tx1 = createInvalidUnsignedTransaction(); - auto transaction_batch = interface::TransactionBatch::createTransactionBatch( - tx1, transaction_validator); + auto transaction_batch = + interface::TransactionBatchFactory::createTransactionBatch( + tx1, transaction_validator); ASSERT_TRUE(framework::expected::err(transaction_batch)); } @@ -162,7 +168,7 @@ auto createBatchWithTransactionsWithQuorum( now, quorum); - return interface::TransactionBatch::createTransactionBatch( + return interface::TransactionBatchFactory::createTransactionBatch( transactions, validation::DefaultUnsignedTransactionsValidator()); } @@ -206,9 +212,10 @@ TEST(TransactionBatchTest, BatchWithNoSignatures) { auto unsigned_transactions = framework::batch::createUnsignedBatchTransactions( interface::types::BatchType::ATOMIC, batch_size); - auto transaction_batch = interface::TransactionBatch::createTransactionBatch( - unsigned_transactions, - validation::DefaultUnsignedTransactionsValidator()); + auto transaction_batch = + interface::TransactionBatchFactory::createTransactionBatch( + unsigned_transactions, + validation::DefaultUnsignedTransactionsValidator()); ASSERT_TRUE(framework::expected::err(transaction_batch)); } @@ -243,9 +250,10 @@ inline auto makeSignedTxBuilder( TEST(TransactionBatchTest, BatchWithOneSignature) { auto unsigned_transactions = framework::batch::makeTestBatchTransactions( makeTxBuilder(1), makeTxBuilder(2), makeSignedTxBuilder(1)); - auto transaction_batch = interface::TransactionBatch::createTransactionBatch( - unsigned_transactions, - validation::DefaultUnsignedTransactionsValidator()); + auto transaction_batch = + interface::TransactionBatchFactory::createTransactionBatch( + unsigned_transactions, + validation::DefaultUnsignedTransactionsValidator()); ASSERT_TRUE(framework::expected::val(transaction_batch)) << framework::expected::err(transaction_batch).value().error; } diff --git a/test/module/shared_model/interface_mocks.hpp b/test/module/shared_model/interface_mocks.hpp index 2ea83a2039..73277bc4e2 100644 --- a/test/module/shared_model/interface_mocks.hpp +++ b/test/module/shared_model/interface_mocks.hpp @@ -7,7 +7,10 @@ #define IROHA_SHARED_MODEL_INTERFACE_MOCKS_HPP #include +#include "interfaces/common_objects/peer.hpp" #include "interfaces/iroha_internal/block.hpp" +#include "interfaces/iroha_internal/proposal.hpp" +#include "interfaces/iroha_internal/unsafe_proposal_factory.hpp" #include "interfaces/transaction.hpp" struct BlockMock : public shared_model::interface::Block { @@ -32,7 +35,7 @@ struct BlockMock : public shared_model::interface::Block { MOCK_CONST_METHOD0(clone, BlockMock *()); }; -struct TransactionMock : public shared_model::interface::Transaction { +struct MockTransaction : public shared_model::interface::Transaction { MOCK_CONST_METHOD0(creatorAccountId, const shared_model::interface::types::AccountIdType &()); MOCK_CONST_METHOD0(quorum, shared_model::interface::types::QuorumType()); @@ -52,7 +55,12 @@ struct TransactionMock : public shared_model::interface::Transaction { MOCK_METHOD2(addSignature, bool(const shared_model::crypto::Signed &, const shared_model::crypto::PublicKey &)); - MOCK_CONST_METHOD0(clone, TransactionMock *()); + MOCK_CONST_METHOD0(clone, MockTransaction *()); + MOCK_CONST_METHOD0(reducedPayload, + const shared_model::interface::types::BlobType &()); + MOCK_CONST_METHOD0( + batchMeta, + boost::optional>()); }; struct SignatureMock : public shared_model::interface::Signature { @@ -61,4 +69,33 @@ struct SignatureMock : public shared_model::interface::Signature { MOCK_CONST_METHOD0(clone, SignatureMock *()); }; +struct MockProposal : public shared_model::interface::Proposal { + MOCK_CONST_METHOD0( + transactions, + shared_model::interface::types::TransactionsCollectionType()); + MOCK_CONST_METHOD0(height, shared_model::interface::types::HeightType()); + MOCK_CONST_METHOD0(createdTime, + shared_model::interface::types::TimestampType()); + MOCK_CONST_METHOD0(blob, const shared_model::interface::types::BlobType &()); + MOCK_CONST_METHOD0(clone, MockProposal *()); +}; + +struct MockPeer : public shared_model::interface::Peer { + MOCK_CONST_METHOD0(address, + const shared_model::interface::types::AddressType &()); + MOCK_CONST_METHOD0(pubkey, + const shared_model::interface::types::PubkeyType &()); + MOCK_CONST_METHOD0(clone, MockPeer *()); +}; + +struct MockUnsafeProposalFactory + : public shared_model::interface::UnsafeProposalFactory { + MOCK_METHOD3( + unsafeCreateProposal, + std::unique_ptr( + shared_model::interface::types::HeightType, + shared_model::interface::types::TimestampType, + const shared_model::interface::types::TransactionsCollectionType &)); +}; + #endif // IROHA_SHARED_MODEL_INTERFACE_MOCKS_HPP