mirror of
https://github.com/EvernodeXRPL/hpcore.git
synced 2026-04-29 15:37:59 +00:00
User nonce validation and expiration. (#164)
This commit is contained in:
@@ -49,6 +49,7 @@ add_executable(hpcore
|
||||
src/p2p/p2p.cpp
|
||||
src/usr/user_comm_session.cpp
|
||||
src/usr/user_session_handler.cpp
|
||||
src/usr/input_nonce_map.cpp
|
||||
src/usr/usr.cpp
|
||||
src/usr/read_req.cpp
|
||||
src/ledger.cpp
|
||||
|
||||
@@ -185,6 +185,8 @@ function HotPocketClient(server, keys, protocol = protocols.BSON) {
|
||||
|
||||
if (!nonce)
|
||||
nonce = (new Date()).getTime().toString();
|
||||
else
|
||||
nonce = nonce.toString();
|
||||
|
||||
// Acquire the current lcl and add the specified offset.
|
||||
const stat = await this.getStatus();
|
||||
|
||||
@@ -421,7 +421,6 @@ namespace consensus
|
||||
// We only process inputs in the submitted order that can be satisfied with the remaining account balance.
|
||||
size_t total_input_len = 0;
|
||||
bool appbill_balance_exceeded = false;
|
||||
util::rollover_hashset recent_user_input_hashes(200);
|
||||
|
||||
for (const usr::user_input &umsg : umsgs)
|
||||
{
|
||||
@@ -436,13 +435,9 @@ namespace consensus
|
||||
util::buffer_view input;
|
||||
std::string hash;
|
||||
uint64_t max_lcl_seqno;
|
||||
reject_reason = usr::validate_user_input_submission(pubkey, umsg, lcl_seq_no, total_input_len, recent_user_input_hashes,
|
||||
hash, input, max_lcl_seqno);
|
||||
reject_reason = usr::validate_user_input_submission(pubkey, umsg, lcl_seq_no, total_input_len, hash, input, max_lcl_seqno);
|
||||
|
||||
if (input.is_null())
|
||||
return -1;
|
||||
|
||||
if (reject_reason == NULL)
|
||||
if (reject_reason == NULL && !input.is_null())
|
||||
{
|
||||
// No reject reason means we should go ahead and subject the input to consensus.
|
||||
ctx.candidate_user_inputs.try_emplace(
|
||||
|
||||
@@ -42,10 +42,10 @@ namespace msg::usrmsg
|
||||
constexpr const char *STATUS_REJECTED = "rejected";
|
||||
constexpr const char *REASON_BAD_MSG_FORMAT = "bad_msg_format";
|
||||
constexpr const char *REASON_INVALID_MSG_TYPE = "invalid_msg_type";
|
||||
constexpr const char *REASON_DUPLICATE_MSG = "dup_msg";
|
||||
constexpr const char *REASON_BAD_SIG = "bad_sig";
|
||||
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";
|
||||
|
||||
} // namespace msg::usrmsg
|
||||
|
||||
|
||||
59
src/usr/input_nonce_map.cpp
Normal file
59
src/usr/input_nonce_map.cpp
Normal file
@@ -0,0 +1,59 @@
|
||||
#include "../pchheader.hpp"
|
||||
#include "../util/util.hpp"
|
||||
#include "input_nonce_map.hpp"
|
||||
|
||||
namespace usr
|
||||
{
|
||||
constexpr uint64_t TTL = 300000; // 5 minutes.
|
||||
constexpr uint16_t CLEANUP_THRESHOLD = 256;
|
||||
|
||||
/**
|
||||
* 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)
|
||||
*/
|
||||
bool input_nonce_map::is_valid(const std::string &pubkey, const std::string &nonce, const bool no_add)
|
||||
{
|
||||
bool valid = false;
|
||||
|
||||
const uint64_t now = util::get_epoch_milliseconds();
|
||||
auto itr = nonce_map.find(pubkey);
|
||||
if (itr == nonce_map.end())
|
||||
{
|
||||
valid = true;
|
||||
if (!no_add)
|
||||
nonce_map.emplace(pubkey, std::pair<std::string, uint64_t>(nonce, 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);
|
||||
|
||||
if (valid && !no_add)
|
||||
{
|
||||
itr->second.first = nonce;
|
||||
itr->second.second = now + TTL;
|
||||
}
|
||||
}
|
||||
|
||||
if (nonce_map.size() > CLEANUP_THRESHOLD)
|
||||
cleanup();
|
||||
|
||||
return valid;
|
||||
}
|
||||
|
||||
void input_nonce_map::cleanup()
|
||||
{
|
||||
const uint64_t now = util::get_epoch_milliseconds();
|
||||
|
||||
for (auto itr = nonce_map.begin(); itr != nonce_map.end();)
|
||||
{
|
||||
const uint64_t expire_on = itr->second.second;
|
||||
if (expire_on <= now)
|
||||
itr = nonce_map.erase(itr);
|
||||
else
|
||||
itr++;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace usr
|
||||
21
src/usr/input_nonce_map.hpp
Normal file
21
src/usr/input_nonce_map.hpp
Normal file
@@ -0,0 +1,21 @@
|
||||
#ifndef _HP_USR_INPUT_NONCE_MAP_
|
||||
#define _HP_USR_INPUT_NONCE_MAP_
|
||||
|
||||
#include "../pchheader.hpp"
|
||||
|
||||
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;
|
||||
void cleanup();
|
||||
|
||||
public:
|
||||
bool is_valid(const std::string &pubkey, const std::string &nonce, const bool no_add = false);
|
||||
};
|
||||
|
||||
} // namespace usr
|
||||
|
||||
#endif
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "user_comm_server.hpp"
|
||||
#include "user_input.hpp"
|
||||
#include "read_req.hpp"
|
||||
#include "input_nonce_map.hpp"
|
||||
|
||||
namespace usr
|
||||
{
|
||||
@@ -22,6 +23,7 @@ namespace usr
|
||||
connected_context ctx;
|
||||
|
||||
util::buffer_store input_store;
|
||||
input_nonce_map nonce_map;
|
||||
uint64_t metric_thresholds[5];
|
||||
bool init_success = false;
|
||||
|
||||
@@ -144,12 +146,25 @@ namespace usr
|
||||
{
|
||||
std::scoped_lock<std::mutex> lock(ctx.users_mutex);
|
||||
|
||||
//Add to the submitted input list.
|
||||
user.submitted_inputs.push_back(user_input(
|
||||
std::move(input_container),
|
||||
std::move(sig),
|
||||
user.protocol));
|
||||
return 0;
|
||||
std::string input_data;
|
||||
std::string nonce;
|
||||
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))
|
||||
{
|
||||
//Add to the submitted input list.
|
||||
user.submitted_inputs.push_back(user_input(
|
||||
std::move(input_container),
|
||||
std::move(sig),
|
||||
user.protocol));
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
send_input_status(parser, user.session, msg::usrmsg::STATUS_REJECTED, msg::usrmsg::REASON_NONCE_EXPIRED, sig);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -262,20 +277,10 @@ namespace usr
|
||||
* Validates the provided user input message against all the required criteria.
|
||||
* @return The rejection reason if input rejected. NULL if the input can be accepted.
|
||||
*/
|
||||
const char *validate_user_input_submission(const std::string_view user_pubkey, const usr::user_input &umsg,
|
||||
const char *validate_user_input_submission(const std::string &user_pubkey, const usr::user_input &umsg,
|
||||
const uint64_t lcl_seq_no, size_t &total_input_len,
|
||||
util::rollover_hashset &recent_user_input_hashes,
|
||||
std::string &hash, util::buffer_view &input, uint64_t &max_lcl_seqno)
|
||||
{
|
||||
const std::string sig_hash = crypto::get_hash(umsg.sig);
|
||||
|
||||
// Check for duplicate messages using hash of the signature.
|
||||
if (!recent_user_input_hashes.try_emplace(sig_hash))
|
||||
{
|
||||
LOG_DEBUG << "Duplicate user message.";
|
||||
return msg::usrmsg::REASON_DUPLICATE_MSG;
|
||||
}
|
||||
|
||||
// Verify the signature of the input_container.
|
||||
if (crypto::verify(umsg.input_container, umsg.sig, user_pubkey) == -1)
|
||||
{
|
||||
@@ -296,6 +301,12 @@ namespace usr
|
||||
return msg::usrmsg::REASON_MAX_LEDGER_EXPIRED;
|
||||
}
|
||||
|
||||
if (!nonce_map.is_valid(user_pubkey, nonce))
|
||||
{
|
||||
LOG_DEBUG << "User message nonce expired.";
|
||||
return msg::usrmsg::REASON_NONCE_EXPIRED;
|
||||
}
|
||||
|
||||
// Keep checking the subtotal of inputs extracted so far with the appbill account balance.
|
||||
total_input_len += input_data.length();
|
||||
if (!verify_appbill_check(user_pubkey, total_input_len))
|
||||
@@ -307,7 +318,7 @@ namespace usr
|
||||
// Hash is prefixed with the nonce to support user-defined sort order.
|
||||
hash = std::move(nonce);
|
||||
// Append the hash of the message signature to get the final hash.
|
||||
hash.append(sig_hash);
|
||||
hash.append(crypto::get_hash(umsg.sig));
|
||||
|
||||
// Copy the input data into the input store.
|
||||
std::string_view s();
|
||||
|
||||
@@ -79,9 +79,8 @@ namespace usr
|
||||
|
||||
int remove_user(const std::string &pubkey);
|
||||
|
||||
const char *validate_user_input_submission(const std::string_view user_pubkey, const usr::user_input &umsg,
|
||||
const char *validate_user_input_submission(const std::string &user_pubkey, const usr::user_input &umsg,
|
||||
const uint64_t lcl_seq_no, size_t &total_input_len,
|
||||
util::rollover_hashset &recent_user_input_hashes,
|
||||
std::string &hash, util::buffer_view &input, uint64_t &max_lcl_seqno);
|
||||
|
||||
bool verify_appbill_check(std::string_view pubkey, const size_t input_len);
|
||||
|
||||
Reference in New Issue
Block a user