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:
Ravin Perera
2020-11-07 15:01:01 +05:30
committed by GitHub
parent 51173e37f2
commit ba0cae019d
17 changed files with 568 additions and 516 deletions

View File

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