Files
hpcore/src/msg/json/usrmsg_json.cpp
2021-04-12 07:15:57 +05:30

1018 lines
37 KiB
C++

#include "../../pchheader.hpp"
#include "../../util/version.hpp"
#include "../../util/util.hpp"
#include "../../util/merkle_hash_tree.hpp"
#include "../../unl.hpp"
#include "../../crypto.hpp"
#include "../../hplog.hpp"
#include "../../conf.hpp"
#include "../../ledger/ledger_query.hpp"
#include "../usrmsg_common.hpp"
#include "usrmsg_json.hpp"
namespace msg::usrmsg::json
{
// JSON separators
constexpr const char *SEP_COMMA = "\",\"";
constexpr const char *SEP_COLON = "\":\"";
constexpr const char *SEP_COMMA_NOQUOTE = ",\"";
constexpr const char *SEP_COLON_NOQUOTE = "\":";
constexpr const char *DOUBLE_QUOTE = "\"";
constexpr const char *OPEN_SQR_BRACKET = "[";
constexpr const char *CLOSE_SQR_BRACKET = "]";
constexpr const size_t MAX_KNOWN_PEERS_INFO = 10;
// std::vector overload to concatonate string.
std::vector<uint8_t> &operator+=(std::vector<uint8_t> &vec, std::string_view sv)
{
vec.insert(vec.end(), sv.begin(), sv.end());
return vec;
}
/**
* Constructs user challenge message json and the challenge string required for
* initial user challenge handshake. This gets called when a user establishes
* a web socket connection to HP.
*
* @param msg Buffer to construct the generated json message string into.
* Message format:
* {
* "hp_version": "<hp protocol version>",
* "type": "user_challenge",
* "contract_id": "<contract id>",
* "contract_version": "<contract version string>",
* "challenge": "<challenge string>"
* }
* @param challenge_bytes Buffer to construct the generated challenge bytes into.
*/
void create_user_challenge(std::vector<uint8_t> &msg, std::string &challenge)
{
std::string challenge_bytes;
crypto::random_bytes(challenge_bytes, msg::usrmsg::CHALLENGE_LEN);
challenge = util::to_hex(challenge_bytes);
// Construct the challenge msg json.
// We do not use jsoncons library here in favour of performance because this is a simple json message.
// Since we know the rough size of the challenge message we reserve adequate amount for the holder.
// Only Hot Pocket version number is variable length.
msg.reserve(256);
msg += "{\"";
msg += msg::usrmsg::FLD_HP_VERSION;
msg += SEP_COLON;
msg += version::HP_VERSION;
msg += SEP_COMMA;
msg += msg::usrmsg::FLD_TYPE;
msg += SEP_COLON;
msg += msg::usrmsg::MSGTYPE_USER_CHALLENGE;
msg += SEP_COMMA;
msg += msg::usrmsg::FLD_CONTRACT_ID;
msg += SEP_COLON;
msg += conf::cfg.contract.id;
msg += SEP_COMMA;
msg += msg::usrmsg::FLD_CONTRACT_VERSION;
msg += SEP_COLON;
msg += conf::cfg.contract.version;
msg += SEP_COMMA;
msg += msg::usrmsg::FLD_CHALLENGE;
msg += SEP_COLON;
msg += challenge;
msg += "\"}";
}
/**
* Constructs server challenge response message json. This gets sent when we receive
* a challenge from the user.
*
* @param msg Buffer to construct the generated json message string into.
* Message format:
* {
* "type": "server_challenge_response",
* "sig": "<hex encoded signature of the [challenge + contract_id]>",
* "pubkey": "<our public key in hex>",
* "unl": [<hex unl pubkey list>]
* }
* @param original_challenge Original challenge issued by the user.
*/
void create_server_challenge_response(std::vector<uint8_t> &msg, const std::string &original_challenge)
{
// Generate signature of challenge + contract id + contract version.
const std::string content = original_challenge + conf::cfg.contract.id + conf::cfg.contract.version;
const std::string sig_hex = util::to_hex(crypto::sign(content, conf::cfg.node.private_key));
// Since we know the rough size of the challenge message we reserve adequate amount for the holder.
msg.reserve(1024);
msg += "{\"";
msg += msg::usrmsg::FLD_TYPE;
msg += SEP_COLON;
msg += msg::usrmsg::MSGTYPE_SERVER_CHALLENGE_RESPONSE;
msg += SEP_COMMA;
msg += msg::usrmsg::FLD_SIG;
msg += SEP_COLON;
msg += sig_hex;
msg += SEP_COMMA;
msg += msg::usrmsg::FLD_PUBKEY;
msg += SEP_COLON;
msg += conf::cfg.node.public_key_hex;
msg += SEP_COMMA;
msg += msg::usrmsg::FLD_UNL;
msg += "\":[";
const std::set<std::string> unl_list = unl::get();
for (auto itr = unl_list.begin(); itr != unl_list.end(); itr++)
{
msg += "\"";
msg += util::to_hex(*itr);
msg += "\"";
if (std::next(itr) != unl_list.end())
msg += ",";
}
msg += "]}";
}
/**
* Constructs a status response message.
* @param msg Buffer to construct the generated json message string into.
* Message format:
* {
* "type": "stat_response",
* "ledger_seq_no": <lcl sequence no>,
* "ledger_hash": "<lcl hash hex>"
* }
*/
void create_status_response(std::vector<uint8_t> &msg, const uint64_t lcl_seq_no, std::string_view lcl_hash)
{
const uint16_t msg_length = 406 + (69 * conf::cfg.contract.unl.size());
msg.reserve(msg_length);
msg += "{\"";
msg += msg::usrmsg::FLD_TYPE;
msg += SEP_COLON;
msg += msg::usrmsg::MSGTYPE_STAT_RESPONSE;
msg += SEP_COMMA;
msg += msg::usrmsg::FLD_HP_VERSION;
msg += SEP_COLON;
msg += version::HP_VERSION;
msg += SEP_COMMA;
msg += msg::usrmsg::FLD_LEDGER_SEQ_NO;
msg += SEP_COLON_NOQUOTE;
msg += std::to_string(lcl_seq_no);
msg += SEP_COMMA_NOQUOTE;
msg += msg::usrmsg::FLD_LEDGER_HASH;
msg += SEP_COLON;
msg += util::to_hex(lcl_hash);
msg += SEP_COMMA;
msg += msg::usrmsg::FLD_ROUND_TIME;
msg += SEP_COLON_NOQUOTE;
msg += std::to_string(conf::cfg.contract.roundtime);
msg += SEP_COMMA_NOQUOTE;
msg += msg::usrmsg::FLD_CONTARCT_EXECUTION_ENABLED;
msg += SEP_COLON_NOQUOTE;
msg += conf::cfg.contract.execute ? "true" : "false";
msg += SEP_COMMA_NOQUOTE;
msg += msg::usrmsg::FLD_READ_REQUESTS_ENABLED;
msg += SEP_COLON_NOQUOTE;
msg += conf::cfg.user.concurrent_read_reqeuests != 0 ? "true" : "false";
msg += SEP_COMMA_NOQUOTE;
msg += msg::usrmsg::FLD_IS_FULL_HISTORY_NODE;
msg += SEP_COLON_NOQUOTE;
msg += conf::cfg.node.history == conf::HISTORY::FULL ? "true" : "false";
msg += SEP_COMMA_NOQUOTE;
msg += msg::usrmsg::FLD_CURRENT_UNL;
msg += SEP_COLON_NOQUOTE;
msg += OPEN_SQR_BRACKET;
for (auto node = conf::cfg.contract.unl.begin(); node != conf::cfg.contract.unl.end(); node++)
{
msg += DOUBLE_QUOTE + util::to_hex(*node) + DOUBLE_QUOTE;
if (std::next(node) != conf::cfg.contract.unl.end())
msg += ",";
}
msg += CLOSE_SQR_BRACKET;
msg += SEP_COMMA_NOQUOTE;
msg += msg::usrmsg::FLD_PEERS;
msg += SEP_COLON_NOQUOTE;
msg += OPEN_SQR_BRACKET;
{
std::scoped_lock<std::mutex> lock(p2p::ctx.peer_connections_mutex);
const size_t max_peers_count = MIN(MAX_KNOWN_PEERS_INFO, p2p::ctx.peer_connections.size());
size_t count = 1;
// Currently all peers, up to a max of 10 are sent regardless of state.
for (auto peer = p2p::ctx.peer_connections.begin(); peer != p2p::ctx.peer_connections.end() && count <= max_peers_count; peer++, count++)
{
msg += DOUBLE_QUOTE + peer->second->known_ipport->host_address + ":" + std::to_string(peer->second->known_ipport->port) + DOUBLE_QUOTE;
if (peer != p2p::ctx.peer_connections.end() && count < max_peers_count)
msg += ",";
}
}
msg += CLOSE_SQR_BRACKET;
msg += "}";
}
/**
* Constructs a contract input status message.
* @param msg Buffer to construct the generated json message string into.
* Message format:
* {
* "type": "contract_input_status",
* "status": "<accepted|rejected>",
* "reason": "<reson>",
* "input_hash": "<hex hash of original input signature>",
* "ledger_seq_no": <sequence no of the ledger that the input got included in>,
* "ledger_hash": "<hex hash no of the ledger that the input got included in>"
* }
* @param is_accepted Whether the original message was accepted or not.
* @param reason Rejected reason. Empty if accepted.
* @param input_hash Binary Hash of the original input signature. This is used by user
* to tie the response with the input submission.
*/
void create_contract_input_status(std::vector<uint8_t> &msg, std::string_view status, std::string_view reason,
std::string_view input_hash, const uint64_t ledger_seq_no, const util::h32 &ledger_hash)
{
msg.reserve(256);
msg += "{\"";
msg += msg::usrmsg::FLD_TYPE;
msg += SEP_COLON;
msg += msg::usrmsg::MSGTYPE_CONTRACT_INPUT_STATUS;
msg += SEP_COMMA;
msg += msg::usrmsg::FLD_STATUS;
msg += SEP_COLON;
msg += status;
msg += SEP_COMMA;
// Reject reason is only included for rejected inputs.
if (!reason.empty())
{
msg += msg::usrmsg::FLD_REASON;
msg += SEP_COLON;
msg += reason;
msg += SEP_COMMA;
}
msg += msg::usrmsg::FLD_INPUT_HASH;
msg += SEP_COLON;
msg += util::to_hex(input_hash);
// Ledger information is only included in 'accepted' input statuses.
if (ledger_seq_no > 0)
{
msg += SEP_COMMA;
msg += msg::usrmsg::FLD_LEDGER_SEQ_NO;
msg += SEP_COLON_NOQUOTE;
msg += std::to_string(ledger_seq_no);
msg += SEP_COMMA_NOQUOTE;
msg += msg::usrmsg::FLD_LEDGER_HASH;
msg += SEP_COLON;
msg += util::to_hex(ledger_hash.to_string_view());
}
msg += "\"}";
}
/**
* Constructs a contract read response message.
* @param msg Buffer to construct the generated json message string into.
* Message format:
* {
* "type": "contract_read_response",
* "content": "<response string>"
* }
* @param content The contract binary output content to be put in the message.
*/
void create_contract_read_response_container(std::vector<uint8_t> &msg, std::string_view content)
{
msg.reserve(content.size() + 256);
msg += "{\"";
msg += msg::usrmsg::FLD_TYPE;
msg += SEP_COLON;
msg += msg::usrmsg::MSGTYPE_CONTRACT_READ_RESPONSE;
msg += SEP_COMMA;
msg += msg::usrmsg::FLD_CONTENT;
msg += SEP_COLON_NOQUOTE;
if (is_json_string(content))
{
// Process the final string using jsoncons.
jsoncons::json jstring = content;
jsoncons::json_options options;
options.escape_all_non_ascii(true);
std::string escaped_content;
jstring.dump(escaped_content);
msg += escaped_content;
}
else
{
msg += content;
}
msg += "}";
}
/**
* Constructs a contract output container message.
* @param msg Buffer to construct the generated json message string into.
* Message format:
* {
* "type": "contract_output",
* "ledger_seq_no": <integer>,
* "ledger_hash": "<lcl hash hex>",
* "outputs": ["<output string 1>", "<output string 2>", ...], // The output order is the hash generation order.
* "output_hash": "<hex hash of user's outputs>", [output hash = hash(pubkey+all outputs for the user)]
* "hash_tree": [<hex merkle hash tree>], // Collapsed merkle tree with user's hash element marked as null.
* "unl_sig": [["<pubkey hex>", "<sig hex>"], ...] // UNL pubkeys and signatures of root hash.
* }
* @param hash This user's combined output hash. [output hash = hash(pubkey+all outputs for the user)]
* @param outputs List of outputs for the user.
* @param hash_root Root node of the collapsed merkle hash tree.
* @param unl_sig List of unl signatures issued on the root hash. (root hash = combined merkle hash of hashes of all users)
* @param lcl_seq_no Current ledger seq no.
* @param lcl_hash Current ledger hash.
*/
void create_contract_output_container(std::vector<uint8_t> &msg, std::string_view hash, const ::std::vector<std::string> &outputs,
const util::merkle_hash_node &hash_root, const std::vector<std::pair<std::string, std::string>> &unl_sig,
const uint64_t lcl_seq_no, std::string_view lcl_hash)
{
msg.reserve(1024);
msg += "{\"";
msg += msg::usrmsg::FLD_TYPE;
msg += SEP_COLON;
msg += msg::usrmsg::MSGTYPE_CONTRACT_OUTPUT;
msg += SEP_COMMA;
msg += msg::usrmsg::FLD_LEDGER_SEQ_NO;
msg += SEP_COLON_NOQUOTE;
msg += std::to_string(lcl_seq_no);
msg += SEP_COMMA_NOQUOTE;
msg += msg::usrmsg::FLD_LEDGER_HASH;
msg += SEP_COLON;
msg += util::to_hex(lcl_hash);
msg += SEP_COMMA;
msg += msg::usrmsg::FLD_OUTPUTS;
msg += "\":[";
for (int i = 0; i < outputs.size(); i++)
{
std::string_view output = outputs[i];
if (is_json_string(output))
{
// Process the final string using jsoncons.
jsoncons::json jstring = output;
jsoncons::json_options options;
options.escape_all_non_ascii(true);
std::string escaped_content;
jstring.dump(escaped_content);
msg += escaped_content;
}
else
{
msg += output;
}
if (i < outputs.size() - 1)
msg += ",";
}
msg += "],\"";
msg += msg::usrmsg::FLD_OUTPUT_HASH;
msg += SEP_COLON;
msg += util::to_hex(hash);
msg += SEP_COMMA;
msg += msg::usrmsg::FLD_HASH_TREE;
msg += SEP_COLON_NOQUOTE;
populate_output_hash_array(msg, hash_root);
msg += SEP_COMMA_NOQUOTE;
msg += msg::usrmsg::FLD_UNL_SIG;
msg += "\":[";
for (int i = 0; i < unl_sig.size(); i++)
{
const auto &sig = unl_sig[i]; // Pubkey and Signature pair.
msg += "[\"";
msg += util::to_hex(sig.first);
msg += SEP_COMMA;
msg += util::to_hex(sig.second);
msg += "\"]";
if (i < unl_sig.size() - 1)
msg += ",";
}
msg += "]}";
}
/**
* Constructs unl list container message.
* @param msg Buffer to construct the generated json message string into.
* Message format:
* {
* "type": "unl_change",
* "unl": ["<pubkey1>{[ed prefix][64 characters]}", ...] // Hex pubkey list of unl nodes.
* }
* @param unl_list The unl node pubkey list to be put in the message.
*/
void create_unl_list_container(std::vector<uint8_t> &msg, const ::std::set<std::string> &unl_list)
{
msg.reserve((69 * unl_list.size()) + 30);
msg += "{\"";
msg += msg::usrmsg::FLD_TYPE;
msg += SEP_COLON;
msg += msg::usrmsg::MSGTYPE_UNL_CHANGE;
msg += SEP_COMMA;
msg += msg::usrmsg::FLD_UNL;
msg += "\":[";
int i = 0;
for (std::string_view unl : unl_list)
{
msg += DOUBLE_QUOTE;
msg += util::to_hex(unl);
msg += DOUBLE_QUOTE;
if (i < unl_list.size() - 1)
msg += ",";
i++;
}
msg += "]}";
}
/**
* Constructs a ledger query response.
* @param msg Buffer to construct the generated json message string into.
* Message format:
* {
* "type": "ledger_query_result",
* "reply_for": "<original query id>",
* "error": "error_code" or NULL,
* "results": [{}...]
* }
* @param reply_for Original query id to associate the response with.
* @param result Query results to be sent in the response.
*/
void create_ledger_query_response(std::vector<uint8_t> &msg, std::string_view reply_for,
const ledger::query::query_result &result)
{
msg.reserve(1024);
msg += "{\"";
msg += msg::usrmsg::FLD_TYPE;
msg += SEP_COLON;
msg += msg::usrmsg::MSGTYPE_LEDGER_QUERY_RESULT;
msg += SEP_COMMA;
msg += msg::usrmsg::FLD_REPLY_FOR;
msg += SEP_COLON;
msg += reply_for;
msg += SEP_COMMA;
msg += msg::usrmsg::FLD_ERROR;
if (result.index() == 1)
{
msg += "\":null,\"";
}
else
{
msg += SEP_COLON;
msg += std::get<const char *>(result);
msg += SEP_COMMA;
}
msg += msg::usrmsg::FLD_RESULTS;
msg += "\":[";
if (result.index() == 1)
populate_ledger_query_results(msg, std::get<std::vector<ledger::ledger_record>>(result));
msg += "]}";
}
/**
* Verifies the user handshake response with the original challenge issued to the user
* and the user public key contained in the response.
*
* @param extracted_pubkeyhex The hex public key extracted from the response.
* @param extracted_protocol The protocol code extracted from the response.
* @param extracted_server_challenge Any server challenge issued by user.
* @param response The response bytes to verify. This will be parsed as json.
* Accepted response format:
* {
* "type": "user_challenge_response",
* "sig": "<hex signature of the challenge>",
* "pubkey": "<hex public key of the user>",
* "server_challenge": "<hex encoded challenge issued to server>", (max 16 bytes/32 chars)
* "protocol": "<json | bson>"
* }
* @param original_challenge The original challenge string we issued to the user.
* @return 0 if challenge response is verified. -1 if challenge not met or an error occurs.
*/
int verify_user_challenge(std::string &extracted_pubkeyhex, std::string &extracted_protocol, std::string &extracted_server_challenge,
std::string_view response, std::string_view original_challenge)
{
jsoncons::json d;
if (parse_user_message(d, response) != 0)
return -1;
// Validate msg type.
if (d[msg::usrmsg::FLD_TYPE] != msg::usrmsg::MSGTYPE_USER_CHALLENGE_RESPONSE)
{
LOG_DEBUG << "User challenge response type invalid. 'handshake_response' expected.";
return -1;
}
// Check for the 'sig' field existence.
if (!d.contains(msg::usrmsg::FLD_SIG) || !d[msg::usrmsg::FLD_SIG].is<std::string>())
{
LOG_DEBUG << "User challenge response 'challenge signature' invalid.";
return -1;
}
// Check for the 'pubkey' field existence.
if (!d.contains(msg::usrmsg::FLD_PUBKEY) || !d[msg::usrmsg::FLD_PUBKEY].is<std::string>())
{
LOG_DEBUG << "User challenge response 'public key' invalid.";
return -1;
}
// Check for optional server challenge field existence and valid value.
if (d.contains(msg::usrmsg::FLD_SERVER_CHALLENGE))
{
bool server_challenge_valid = false;
if (d[msg::usrmsg::FLD_SERVER_CHALLENGE].is<std::string>())
{
std::string_view challenge = d[msg::usrmsg::FLD_SERVER_CHALLENGE].as<std::string_view>();
if (!challenge.empty() && challenge.size() <= 32)
{
server_challenge_valid = true;
extracted_server_challenge = challenge;
}
}
if (!server_challenge_valid)
{
LOG_DEBUG << "User challenge response 'server_challenge' invalid.";
return -1;
}
}
// Check for protocol field existence and valid value.
if (!d.contains(msg::usrmsg::FLD_PROTOCOL) || !d[msg::usrmsg::FLD_PROTOCOL].is<std::string>())
{
LOG_DEBUG << "User challenge response 'protocol' invalid.";
return -1;
}
std::string_view protocolsv = d[msg::usrmsg::FLD_PROTOCOL].as<std::string_view>();
if (protocolsv != "json" && protocolsv != "bson")
{
LOG_DEBUG << "User challenge response 'protocol' type invalid.";
return -1;
}
// Verify the challenge signature. We do this last due to signature verification cost.
std::string_view pubkey_hex = d[msg::usrmsg::FLD_PUBKEY].as<std::string_view>();
const std::string pubkey_bytes = util::to_bin(pubkey_hex);
std::string_view sig_hex = d[msg::usrmsg::FLD_SIG].as<std::string_view>();
const std::string sig_bytes = util::to_bin(sig_hex);
if (pubkey_bytes.empty() || sig_bytes.empty() || crypto::verify(original_challenge, sig_bytes, pubkey_bytes) != 0)
{
LOG_DEBUG << "User challenge response signature verification failed.";
return -1;
}
extracted_pubkeyhex = pubkey_hex;
extracted_protocol = protocolsv;
return 0;
}
/**
* Parses a json message sent by a user.
* @param d Jsoncons document to which the parsed json should be loaded.
* @param message The message to parse.
* Accepted message format:
* {
* 'type': '<message type>'
* ...
* }
* @return 0 on successful parsing. -1 for failure.
*/
int parse_user_message(jsoncons::json &d, std::string_view message)
{
try
{
d = jsoncons::json::parse(message, jsoncons::strict_json_parsing());
}
catch (const std::exception &e)
{
LOG_DEBUG << "User json message parsing failed. " << e.what();
return -1;
}
// Check existence of msg type field.
if (!d.contains(msg::usrmsg::FLD_TYPE) || !d[msg::usrmsg::FLD_TYPE].is<std::string>())
{
LOG_DEBUG << "User json message 'type' missing or invalid.";
return -1;
}
return 0;
}
/**
* Extracts the message 'type' value from the json document.
*/
int extract_type(std::string &extracted_type, const jsoncons::json &d)
{
extracted_type = d[msg::usrmsg::FLD_TYPE].as<std::string>();
return 0;
}
/**
* Extracts a contract read request message sent by user.
*
* @param extracted_content The content to be passed to the contract, extracted from the message.
* @param d The json document holding the read request message.
* Accepted signed input container format:
* {
* "type": "contract_read_request",
* "content": "<any string>"
* }
* @return 0 on successful extraction. -1 for failure.
*/
int extract_read_request(std::string &extracted_content, const jsoncons::json &d)
{
if (!d.contains(msg::usrmsg::FLD_CONTENT))
{
LOG_DEBUG << "Read request required fields missing.";
return -1;
}
if (!d[msg::usrmsg::FLD_CONTENT].is<std::string>())
{
LOG_DEBUG << "Read request invalid field values.";
return -1;
}
extracted_content = d[msg::usrmsg::FLD_CONTENT].as<std::string>();
return 0;
}
/**
* Extracts a signed input container message sent by user.
*
* @param extracted_input_container The input container extracted from the message.
* @param extracted_sig The binary signature extracted from the message.
* @param d The json document holding the input container.
* Accepted signed input container format:
* {
* "type": "contract_input",
* "input_container": "<stringified json input container>",
* "sig": "<hex encoded signature of stringified input container>"
* }
* @return 0 on successful extraction. -1 for failure.
*/
int extract_signed_input_container(
std::string &extracted_input_container, std::string &extracted_sig, const jsoncons::json &d)
{
if (!d.contains(msg::usrmsg::FLD_INPUT_CONTAINER) || !d.contains(msg::usrmsg::FLD_SIG))
{
LOG_DEBUG << "User signed input required fields missing.";
return -1;
}
if (!d[msg::usrmsg::FLD_INPUT_CONTAINER].is<std::string>() || !d[msg::usrmsg::FLD_SIG].is<std::string>())
{
LOG_DEBUG << "User signed input invalid field values.";
return -1;
}
// We do not verify the signature of the content here since we need to let each node
// (including self) to verify that individually after we broadcast the NUP proposal.
extracted_input_container = d[msg::usrmsg::FLD_INPUT_CONTAINER].as<std::string>();
// Extract the hex signature and convert to binary.
const std::string_view sig_hex = d[msg::usrmsg::FLD_SIG].as<std::string_view>();
extracted_sig = util::to_bin(sig_hex);
return 0;
}
/**
* Extract the individual components of a given input container json.
* @param input The extracted input.
* @param nonce The extracted nonce.
* @param max_ledger_seq_no The extracted max ledger sequence no.
* @param contentjson The json string containing the input container message.
* {
* "input": "<any string>",
* "nonce": <integer>, // Indicates input ordering.
* "max_ledger_seq_no": <integer>
* }
* @return 0 on succesful extraction. -1 on failure.
*/
int extract_input_container(std::string &input, uint64_t &nonce, uint64_t &max_ledger_seq_no, std::string_view contentjson)
{
jsoncons::json d;
try
{
d = jsoncons::json::parse(contentjson, jsoncons::strict_json_parsing());
}
catch (const std::exception &e)
{
LOG_DEBUG << "User input container json parsing failed.";
return -1;
}
if (!d.contains(msg::usrmsg::FLD_INPUT) || !d.contains(msg::usrmsg::FLD_NONCE) || !d.contains(msg::usrmsg::FLD_MAX_LEDGER_SEQ_NO))
{
LOG_DEBUG << "User input container required fields missing.";
return -1;
}
if (!d[msg::usrmsg::FLD_INPUT].is<std::string>() || !d[msg::usrmsg::FLD_NONCE].is<uint64_t>() || !d[msg::usrmsg::FLD_MAX_LEDGER_SEQ_NO].is<uint64_t>())
{
LOG_DEBUG << "User input container invalid field values.";
return -1;
}
nonce = d[msg::usrmsg::FLD_NONCE].as<uint64_t>();
if (nonce == 0)
{
LOG_DEBUG << "Input nonce must be a positive integer.";
return -1;
}
input = d[msg::usrmsg::FLD_INPUT].as<std::string>();
max_ledger_seq_no = d[msg::usrmsg::FLD_MAX_LEDGER_SEQ_NO].as<uint64_t>();
return 0;
}
/**
* Extract query information from a ledger query request.
* @param extracted_query Extracted query criteria.
* @param extracted_id The query id.
* @param d The json document holding the query.
* Accepted query message format:
* {
* "type": "ledger_query",
* "id": "<query id>",
* "filter_by": "<filter by>",
* "params": {...}, // Params supported by the specified filter.
* "include": ["inputs", "outputs"]
* }
* @return 0 on successful extraction. -1 for failure.
*/
int extract_ledger_query(ledger::query::query_request &extracted_query, std::string &extracted_id, const jsoncons::json &d)
{
if (!d.contains(msg::usrmsg::FLD_ID) || !d.contains(msg::usrmsg::FLD_FILTER_BY) ||
!d.contains(msg::usrmsg::FLD_PARAMS) || !d.contains(msg::usrmsg::FLD_INCLUDE))
{
LOG_DEBUG << "Ledger query required fields missing.";
return -1;
}
if (!d[msg::usrmsg::FLD_ID].is<std::string>() || !d[msg::usrmsg::FLD_FILTER_BY].is<std::string>() ||
!d[msg::usrmsg::FLD_PARAMS].is_object() || !d[msg::usrmsg::FLD_INCLUDE].is_array())
{
LOG_DEBUG << "Ledger query invalid field values.";
return -1;
}
const std::string id = d[msg::usrmsg::FLD_ID].as<std::string>();
if (id.empty())
{
LOG_DEBUG << "Ledger query invalid id.";
return -1;
}
extracted_id = std::move(id);
// Detect includes.
bool inputs = false;
bool outputs = false;
for (auto &val : d[msg::usrmsg::FLD_INCLUDE].array_range())
{
if (val == msg::usrmsg::FLD_INPUTS)
inputs = true;
else if (val == msg::usrmsg::FLD_OUTPUTS)
outputs = true;
}
auto &params_field = d[msg::usrmsg::FLD_PARAMS];
if (d[msg::usrmsg::FLD_FILTER_BY] == msg::usrmsg::QUERY_FILTER_BY_SEQ_NO)
{
if (!params_field.contains(msg::usrmsg::FLD_SEQ_NO) || !params_field[msg::usrmsg::FLD_SEQ_NO].is<uint64_t>())
{
LOG_DEBUG << "Ledger query seq no filter invalid params.";
return -1;
}
extracted_query = ledger::query::seq_no_query{
params_field[msg::usrmsg::FLD_SEQ_NO].as<uint64_t>(),
inputs,
outputs};
return 0;
}
else
{
LOG_DEBUG << "Ledger query invalid filter-by criteria.";
return -1;
}
}
bool is_json_string(std::string_view content)
{
if (content.empty())
return true;
const char first = content[0];
const char last = content[content.size() - 1];
if ((first == '\"' && last == '\"') ||
(first == '{' && last == '}') ||
(first == '[' && last == ']') ||
content == "true" || content == "false")
return false;
// Check whether all characters are digits.
bool decimal_found = false;
for (const char c : content)
{
if ((c != '.' && (c < '0' || c > '9')) || (c == '.' && decimal_found)) // Not a number.
return true;
else if (c == '.') // There can only be one decimal in a proper number.
decimal_found = true;
}
return false; // Is a number.
}
void populate_output_hash_array(std::vector<uint8_t> &msg, const util::merkle_hash_node &node)
{
if (node.children.empty())
{
if (node.is_retained)
{
// The retained node is serialized as null.
// This is so the client can identify the self-hash position within the hash tree.
msg += "null";
}
else
{
msg += "\"";
msg += util::to_hex(node.hash);
msg += "\"";
}
return;
}
else
{
msg += "[";
for (auto itr = node.children.begin(); itr != node.children.end(); itr++)
{
populate_output_hash_array(msg, *itr);
if (std::next(itr) != node.children.end())
msg += ",";
}
msg += "]";
}
}
void populate_ledger_query_results(std::vector<uint8_t> &msg, const std::vector<ledger::ledger_record> &results)
{
for (size_t i = 0; i < results.size(); i++)
{
const ledger::ledger_record &ledger = results[i];
msg += "{\"";
msg += msg::usrmsg::FLD_SEQ_NO;
msg += SEP_COLON_NOQUOTE;
msg += std::to_string(ledger.seq_no);
msg += SEP_COMMA_NOQUOTE;
msg += msg::usrmsg::FLD_TIMESTAMP;
msg += SEP_COLON_NOQUOTE;
msg += std::to_string(ledger.timestamp);
msg += SEP_COMMA_NOQUOTE;
msg += msg::usrmsg::FLD_HASH;
msg += SEP_COLON;
msg += util::to_hex(ledger.ledger_hash);
msg += SEP_COMMA;
msg += msg::usrmsg::FLD_PREV_HASH;
msg += SEP_COLON;
msg += util::to_hex(ledger.prev_ledger_hash);
msg += SEP_COMMA;
msg += msg::usrmsg::FLD_STATE_HASH;
msg += SEP_COLON;
msg += util::to_hex(ledger.state_hash);
msg += SEP_COMMA;
msg += msg::usrmsg::FLD_CONFIG_HASH;
msg += SEP_COLON;
msg += util::to_hex(ledger.config_hash);
msg += SEP_COMMA;
msg += msg::usrmsg::FLD_USER_HASH;
msg += SEP_COLON;
msg += util::to_hex(ledger.user_hash);
msg += SEP_COMMA;
msg += msg::usrmsg::FLD_INPUT_HASH;
msg += SEP_COLON;
msg += util::to_hex(ledger.input_hash);
msg += SEP_COMMA;
msg += msg::usrmsg::FLD_OUTPUT_HASH;
msg += SEP_COLON;
msg += util::to_hex(ledger.output_hash);
msg += "\"";
// If raw inputs or outputs is not requested, we don't include that field at all in the response.
// Otherwise the field will always contain an array (empty array if no data).
if (ledger.inputs)
{
msg += SEP_COMMA_NOQUOTE;
msg += msg::usrmsg::FLD_INPUTS;
msg += SEP_COLON_NOQUOTE;
populate_ledger_inputs(msg, *ledger.inputs);
}
if (ledger.outputs)
{
msg += SEP_COMMA_NOQUOTE;
msg += msg::usrmsg::FLD_OUTPUTS;
msg += SEP_COLON_NOQUOTE;
populate_ledger_outputs(msg, *ledger.outputs);
}
msg += (i == (results.size() - 1) ? "}" : "},");
}
}
void populate_ledger_inputs(std::vector<uint8_t> &msg, const std::vector<ledger::ledger_user_input> &inputs)
{
msg += "[";
for (auto itr = inputs.begin(); itr != inputs.end();)
{
msg += "{\"";
msg += msg::usrmsg::FLD_PUBKEY;
msg += SEP_COLON;
msg += util::to_hex(itr->pubkey);
msg += SEP_COMMA;
msg += msg::usrmsg::FLD_HASH;
msg += SEP_COLON;
msg += util::to_hex(itr->hash);
msg += SEP_COMMA;
msg += msg::usrmsg::FLD_NONCE;
msg += SEP_COLON_NOQUOTE;
msg += std::to_string(itr->nonce);
msg += SEP_COMMA_NOQUOTE;
msg += msg::usrmsg::FLD_BLOB;
msg += SEP_COLON;
msg += util::to_hex(itr->blob);
itr++;
msg += (itr == inputs.end() ? "\"}" : "\"},");
}
msg += "]";
}
void populate_ledger_outputs(std::vector<uint8_t> &msg, const std::vector<ledger::ledger_user_output> &users)
{
msg += "[";
for (auto itr = users.begin(); itr != users.end();)
{
msg += "{\"";
msg += msg::usrmsg::FLD_PUBKEY;
msg += SEP_COLON;
msg += util::to_hex(itr->pubkey);
msg += SEP_COMMA;
msg += msg::usrmsg::FLD_HASH;
msg += SEP_COLON;
msg += util::to_hex(itr->hash);
msg += SEP_COMMA;
msg += msg::usrmsg::FLD_BLOBS;
msg += "\":[";
for (auto o_itr = itr->outputs.begin(); o_itr != itr->outputs.end();)
{
msg += "\"";
msg += util::to_hex(*o_itr);
o_itr++;
msg += (o_itr == itr->outputs.end() ? "\"" : "\",");
}
itr++;
msg += (itr == users.end() ? "]}" : "]},");
}
msg += "]";
}
} // namespace msg::usrmsg::json