From 4de1bb23931467a9207cee7c6755d75622cc79a1 Mon Sep 17 00:00:00 2001 From: Ravin Perera <33562092+ravinsp@users.noreply.github.com> Date: Thu, 26 Nov 2020 23:27:40 +0530 Subject: [PATCH] Already submitted input detection. (#170) * Added same nonce/sig for input comparison. * Added status response withholding for already submitted inputs. --- src/consensus.cpp | 19 ++++++++++++------- src/msg/usrmsg_common.hpp | 1 + src/usr/input_nonce_map.cpp | 37 +++++++++++++++++++++++++------------ src/usr/input_nonce_map.hpp | 6 +++--- src/usr/usr.cpp | 13 ++++++++----- 5 files changed, 49 insertions(+), 27 deletions(-) diff --git a/src/consensus.cpp b/src/consensus.cpp index c6288d1a..9848940e 100644 --- a/src/consensus.cpp +++ b/src/consensus.cpp @@ -474,15 +474,20 @@ namespace consensus for (auto &resp : user_responses) { // resp: 0=protocl, 1=msg sig, 2=reject reason. - msg::usrmsg::usrmsg_parser parser(std::get<0>(resp)); - const std::string &msg_sig = std::get<1>(resp); const char *reject_reason = std::get<2>(resp); - usr::send_input_status(parser, - user_itr->second.session, - reject_reason == NULL ? msg::usrmsg::STATUS_ACCEPTED : msg::usrmsg::STATUS_REJECTED, - reject_reason == NULL ? "" : reject_reason, - msg_sig); + // We are not sending any status response for 'already submitted' inputs. This is because the user + // would have gotten the proper status response during first submission. + if (reject_reason != msg::usrmsg::REASON_ALREADY_SUBMITTED) + { + msg::usrmsg::usrmsg_parser parser(std::get<0>(resp)); + const std::string &msg_sig = std::get<1>(resp); + usr::send_input_status(parser, + user_itr->second.session, + reject_reason == NULL ? msg::usrmsg::STATUS_ACCEPTED : msg::usrmsg::STATUS_REJECTED, + reject_reason == NULL ? "" : reject_reason, + msg_sig); + } } } } diff --git a/src/msg/usrmsg_common.hpp b/src/msg/usrmsg_common.hpp index d1adc8c9..580635e3 100644 --- a/src/msg/usrmsg_common.hpp +++ b/src/msg/usrmsg_common.hpp @@ -46,6 +46,7 @@ namespace msg::usrmsg constexpr const char *REASON_APPBILL_BALANCE_EXCEEDED = "appbill_balance_exceeded"; constexpr const char *REASON_MAX_LEDGER_EXPIRED = "max_ledger_expired"; constexpr const char *REASON_NONCE_EXPIRED = "nonce_expired"; + constexpr const char *REASON_ALREADY_SUBMITTED = "already_submitted"; } // namespace msg::usrmsg diff --git a/src/usr/input_nonce_map.cpp b/src/usr/input_nonce_map.cpp index 5fa20bd0..e07ea767 100644 --- a/src/usr/input_nonce_map.cpp +++ b/src/usr/input_nonce_map.cpp @@ -10,36 +10,49 @@ namespace usr /** * Checks whether the given nonce is valid for the given user pubkey. If it is valid, remembers this nonce * to be checked for future checks. (If no_add is true, this nonce will not be remembered) + * @return 0 if nonce is valid to be submitted. + * 1 if nonce has expired. + * 2 if message with same nonce/sig has already been submitted. */ - bool input_nonce_map::is_valid(const std::string &pubkey, const std::string &nonce, const bool no_add) + int input_nonce_map::check(const std::string &pubkey, const std::string &nonce, const std::string &sig, const bool no_add) { - bool valid = false; + int result = 0; const uint64_t now = util::get_epoch_milliseconds(); auto itr = nonce_map.find(pubkey); if (itr == nonce_map.end()) { - valid = true; + result = 0; if (!no_add) - nonce_map.emplace(pubkey, std::pair(nonce, util::get_epoch_milliseconds() + TTL)); + nonce_map.emplace(pubkey, std::tuple(nonce, sig, (util::get_epoch_milliseconds() + TTL))); } else { - const std::string &existing_nonce = itr->second.first; - const uint64_t expire_on = itr->second.second; - valid = (expire_on <= now || existing_nonce < nonce); + const std::string &existing_nonce = std::get<0>(itr->second); + const uint64_t expire_on = std::get<2>(itr->second); - if (valid && !no_add) + // Check if previous nonce has already expired or it is less than new nonce. + if (expire_on <= now || existing_nonce < nonce) { - itr->second.first = nonce; - itr->second.second = now + TTL; + if (!no_add) + { + std::get<0>(itr->second) = nonce; + std::get<2>(itr->second) = now + TTL; + } + result = 0; + } + else + { + // If new nonce is deemed invalid, check if new nonce/sig is same as old nonce/sig. + const std::string &existing_sig = std::get<1>(itr->second); + result = (existing_nonce == nonce && existing_sig == sig) ? 2 : 1; } } if (nonce_map.size() > CLEANUP_THRESHOLD) cleanup(); - return valid; + return result; } void input_nonce_map::cleanup() @@ -48,7 +61,7 @@ namespace usr for (auto itr = nonce_map.begin(); itr != nonce_map.end();) { - const uint64_t expire_on = itr->second.second; + const uint64_t expire_on = std::get<2>(itr->second); if (expire_on <= now) itr = nonce_map.erase(itr); else diff --git a/src/usr/input_nonce_map.hpp b/src/usr/input_nonce_map.hpp index 6f31ff95..e77d4793 100644 --- a/src/usr/input_nonce_map.hpp +++ b/src/usr/input_nonce_map.hpp @@ -8,12 +8,12 @@ namespace usr class input_nonce_map { private: - // Keeps short-lived items with their absolute expiration time. - std::unordered_map> nonce_map; + // Keeps short-lived nonces and signatures with their absolute expiration time. + std::unordered_map> nonce_map; void cleanup(); public: - bool is_valid(const std::string &pubkey, const std::string &nonce, const bool no_add = false); + int check(const std::string &pubkey, const std::string &nonce, const std::string &sig, const bool no_add = false); }; } // namespace usr diff --git a/src/usr/usr.cpp b/src/usr/usr.cpp index 04014535..5c93555e 100644 --- a/src/usr/usr.cpp +++ b/src/usr/usr.cpp @@ -151,7 +151,8 @@ namespace usr uint64_t max_lcl_seqno; parser.extract_input_container(input_data, nonce, max_lcl_seqno, input_container); - if (nonce_map.is_valid(user.pubkey, nonce, true)) + const int nonce_status = nonce_map.check(user.pubkey, nonce, sig, true); + if (nonce_status == 0) { //Add to the submitted input list. user.submitted_inputs.push_back(user_input( @@ -162,7 +163,8 @@ namespace usr } else { - send_input_status(parser, user.session, msg::usrmsg::STATUS_REJECTED, msg::usrmsg::REASON_NONCE_EXPIRED, sig); + const char *reason = nonce_status == 1 ? msg::usrmsg::REASON_NONCE_EXPIRED : msg::usrmsg::REASON_ALREADY_SUBMITTED; + send_input_status(parser, user.session, msg::usrmsg::STATUS_REJECTED, reason, sig); return -1; } } @@ -301,10 +303,11 @@ namespace usr return msg::usrmsg::REASON_MAX_LEDGER_EXPIRED; } - if (!nonce_map.is_valid(user_pubkey, nonce)) + const int nonce_status = nonce_map.check(user_pubkey, nonce, umsg.sig); + if (nonce_status > 0) { - LOG_DEBUG << "User message nonce expired."; - return msg::usrmsg::REASON_NONCE_EXPIRED; + LOG_DEBUG << (nonce_status == 1 ? "User message nonce expired." : "User message with same nonce/sig already submitted."); + return (nonce_status == 1 ? msg::usrmsg::REASON_NONCE_EXPIRED : msg::usrmsg::REASON_ALREADY_SUBMITTED); } // Keep checking the subtotal of inputs extracted so far with the appbill account balance.