#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 &operator+=(std::vector &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": "", * "type": "user_challenge", * "contract_id": "", * "contract_version": "", * "challenge": "" * } * @param challenge_bytes Buffer to construct the generated challenge bytes into. */ void create_user_challenge(std::vector &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": "", * "pubkey": "", * "unl": [] * } * @param original_challenge Original challenge issued by the user. */ void create_server_challenge_response(std::vector &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 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": , * "ledger_hash": "" * } */ void create_status_response(std::vector &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 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": "", * "reason": "", * "input_hash": "", * "ledger_seq_no": , * "ledger_hash": "" * } * @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 &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": "" * } * @param content The contract binary output content to be put in the message. */ void create_contract_read_response_container(std::vector &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": , * "ledger_hash": "", * "outputs": ["", "", ...], // The output order is the hash generation order. * "output_hash": "", [output hash = hash(pubkey+all outputs for the user)] * "hash_tree": [], // Collapsed merkle tree with user's hash element marked as null. * "unl_sig": [["", ""], ...] // 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 &msg, std::string_view hash, const ::std::vector &outputs, const util::merkle_hash_node &hash_root, const std::vector> &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": ["{[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 &msg, const ::std::set &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": "", * "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 &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(result); msg += SEP_COMMA; } msg += msg::usrmsg::FLD_RESULTS; msg += "\":["; if (result.index() == 1) populate_ledger_query_results(msg, std::get>(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": "", * "pubkey": "", * "server_challenge": "", (max 16 bytes/32 chars) * "protocol": "" * } * @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()) { 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()) { 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_view challenge = d[msg::usrmsg::FLD_SERVER_CHALLENGE].as(); 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()) { LOG_DEBUG << "User challenge response 'protocol' invalid."; return -1; } std::string_view protocolsv = d[msg::usrmsg::FLD_PROTOCOL].as(); 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(); const std::string pubkey_bytes = util::to_bin(pubkey_hex); std::string_view sig_hex = d[msg::usrmsg::FLD_SIG].as(); 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': '' * ... * } * @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()) { 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(); 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": "" * } * @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()) { LOG_DEBUG << "Read request invalid field values."; return -1; } extracted_content = d[msg::usrmsg::FLD_CONTENT].as(); 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": "", * "sig": "" * } * @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() || !d[msg::usrmsg::FLD_SIG].is()) { 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(); // Extract the hex signature and convert to binary. const std::string_view sig_hex = d[msg::usrmsg::FLD_SIG].as(); 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": "", * "nonce": , // Indicates input ordering. * "max_ledger_seq_no": * } * @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() || !d[msg::usrmsg::FLD_NONCE].is() || !d[msg::usrmsg::FLD_MAX_LEDGER_SEQ_NO].is()) { LOG_DEBUG << "User input container invalid field values."; return -1; } nonce = d[msg::usrmsg::FLD_NONCE].as(); if (nonce == 0) { LOG_DEBUG << "Input nonce must be a positive integer."; return -1; } input = d[msg::usrmsg::FLD_INPUT].as(); max_ledger_seq_no = d[msg::usrmsg::FLD_MAX_LEDGER_SEQ_NO].as(); 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": "", * "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() || !d[msg::usrmsg::FLD_FILTER_BY].is() || !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(); 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 ¶ms_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()) { 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(), 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 &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 &msg, const std::vector &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 &msg, const std::vector &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 &msg, const std::vector &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