#include "../../conf.hpp" #include "../../p2p/p2p.hpp" #include "../../pchheader.hpp" #include "../../util/version.hpp" #include "../../util/util.hpp" #include "../../hplog.hpp" #include "../../ledger/ledger_query.hpp" #include "../usrmsg_common.hpp" #include "usrmsg_bson.hpp" namespace msg::usrmsg::bson { /** * Constructs a status response message. * @param msg Buffer to construct the generated bson message into. * Message format: * { * "type": "stat_response", * "ledger_seq_no": , * "ledger_hash": * } */ constexpr const size_t MAX_KNOWN_PEERS_INFO = 10; void create_status_response(std::vector &msg, const uint64_t lcl_seq_no, std::string_view lcl_hash) { jsoncons::bson::bson_bytes_encoder encoder(msg); encoder.begin_object(); encoder.key(msg::usrmsg::FLD_TYPE); encoder.string_value(msg::usrmsg::MSGTYPE_STAT_RESPONSE); encoder.key(msg::usrmsg::FLD_HP_VERSION); encoder.string_value(version::HP_VERSION); encoder.key(msg::usrmsg::FLD_LEDGER_SEQ_NO); encoder.int64_value(lcl_seq_no); encoder.key(msg::usrmsg::FLD_LEDGER_HASH); encoder.byte_string_value(lcl_hash); encoder.key(msg::usrmsg::FLD_ROUND_TIME); encoder.uint64_value(conf::cfg.contract.roundtime); encoder.key(msg::usrmsg::FLD_CONTARCT_EXECUTION_ENABLED); encoder.bool_value(conf::cfg.contract.execute); encoder.key(msg::usrmsg::FLD_READ_REQUESTS_ENABLED); encoder.bool_value(conf::cfg.user.concurrent_read_reqeuests != 0); encoder.key(msg::usrmsg::FLD_IS_FULL_HISTORY_NODE); encoder.bool_value(conf::cfg.node.history == conf::HISTORY::FULL); encoder.key(msg::usrmsg::FLD_CURRENT_UNL); encoder.begin_array(); for (std::string_view unl : conf::cfg.contract.unl) encoder.byte_string_value(unl); encoder.end_array(); encoder.key(msg::usrmsg::FLD_PEERS); { 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; encoder.begin_array(); // 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++) encoder.string_value(peer->second->known_ipport->host_address + ":" + std::to_string(peer->second->known_ipport->port)); encoder.end_array(); } encoder.end_object(); encoder.flush(); } /** * Constructs a contract input status message. * @param msg Buffer to construct the generated bson message 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) { jsoncons::bson::bson_bytes_encoder encoder(msg); encoder.begin_object(); encoder.key(msg::usrmsg::FLD_TYPE); encoder.string_value(msg::usrmsg::MSGTYPE_CONTRACT_INPUT_STATUS); encoder.key(msg::usrmsg::FLD_STATUS); encoder.string_value(status); // Reject reason is only included for rejected inputs. if (!reason.empty()) { encoder.key(msg::usrmsg::FLD_REASON); encoder.string_value(reason); } encoder.key(msg::usrmsg::FLD_INPUT_HASH); encoder.byte_string_value(input_hash); // Ledger information is only included in 'accepted' input statuses. if (ledger_seq_no > 0) { encoder.key(msg::usrmsg::FLD_LEDGER_SEQ_NO); encoder.uint64_value(ledger_seq_no); encoder.key(msg::usrmsg::FLD_LEDGER_HASH); encoder.byte_string_value(ledger_hash.to_string_view()); } encoder.end_object(); encoder.flush(); } /** * Constructs a contract read response message. * @param msg Buffer to construct the generated bson message 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) { jsoncons::bson::bson_bytes_encoder encoder(msg); encoder.begin_object(); encoder.key(msg::usrmsg::FLD_TYPE); encoder.string_value(msg::usrmsg::MSGTYPE_CONTRACT_READ_RESPONSE); encoder.key(msg::usrmsg::FLD_CONTENT); encoder.byte_string_value(content); encoder.end_object(); encoder.flush(); } /** * Constructs a contract output container message. * @param msg Buffer to construct the generated bson message 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": [["", ""], ...] // Binary 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 for this round. * @param unl_sig List of unl signatures issued on the root hash. (root hash = merkle root 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) { jsoncons::bson::bson_bytes_encoder encoder(msg); encoder.begin_object(); encoder.key(msg::usrmsg::FLD_TYPE); encoder.string_value(msg::usrmsg::MSGTYPE_CONTRACT_OUTPUT); encoder.key(msg::usrmsg::FLD_LEDGER_SEQ_NO); encoder.int64_value(lcl_seq_no); encoder.key(msg::usrmsg::FLD_LEDGER_HASH); encoder.byte_string_value(lcl_hash); encoder.key(msg::usrmsg::FLD_OUTPUTS); encoder.begin_array(); for (int i = 0; i < outputs.size(); i++) encoder.byte_string_value(outputs[i]); encoder.end_array(); encoder.key(msg::usrmsg::FLD_OUTPUT_HASH); encoder.byte_string_value(hash); encoder.key(msg::usrmsg::FLD_HASH_TREE); populate_output_hash_array(encoder, hash_root); encoder.key(msg::usrmsg::FLD_UNL_SIG); encoder.begin_array(); for (const auto &[pubkey, sig] : unl_sig) { encoder.begin_array(); encoder.byte_string_value(pubkey); encoder.byte_string_value(sig); encoder.end_array(); } encoder.end_array(); encoder.end_object(); encoder.flush(); } /** * Constructs unl list container message. * @param msg Buffer to construct the generated bson message string into. * Message format: * { * "type": "unl_change", * "unl": ["{[1byte(11101101) prefix][32byte]}", ...] // Binary 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) { jsoncons::bson::bson_bytes_encoder encoder(msg); encoder.begin_object(); encoder.key(msg::usrmsg::FLD_TYPE); encoder.string_value(msg::usrmsg::MSGTYPE_UNL_CHANGE); encoder.key(msg::usrmsg::FLD_UNL); encoder.begin_array(); for (std::string_view unl : unl_list) encoder.byte_string_value(unl); encoder.end_array(); encoder.end_object(); encoder.flush(); } /** * 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) { jsoncons::bson::bson_bytes_encoder encoder(msg); encoder.begin_object(); encoder.key(msg::usrmsg::FLD_TYPE); encoder.string_value(msg::usrmsg::MSGTYPE_LEDGER_QUERY_RESULT); encoder.key(msg::usrmsg::FLD_REPLY_FOR); encoder.string_value(reply_for); encoder.key(msg::usrmsg::FLD_ERROR); if (result.index() == 1) encoder.null_value(); else encoder.string_value(std::get(result)); encoder.key(msg::usrmsg::FLD_RESULTS); encoder.begin_array(); populate_ledger_query_results(encoder, std::get>(result)); encoder.end_array(); encoder.end_object(); encoder.flush(); } /** * Parses a bson message sent by a user. * @param d BSON document to which the parsed bson 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::ojson &d, std::string_view message) { try { d = jsoncons::bson::decode_bson(message); } catch (const std::exception &e) { LOG_DEBUG << "User bson message parsing failed."; return -1; } if (!d.contains(FLD_TYPE) || !d[FLD_TYPE].is_string()) { LOG_DEBUG << "User bson message 'type' missing or invalid."; return -1; } return 0; } /** * Extracts the message 'type' value from the bson document. */ int extract_type(std::string &extracted_type, const jsoncons::ojson &d) { extracted_type = d[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 bson 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::ojson &d) { if (!d.contains(msg::usrmsg::FLD_CONTENT) || !d[msg::usrmsg::FLD_CONTENT].is_byte_string_view()) { LOG_DEBUG << "Read request 'content' field missing or invalid."; return -1; } const jsoncons::byte_string_view &bsv = d[msg::usrmsg::FLD_CONTENT].as_byte_string_view(); extracted_content = std::string_view(reinterpret_cast(bsv.data()), bsv.size()); 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 bson 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::ojson &d) { if (!d.contains(msg::usrmsg::FLD_INPUT_CONTAINER) || !d.contains(msg::usrmsg::FLD_SIG) || !d[msg::usrmsg::FLD_INPUT_CONTAINER].is_byte_string_view() || !d[msg::usrmsg::FLD_SIG].is_byte_string_view()) { LOG_DEBUG << "User signed input required fields missing or invalid."; return -1; } const jsoncons::byte_string_view &bsv1 = d[msg::usrmsg::FLD_INPUT_CONTAINER].as_byte_string_view(); extracted_input_container = std::string_view(reinterpret_cast(bsv1.data()), bsv1.size()); const jsoncons::byte_string_view &bsv2 = d[msg::usrmsg::FLD_SIG].as_byte_string_view(); extracted_sig = std::string_view(reinterpret_cast(bsv2.data()), bsv2.size()); return 0; } /** * Extract the individual components of a given input container bson. * @param input The extracted input. * @param nonce The extracted nonce. * @param max_ledger_seq_no The extracted max ledger sequence no. * @param contentjson The bson 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 contentbson) { jsoncons::ojson d; try { d = jsoncons::bson::decode_bson(contentbson); } catch (const std::exception &e) { LOG_DEBUG << "User input container bson 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 or invalid."; return -1; } if (!d[msg::usrmsg::FLD_INPUT].is_byte_string_view() || !d[msg::usrmsg::FLD_NONCE].is_uint64() || !d[msg::usrmsg::FLD_MAX_LEDGER_SEQ_NO].is_uint64()) { 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; } max_ledger_seq_no = d[msg::usrmsg::FLD_MAX_LEDGER_SEQ_NO].as(); const jsoncons::byte_string_view &bsv = d[msg::usrmsg::FLD_INPUT].as_byte_string_view(); input = std::string_view(reinterpret_cast(bsv.data()), bsv.size()); 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 bson 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::ojson &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; } } void populate_output_hash_array(jsoncons::bson::bson_bytes_encoder &encoder, const util::merkle_hash_node &node) { if (node.children.empty()) { // The retained node is serialized as null. // This is so the client can identify the self-hash position within the hash tree. if (node.is_retained) encoder.null_value(); else encoder.byte_string_value(node.hash); return; } else { encoder.begin_array(); for (const auto &child : node.children) populate_output_hash_array(encoder, child); encoder.end_array(); } } void populate_ledger_query_results(jsoncons::bson::bson_bytes_encoder &encoder, const std::vector &results) { for (const ledger::ledger_record &ledger : results) { encoder.begin_object(); encoder.key(msg::usrmsg::FLD_SEQ_NO); encoder.uint64_value(ledger.seq_no); encoder.key(msg::usrmsg::FLD_TIMESTAMP); encoder.uint64_value(ledger.timestamp); encoder.key(msg::usrmsg::FLD_HASH); encoder.byte_string_value(ledger.ledger_hash); encoder.key(msg::usrmsg::FLD_PREV_HASH); encoder.byte_string_value(ledger.prev_ledger_hash); encoder.key(msg::usrmsg::FLD_STATE_HASH); encoder.byte_string_value(ledger.state_hash); encoder.key(msg::usrmsg::FLD_CONFIG_HASH); encoder.byte_string_value(ledger.config_hash); encoder.key(msg::usrmsg::FLD_USER_HASH); encoder.byte_string_value(ledger.user_hash); encoder.key(msg::usrmsg::FLD_INPUT_HASH); encoder.byte_string_value(ledger.input_hash); encoder.key(msg::usrmsg::FLD_OUTPUT_HASH); encoder.byte_string_value(ledger.output_hash); // 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) { encoder.key(msg::usrmsg::FLD_INPUTS); populate_ledger_inputs(encoder, *ledger.inputs); } if (ledger.outputs) { encoder.key(msg::usrmsg::FLD_OUTPUTS); populate_ledger_outputs(encoder, *ledger.outputs); } encoder.end_object(); } } void populate_ledger_inputs(jsoncons::bson::bson_bytes_encoder &encoder, const std::vector &inputs) { encoder.begin_array(); for (const ledger::ledger_user_input &inp : inputs) { encoder.begin_object(); encoder.key(msg::usrmsg::FLD_PUBKEY); encoder.byte_string_value(inp.pubkey); encoder.key(msg::usrmsg::FLD_HASH); encoder.byte_string_value(inp.hash); encoder.key(msg::usrmsg::FLD_NONCE); encoder.uint64_value(inp.nonce); encoder.key(msg::usrmsg::FLD_BLOB); encoder.byte_string_value(inp.blob); encoder.end_object(); } encoder.end_array(); } void populate_ledger_outputs(jsoncons::bson::bson_bytes_encoder &encoder, const std::vector &users) { encoder.begin_array(); for (const ledger::ledger_user_output &user : users) { encoder.begin_object(); encoder.key(msg::usrmsg::FLD_PUBKEY); encoder.byte_string_value(user.pubkey); encoder.key(msg::usrmsg::FLD_HASH); encoder.byte_string_value(user.hash); encoder.key(msg::usrmsg::FLD_BLOBS); encoder.begin_array(); for (const std::string &output : user.outputs) encoder.byte_string_value(output); encoder.end_array(); encoder.end_object(); } encoder.end_array(); } } // namespace msg::usrmsg::bson