mirror of
https://github.com/EvernodeXRPL/hpcore.git
synced 2026-04-29 15:37:59 +00:00
Refactored consensus into 3 rounds. (#144)
* Refactored consensus into 3 stages and removed stage 0. * Consensus threshold calculation improvements. * Refactored candidate user input processing. * Renamed proposal sent timestamp field. * Introduced comm_session display name.
This commit is contained in:
318
src/usr/usr.cpp
318
src/usr/usr.cpp
@@ -24,9 +24,9 @@ namespace usr
|
||||
bool init_success = false;
|
||||
|
||||
/**
|
||||
* Initializes the usr subsystem. Must be called once during application startup.
|
||||
* @return 0 for successful initialization. -1 for failure.
|
||||
*/
|
||||
* Initializes the usr subsystem. Must be called once during application startup.
|
||||
* @return 0 for successful initialization. -1 for failure.
|
||||
*/
|
||||
int init()
|
||||
{
|
||||
metric_thresholds[0] = conf::cfg.pubmaxcpm;
|
||||
@@ -43,8 +43,8 @@ namespace usr
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup any running processes.
|
||||
*/
|
||||
* Cleanup any running processes.
|
||||
*/
|
||||
void deinit()
|
||||
{
|
||||
if (init_success)
|
||||
@@ -52,8 +52,8 @@ namespace usr
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts listening for incoming user websocket connections.
|
||||
*/
|
||||
* Starts listening for incoming user websocket connections.
|
||||
*/
|
||||
int start_listening()
|
||||
{
|
||||
if (ctx.listener.start(
|
||||
@@ -65,72 +65,43 @@ namespace usr
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies the given message for a previously issued user challenge.
|
||||
* @param message Challenge response.
|
||||
* @param session The socket session that received the response.
|
||||
* @return 0 for successful verification. -1 for failure.
|
||||
*/
|
||||
* Verifies the given message for a previously issued user challenge.
|
||||
* @param message Challenge response.
|
||||
* @param session The socket session that received the response.
|
||||
* @return 0 for successful verification. -1 for failure.
|
||||
*/
|
||||
int verify_challenge(std::string_view message, comm::comm_session &session)
|
||||
{
|
||||
// The received message must be the challenge response. We need to verify it.
|
||||
if (session.issued_challenge.empty())
|
||||
{
|
||||
LOG_DEBUG << "No challenge found for the session " << session.uniqueid.substr(0, 10);
|
||||
LOG_DEBUG << "No user challenge found for the session " << session.display_name();
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::string userpubkeyhex;
|
||||
std::string user_pubkey_hex;
|
||||
std::string protocol_code;
|
||||
std::string_view original_challenge = session.issued_challenge;
|
||||
|
||||
if (msg::usrmsg::json::verify_user_handshake_response(userpubkeyhex, protocol_code, message, original_challenge) == 0)
|
||||
if (msg::usrmsg::json::verify_user_handshake_response(user_pubkey_hex, protocol_code, message, original_challenge) == 0)
|
||||
{
|
||||
// Challenge signature verification successful.
|
||||
|
||||
// Decode hex pubkey and get binary pubkey. We are only going to keep
|
||||
// the binary pubkey due to reduced memory footprint.
|
||||
std::string userpubkey;
|
||||
userpubkey.resize(userpubkeyhex.length() / 2);
|
||||
util::hex2bin(
|
||||
reinterpret_cast<unsigned char *>(userpubkey.data()),
|
||||
userpubkey.length(),
|
||||
userpubkeyhex);
|
||||
|
||||
// Now check whether this user public key is a duplicate.
|
||||
if (ctx.sessionids.count(userpubkey) == 0)
|
||||
{
|
||||
// All good. Unique public key.
|
||||
// Promote the connection from pending-challenges to authenticated users.
|
||||
|
||||
const util::PROTOCOL user_protocol = (protocol_code == "json" ? util::PROTOCOL::JSON : util::PROTOCOL::BSON);
|
||||
|
||||
session.challenge_status = comm::CHALLENGE_VERIFIED; // Set as challenge verified
|
||||
add_user(session, userpubkey, user_protocol); // Add the user to the global authed user list
|
||||
session.issued_challenge.clear(); // Remove the stored challenge
|
||||
|
||||
LOG_DEBUG << "User connection " << session.uniqueid.substr(0, 10) << " authenticated. Public key "
|
||||
<< userpubkeyhex;
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_DEBUG << "Duplicate user public key " << session.uniqueid.substr(0, 10);
|
||||
}
|
||||
// Challenge signature verification successful. Add the user to our global user list.
|
||||
add_user(session, user_pubkey_hex, protocol_code);
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_DEBUG << "Challenge verification failed " << session.uniqueid.substr(0, 10);
|
||||
LOG_DEBUG << "User challenge verification failed " << session.display_name();
|
||||
return -1;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a message sent by a connected user. This will be invoked by web socket on_message handler.
|
||||
* @param user The authenticated user who sent the message.
|
||||
* @param message The message sent by user.
|
||||
* @return 0 on successful processing. -1 for failure.
|
||||
*/
|
||||
* Processes a message sent by a connected user. This will be invoked by web socket on_message handler.
|
||||
* @param user The authenticated user who sent the message.
|
||||
* @param message The message sent by user.
|
||||
* @return 0 on successful processing. -1 for failure.
|
||||
*/
|
||||
int handle_user_message(connected_user &user, std::string_view message)
|
||||
{
|
||||
msg::usrmsg::usrmsg_parser parser(user.protocol);
|
||||
@@ -200,8 +171,8 @@ namespace usr
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the specified contract input status result via the provided session.
|
||||
*/
|
||||
* Send the specified contract input status result via the provided session.
|
||||
*/
|
||||
void send_input_status(const msg::usrmsg::usrmsg_parser &parser, comm::comm_session &session,
|
||||
std::string_view status, std::string_view reason, std::string_view input_sig)
|
||||
{
|
||||
@@ -211,78 +182,179 @@ namespace usr
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the user denoted by specified session id and public key to the global authed user list.
|
||||
* This should get called after the challenge handshake is verified.
|
||||
*
|
||||
* @param session User socket session.
|
||||
* @param pubkey User's binary public key.
|
||||
* @param protocol Messaging protocol used by user.
|
||||
* @return 0 on successful additions. -1 on failure.
|
||||
*/
|
||||
int add_user(comm::comm_session &session, const std::string &pubkey, const util::PROTOCOL protocol)
|
||||
{
|
||||
const std::string &sessionid = session.uniqueid;
|
||||
if (ctx.users.count(sessionid) == 1)
|
||||
{
|
||||
LOG_INFO << sessionid << " already exist. Cannot add user.";
|
||||
return -1;
|
||||
}
|
||||
|
||||
{
|
||||
std::scoped_lock<std::mutex> lock(ctx.users_mutex);
|
||||
ctx.users.emplace(sessionid, usr::connected_user(session, pubkey, protocol));
|
||||
}
|
||||
|
||||
// Populate sessionid map so we can lookup by user pubkey.
|
||||
ctx.sessionids.try_emplace(pubkey, sessionid);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the specified public key from the global user list.
|
||||
* This must get called when a user disconnects from HP.
|
||||
*
|
||||
* @param sessionid User socket session id.
|
||||
* @return 0 on successful removals. -1 on failure.
|
||||
*/
|
||||
int remove_user(const std::string &sessionid)
|
||||
{
|
||||
const auto itr = ctx.users.find(sessionid);
|
||||
|
||||
if (itr == ctx.users.end())
|
||||
{
|
||||
LOG_INFO << sessionid << " does not exist. Cannot remove user.";
|
||||
return -1;
|
||||
}
|
||||
|
||||
usr::connected_user &user = itr->second;
|
||||
|
||||
{
|
||||
std::scoped_lock<std::mutex> lock(ctx.users_mutex);
|
||||
ctx.sessionids.erase(user.pubkey);
|
||||
}
|
||||
|
||||
ctx.users.erase(itr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds and returns the socket session for the proided user pubkey.
|
||||
* @param pubkey User binary pubkey.
|
||||
* @return Pointer to the socket session. NULL of not found.
|
||||
* Adds the user denoted by specified session id and public key to the global authed user list.
|
||||
* This should get called after the challenge handshake is verified.
|
||||
*
|
||||
* @param session User socket session.
|
||||
* @param user_pubkey_hex User's hex public key.
|
||||
* @param protocol_code Messaging protocol used by user.
|
||||
* @return 0 on successful additions. -1 on failure.
|
||||
*/
|
||||
comm::comm_session *get_session_by_pubkey(const std::string &pubkey)
|
||||
int add_user(comm::comm_session &session, const std::string &pubkey_hex, std::string_view protocol_code)
|
||||
{
|
||||
const auto sessionid_itr = ctx.sessionids.find(pubkey);
|
||||
if (sessionid_itr != ctx.sessionids.end())
|
||||
// Decode hex pubkey and get binary pubkey. We are only going to keep
|
||||
// the binary pubkey due to reduced memory footprint.
|
||||
std::string pubkey;
|
||||
pubkey.resize(pubkey_hex.length() / 2);
|
||||
util::hex2bin(
|
||||
reinterpret_cast<unsigned char *>(pubkey.data()),
|
||||
pubkey.length(),
|
||||
pubkey_hex);
|
||||
|
||||
// Acquire user list lock.
|
||||
std::scoped_lock<std::mutex> lock(ctx.users_mutex);
|
||||
|
||||
// Now check whether this user public key is a duplicate.
|
||||
if (ctx.users.count(pubkey) == 0)
|
||||
{
|
||||
const auto user_itr = ctx.users.find(sessionid_itr->second);
|
||||
if (user_itr != ctx.users.end())
|
||||
return &user_itr->second.session;
|
||||
// All good. Unique public key.
|
||||
// Promote the connection from pending-challenges to authenticated users.
|
||||
|
||||
const util::PROTOCOL protocol = (protocol_code == "json" ? util::PROTOCOL::JSON : util::PROTOCOL::BSON);
|
||||
|
||||
session.challenge_status = comm::CHALLENGE_VERIFIED; // Set as challenge verified
|
||||
session.issued_challenge.clear(); // Remove the stored challenge
|
||||
session.uniqueid = pubkey;
|
||||
|
||||
// Add the user to the global authed user list
|
||||
ctx.users.emplace(pubkey, usr::connected_user(session, pubkey, protocol));
|
||||
LOG_DEBUG << "User connection authenticated. Public key " << pubkey_hex;
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_DEBUG << "Duplicate user public key " << session.display_name();
|
||||
}
|
||||
|
||||
return NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the specified public key from the global user list.
|
||||
* This must get called when an authenticated user disconnects from HP.
|
||||
*
|
||||
* @param pubkey User pubkey.
|
||||
* @return 0 on successful removals. -1 on failure.
|
||||
*/
|
||||
int remove_user(const std::string &pubkey)
|
||||
{
|
||||
std::scoped_lock<std::mutex> lock(ctx.users_mutex);
|
||||
const auto itr = ctx.users.erase(pubkey);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 uint64_t lcl_seq_no, size_t &total_input_len,
|
||||
util::rollover_hashset &recent_user_input_hashes,
|
||||
std::string &hash, std::string &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)
|
||||
{
|
||||
LOG_DEBUG << "User message bad signature.";
|
||||
return msg::usrmsg::REASON_BAD_SIG;
|
||||
}
|
||||
|
||||
std::string nonce;
|
||||
msg::usrmsg::usrmsg_parser parser(umsg.protocol);
|
||||
parser.extract_input_container(input, nonce, max_lcl_seqno, umsg.input_container);
|
||||
|
||||
// Ignore the input if our ledger has passed the input TTL.
|
||||
if (max_lcl_seqno <= lcl_seq_no)
|
||||
{
|
||||
LOG_DEBUG << "User message bad max ledger seq expired.";
|
||||
return msg::usrmsg::REASON_MAX_LEDGER_EXPIRED;
|
||||
}
|
||||
|
||||
// Keep checking the subtotal of inputs extracted so far with the appbill account balance.
|
||||
total_input_len += input.length();
|
||||
if (!verify_appbill_check(user_pubkey, total_input_len))
|
||||
{
|
||||
LOG_DEBUG << "User message app bill balance exceeded.";
|
||||
return msg::usrmsg::REASON_APPBILL_BALANCE_EXCEEDED;
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
return NULL; // Success. No reject reason.
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the appbill and verifies whether the user has enough account balance to process the provided input.
|
||||
* @param pubkey User binary pubkey.
|
||||
* @param input_len Total bytes length of user input.
|
||||
* @return Whether the user is allowed to process the input or not.
|
||||
*/
|
||||
bool verify_appbill_check(std::string_view pubkey, const size_t input_len)
|
||||
{
|
||||
// If appbill not enabled always green light the input.
|
||||
if (conf::cfg.appbill.empty())
|
||||
return true;
|
||||
|
||||
// execute appbill in --check mode to verify this user can submit a packet/connection to the network
|
||||
// todo: this can be made more efficient, appbill --check can process 7 at a time
|
||||
|
||||
// Fill appbill args
|
||||
const int len = conf::cfg.runtime_appbill_args.size() + 4;
|
||||
char *execv_args[len];
|
||||
for (int i = 0; i < conf::cfg.runtime_appbill_args.size(); i++)
|
||||
execv_args[i] = conf::cfg.runtime_appbill_args[i].data();
|
||||
char option[] = "--check";
|
||||
execv_args[len - 4] = option;
|
||||
// add the hex encoded public key as the last parameter
|
||||
std::string hexpubkey;
|
||||
util::bin2hex(hexpubkey, reinterpret_cast<const unsigned char *>(pubkey.data()), pubkey.size());
|
||||
std::string inputsize = std::to_string(input_len);
|
||||
execv_args[len - 3] = hexpubkey.data();
|
||||
execv_args[len - 2] = inputsize.data();
|
||||
execv_args[len - 1] = NULL;
|
||||
|
||||
int pid = fork();
|
||||
if (pid == 0)
|
||||
{
|
||||
// appbill process.
|
||||
util::fork_detach();
|
||||
|
||||
// before execution chdir into a valid the latest state data directory that contains an appbill.table
|
||||
chdir(conf::ctx.state_rw_dir.c_str());
|
||||
int ret = execv(execv_args[0], execv_args);
|
||||
std::cerr << errno << ": Appbill process execv failed.\n";
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// app bill in check mode takes a very short period of time to execute, typically 1ms
|
||||
// so we will blocking wait for it here
|
||||
int status = 0;
|
||||
waitpid(pid, &status, 0); //todo: check error conditions here
|
||||
status = WEXITSTATUS(status);
|
||||
if (status != 128 && status != 0)
|
||||
{
|
||||
// this user's key passed appbill
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// user's key did not pass, do not add to user input candidates
|
||||
LOG_DEBUG << "Appbill validation failed " << hexpubkey << " return code was " << status;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace usr
|
||||
Reference in New Issue
Block a user