Already submitted input detection. (#170)

* Added same nonce/sig for input comparison.
* Added status response withholding for already submitted inputs.
This commit is contained in:
Ravin Perera
2020-11-26 23:27:40 +05:30
committed by GitHub
parent d476f787a7
commit 4de1bb2393
5 changed files with 49 additions and 27 deletions

View File

@@ -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);
}
}
}
}

View File

@@ -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

View File

@@ -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<std::string, uint64_t>(nonce, util::get_epoch_milliseconds() + TTL));
nonce_map.emplace(pubkey, std::tuple<std::string, std::string, uint64_t>(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

View File

@@ -8,12 +8,12 @@ namespace usr
class input_nonce_map
{
private:
// Keeps short-lived items with their absolute expiration time.
std::unordered_map<std::string, std::pair<std::string, uint64_t>> nonce_map;
// Keeps short-lived nonces and signatures with their absolute expiration time.
std::unordered_map<std::string, std::tuple<std::string, std::string, uint64_t>> 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

View File

@@ -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.