oxen-core/src/rpc/core_rpc_server_command_par...

398 lines
15 KiB
C++

#include "core_rpc_server_command_parser.h"
#include "rpc/common/param_parser.hpp"
#include <chrono>
#include <oxenc/bt_serialize.h>
#include <oxenc/base64.h>
#include <oxenc/hex.h>
#include <type_traits>
#include <utility>
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<json>(&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<bool>()) {
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<std::string_view> 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<json>(&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<uint64_t>());
}
}
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<json>(&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_TRANSACTIONS&>(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<json>(&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<service_nodes::quorum_type>)
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});
}
}