#include "core_rpc_server_command_parser.h" #include "rpc/common/param_parser.hpp" #include #include #include #include #include #include namespace cryptonote::rpc { using nlohmann::json; void parse_request(ONS_RESOLVE& ons, rpc_input in) { get_values(in, "name_hash", required{ons.request.name_hash}, "type", required{ons.request.type}); } void parse_request(GET_SERVICE_NODES& sns, rpc_input in) { // Remember: key access must be in sorted order (even across get_values() calls). get_values(in, "active_only", sns.request.active_only); bool fields_dict = false; if (auto* json_in = std::get_if(&in)) { // Deprecated {"field":true, "field2":true, ...} handling: if (auto fit = json_in->find("fields"); fit != json_in->end() && fit->is_object()) { fields_dict = true; for (auto& [k, v] : fit->items()) { if (v.get()) { if (k == "all") { sns.request.fields.clear(); // Empty means all break; // The old behaviour just ignored everything else if you specified all } sns.request.fields.insert(k); } } } } if (!fields_dict) { std::vector fields; get_values(in, "fields", fields); for (const auto& f : fields) sns.request.fields.emplace(f); // If the only thing given is "all" then just clear it (as a small optimization): if (sns.request.fields.size() == 1 && *sns.request.fields.begin() == "all") sns.request.fields.clear(); } get_values(in, "limit", sns.request.limit, "poll_block_hash", ignore_empty_string{sns.request.poll_block_hash}, "service_node_pubkeys", sns.request.service_node_pubkeys); } void parse_request(START_MINING& start_mining, rpc_input in) { get_values(in, "miner_address", required{start_mining.request.miner_address}, "num_blocks", start_mining.request.num_blocks, "slow_mining", start_mining.request.slow_mining, "threads_count", start_mining.request.threads_count); } void parse_request(GET_OUTPUTS& get_outputs, rpc_input in) { get_values(in, "as_tuple", get_outputs.request.as_tuple, "get_txid", get_outputs.request.get_txid); // "outputs" is trickier: for backwards compatibility we need to accept json of: // [{"amount":0,"index":i1}, ...] // but that is incredibly wasteful and so we also want the more efficient (and we only accept // this for bt, since we don't have backwards compat to worry about): // [i1, i2, ...] bool legacy_outputs = false; if (auto* json_in = std::get_if(&in)) { if (auto outputs = json_in->find("outputs"); outputs != json_in->end() && !outputs->empty() && outputs->is_array() && outputs->front().is_object()) { legacy_outputs = true; auto& reqoi = get_outputs.request.output_indices; reqoi.reserve(outputs->size()); for (auto& o : *outputs) reqoi.push_back(o["index"].get()); } } if (!legacy_outputs) get_values(in, "outputs", get_outputs.request.output_indices); } void parse_request(GET_TRANSACTION_POOL_STATS& pstats, rpc_input in) { get_values(in, "include_unrelayed", pstats.request.include_unrelayed); } void parse_request(HARD_FORK_INFO& hfinfo, rpc_input in) { get_values(in, "height", hfinfo.request.height, "version", hfinfo.request.version); if (hfinfo.request.height && hfinfo.request.version) throw std::runtime_error{"Error: at most one of 'height'" + std::to_string(hfinfo.request.height) + "/" + std::to_string(hfinfo.request.version) + " and 'version' may be specified"}; } void parse_request(GET_TRANSACTIONS& get, rpc_input in) { // Backwards compat for old stupid "txs_hashes" input name if (auto* json_in = std::get_if(&in)) if (auto it = json_in->find("txs_hashes"); it != json_in->end()) (*json_in)["tx_hashes"] = std::move(*it); get_values(in, "data", get.request.data, "memory_pool", get.request.memory_pool, "prune", get.request.prune, "split", get.request.split, "tx_extra", get.request.tx_extra, "tx_extra_raw", get.request.tx_extra_raw, "tx_hashes", get.request.tx_hashes); if (get.request.memory_pool && !get.request.tx_hashes.empty()) throw std::runtime_error{"Error: 'memory_pool' and 'tx_hashes' are mutually exclusive"}; } void parse_request(GET_TRANSACTION_POOL& get, rpc_input in) { // Deprecated wrapper; GET_TRANSACTION_POOL is a no-member subclass of GET_TRANSACTIONS; it // works identically, except that we force `memory_pool` to true. parse_request(static_cast(get), std::move(in)); if (!get.request.tx_hashes.empty()) throw std::runtime_error{"Error: 'get_transaction_pool' does not support specifying 'tx_hashes'"}; get.request.memory_pool = true; } void parse_request(SET_LIMIT& limit, rpc_input in) { get_values(in, "limit_down", limit.request.limit_down, "limit_up", limit.request.limit_up); if (limit.request.limit_down < -1) throw std::domain_error{"limit_down must be >= -1"}; if (limit.request.limit_down < -1) throw std::domain_error{"limit_up must be >= -1"}; } void parse_request(IS_KEY_IMAGE_SPENT& spent, rpc_input in) { get_values(in, "key_images", spent.request.key_images); } void parse_request(SUBMIT_TRANSACTION& tx, rpc_input in) { if (auto* json_in = std::get_if(&in)) if (auto it = json_in->find("tx_as_hex"); it != json_in->end()) (*json_in)["tx"] = std::move(*it); auto& tx_data = tx.request.tx; get_values(in, "blink", tx.request.blink, "tx", required{tx_data}); if (tx_data.empty()) // required above will make sure it's specified, but doesn't guarantee against an empty value throw std::domain_error{"Invalid 'tx' value: cannot be empty"}; // tx can be specified as base64, hex, or binary, so try to figure out which one we have by // looking at the beginning. // // An encoded transaction always starts with the version byte, currently 0-4 (though 0 isn't // actually used), with higher future values possible. That means in hex we get something like: // `04...` and in base64 we get `B` (because the first 6 bits are 000001, and the b64 alphabet // begins at `A` for 0). Thus the first bytes, for tx versions 0 through 48, are thus: // // binary: (31 binary control characters through 0x1f ... ) (space) ! " # $ % & ' ( ) * + , - . / 0 // base64: A A A A B B B B C C C C D D D D E E E E F F F F G G G G H H H H I I I I J J J J K K K K L L L L M // hex: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 // // and so we run into the first ambiguity at version 48. Since we are currently only at version // 4 (and Oxen started at version 2) this is likely to be sufficient for an extremely long time. // // Thus our heuristic: // 'A'-'L' => base64 // '0'-'2' => hex // \x00-\x2f => bytes // anything else we reject as garbage. auto tx0 = tx_data.front(); bool good = false; if (tx0 <= 0x2f) { good = true; } else if (tx0 >= 'A' && tx0 <= 'L') { if (oxenc::is_base64(tx_data)) { auto end = oxenc::from_base64(tx_data.begin(), tx_data.end(), tx_data.begin()); tx_data.erase(end, tx_data.end()); good = true; } } else if (tx0 >= '0' && tx0 <= '2') { if (oxenc::is_hex(tx_data)) { auto end = oxenc::from_hex(tx_data.begin(), tx_data.end(), tx_data.begin()); tx_data.erase(end, tx_data.end()); good = true; } } if (!good) throw std::domain_error{"Invalid 'tx' value: expected hex, base64, or bytes"}; } void parse_request(GET_BLOCK_HASH& bh, rpc_input in) { get_values(in, "heights", bh.request.heights); if (bh.request.heights.size() > bh.MAX_HEIGHTS) throw std::domain_error{"Error: too many block heights requested at once"}; } void parse_request(GET_PEER_LIST& pl, rpc_input in) { get_values(in, "public_only", pl.request.public_only); } void parse_request(SET_LOG_LEVEL& set_log_level, rpc_input in) { get_values(in, "level", required{set_log_level.request.level}); } void parse_request(SET_LOG_CATEGORIES& set_log_categories, rpc_input in) { get_values(in, "categories", required{set_log_categories.request.categories}); } void parse_request(BANNED& banned, rpc_input in) { get_values(in, "address", required{banned.request.address}); } void parse_request(FLUSH_TRANSACTION_POOL& flush_transaction_pool, rpc_input in) { get_values(in, "txids", flush_transaction_pool.request.txids); } void parse_request(GET_COINBASE_TX_SUM& get_coinbase_tx_sum, rpc_input in) { get_values(in, "count", get_coinbase_tx_sum.request.count, "height", get_coinbase_tx_sum.request.height); } void parse_request(GET_BASE_FEE_ESTIMATE& get_base_fee_estimate, rpc_input in) { get_values(in, "grace_blocks", get_base_fee_estimate.request.grace_blocks); } void parse_request(OUT_PEERS& out_peers, rpc_input in){ get_values(in, "out_peers", out_peers.request.out_peers, "set", out_peers.request.set); } void parse_request(IN_PEERS& in_peers, rpc_input in){ get_values(in, "in_peers", in_peers.request.in_peers, "set", in_peers.request.set); } void parse_request(POP_BLOCKS& pop_blocks, rpc_input in){ get_values(in, "nblocks", required{pop_blocks.request.nblocks}); } void parse_request(LOKINET_PING& lokinet_ping, rpc_input in){ get_values(in, "ed25519_pubkey", lokinet_ping.request.ed25519_pubkey, "error", lokinet_ping.request.error, "version", required{lokinet_ping.request.version}); } void parse_request(STORAGE_SERVER_PING& storage_server_ping, rpc_input in){ get_values(in, "ed25519_pubkey", required{storage_server_ping.request.ed25519_pubkey}, "error", storage_server_ping.request.error, "https_port", required{storage_server_ping.request.https_port}, "omq_port", required{storage_server_ping.request.omq_port}, "version", required{storage_server_ping.request.version}); } void parse_request(PRUNE_BLOCKCHAIN& prune_blockchain, rpc_input in){ get_values(in, "check", prune_blockchain.request.check); } void parse_request(GET_SN_STATE_CHANGES& get_sn_state_changes, rpc_input in) { get_values(in, "end_height", get_sn_state_changes.request.end_height, "start_height", required{get_sn_state_changes.request.start_height}); } void parse_request(REPORT_PEER_STATUS& report_peer_status, rpc_input in) { get_values(in, "passed", report_peer_status.request.passed, "pubkey", report_peer_status.request.pubkey, "type", report_peer_status.request.type); } void parse_request(FLUSH_CACHE& flush_cache, rpc_input in) { get_values(in, "bad_blocks", flush_cache.request.bad_blocks, "bad_txs", flush_cache.request.bad_txs); } void parse_request(GET_LAST_BLOCK_HEADER& get_last_block_header, rpc_input in) { get_values(in, "fill_pow_hash", get_last_block_header.request.fill_pow_hash, "get_tx_hashes", get_last_block_header.request.get_tx_hashes); } void parse_request(GET_BLOCK_HEADER_BY_HASH& get_block_header_by_hash, rpc_input in) { get_values(in, "fill_pow_hash", get_block_header_by_hash.request.fill_pow_hash, "get_tx_hashes", get_block_header_by_hash.request.get_tx_hashes, "hash", get_block_header_by_hash.request.hash, "hashes", get_block_header_by_hash.request.hashes); } void parse_request(SET_BANS& set_bans, rpc_input in) { get_values(in, "ban", required{set_bans.request.ban}, "host", required{set_bans.request.host}, "seconds", required{set_bans.request.seconds}); } void parse_request(GET_STAKING_REQUIREMENT& get_staking_requirement, rpc_input in) { get_values(in, "height", get_staking_requirement.request.height); } void parse_request(GET_BLOCK_HEADERS_RANGE& get_block_headers_range, rpc_input in) { get_values(in, "end_height", get_block_headers_range.request.end_height, "fill_pow_hash", get_block_headers_range.request.fill_pow_hash, "get_tx_hashes", get_block_headers_range.request.get_tx_hashes, "start_height", get_block_headers_range.request.start_height); } void parse_request(GET_BLOCK_HEADER_BY_HEIGHT& get_block_header_by_height, rpc_input in) { get_values(in, "fill_pow_hash", get_block_header_by_height.request.fill_pow_hash, "get_tx_hashes", get_block_header_by_height.request.get_tx_hashes, "height", get_block_header_by_height.request.height, "heights", get_block_header_by_height.request.heights); } void parse_request(GET_BLOCK& get_block, rpc_input in) { get_values(in, "fill_pow_hash", get_block.request.fill_pow_hash, "hash", get_block.request.hash, "height", get_block.request.height); } void parse_request(GET_OUTPUT_HISTOGRAM& get_output_histogram, rpc_input in) { get_values(in, "amounts", get_output_histogram.request.amounts, "max_count", get_output_histogram.request.max_count, "min_count", get_output_histogram.request.min_count, "recent_cutoff", get_output_histogram.request.recent_cutoff, "unlocked", get_output_histogram.request.unlocked); } void parse_request(GET_ACCRUED_BATCHED_EARNINGS& get_accrued_batched_earnings, rpc_input in) { get_values(in, "addresses", get_accrued_batched_earnings.request.addresses); } void parse_request(ONS_OWNERS_TO_NAMES& ons_owners_to_names, rpc_input in) { get_values(in, "entries", required{ons_owners_to_names.request.entries}, "include_expired", ons_owners_to_names.request.include_expired); } void parse_request(GET_QUORUM_STATE& qs, rpc_input in) { get_values(in, "end_height", qs.request.end_height, "quorum_type", qs.request.quorum_type, "start_height", qs.request.start_height); if (qs.request.quorum_type) { if (*qs.request.quorum_type == 255) // backwards-compat magic value qs.request.quorum_type = std::nullopt; else if (*qs.request.quorum_type > tools::enum_count) throw std::domain_error{ "Quorum type specifies an invalid value: "_format(*qs.request.quorum_type)}; } } void parse_request(GET_CHECKPOINTS& getcp, rpc_input in) { get_values(in, "count", getcp.request.count, "end_height", getcp.request.end_height, "start_height", getcp.request.start_height); } void parse_request(GET_SERVICE_NODE_REGISTRATION_CMD_RAW& cmd, rpc_input in) { get_values(in, "args", required{cmd.request.args}, "make_friendly", cmd.request.make_friendly, "staking_requirement", required{cmd.request.staking_requirement}); } }