Merge pull request #1314 from loki-project/dev

Dev -> stable for 8.1.1 release
This commit is contained in:
Jason Rhinelander 2020-10-11 22:51:33 -03:00 committed by GitHub
commit db1b2d9501
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 341 additions and 194 deletions

View file

@ -57,7 +57,7 @@ cmake_minimum_required(VERSION 3.10)
message(STATUS "CMake version ${CMAKE_VERSION}")
project(loki
VERSION 8.1.0
VERSION 8.1.1
LANGUAGES CXX C)
set(LOKI_RELEASE_CODENAME "Salty Saga")
@ -66,7 +66,7 @@ set(LOKI_RELEASE_CODENAME "Salty Saga")
# empty string on `stable`, "-dev" on the `dev` branch, and can be set externally (via cmake
# arguments) where it makes sense to take some other branch release with an extra value.
if(NOT DEFINED LOKI_RELEASE_SUFFIX)
set(LOKI_RELEASE_SUFFIX "")
set(LOKI_RELEASE_SUFFIX "-dev")
endif()
if(POLICY CMP0079)

View file

@ -776,9 +776,6 @@ static bool check_condition(bool condition, std::string* reason, T&&... args) {
bool validate_lns_name(mapping_type type, std::string name, std::string *reason)
{
std::stringstream err_stream;
LOKI_DEFER { if (reason) *reason = err_stream.str(); };
bool const is_lokinet = is_lokinet_type(type);
size_t max_name_len = 0;
@ -790,7 +787,12 @@ bool validate_lns_name(mapping_type type, std::string name, std::string *reason)
else if (type == mapping_type::wallet) max_name_len = lns::WALLET_NAME_MAX;
else
{
if (reason) err_stream << "LNS type=" << type << ", specifies unhandled mapping type in name validation";
if (reason)
{
std::stringstream err_stream;
err_stream << "LNS type=" << mapping_type_str(type) << ", specifies unhandled mapping type in name validation";
*reason = err_stream.str();
}
return false;
}
@ -2235,9 +2237,9 @@ std::vector<mapping_record> name_system_db::get_mappings_by_owners(std::vector<g
std::vector<std::variant<blob_view, uint64_t>> bind;
// Generate string statement
{
constexpr auto SQL_WHERE_OWNER = R"(WHERE "o1"."address" IN ()"sv;
constexpr auto SQL_WHERE_OWNER = R"(WHERE ("o1"."address" IN ()"sv;
constexpr auto SQL_OR_BACKUP_OWNER = R"() OR "o2"."address" IN ()"sv;
constexpr auto SQL_SUFFIX = ")"sv;
constexpr auto SQL_SUFFIX = "))"sv;
std::string placeholders;
placeholders.reserve(3*owners.size());

View file

@ -105,17 +105,17 @@ struct mapping_value
};
inline std::ostream &operator<<(std::ostream &os, mapping_value const &v) { return os << lokimq::to_hex(v.to_view()); }
inline char const *mapping_type_str(mapping_type type)
inline std::string_view mapping_type_str(mapping_type type)
{
switch(type)
{
case mapping_type::lokinet: return "lokinet"; // general type stored in the database; 1 year when in a purchase tx
case mapping_type::lokinet_2years: return "lokinet_2years"; // Only used in a buy tx, not in the DB
case mapping_type::lokinet_5years: return "lokinet_5years"; // "
case mapping_type::lokinet_10years: return "lokinet_10years"; // "
case mapping_type::session: return "session";
case mapping_type::wallet: return "wallet";
default: assert(false); return "xx_unhandled_type";
case mapping_type::lokinet: return "lokinet"sv; // general type stored in the database; 1 year when in a purchase tx
case mapping_type::lokinet_2years: return "lokinet_2years"sv; // Only used in a buy tx, not in the DB
case mapping_type::lokinet_5years: return "lokinet_5years"sv; // "
case mapping_type::lokinet_10years: return "lokinet_10years"sv; // "
case mapping_type::session: return "session"sv;
case mapping_type::wallet: return "wallet"sv;
default: assert(false); return "xx_unhandled_type"sv;
}
}
inline std::ostream &operator<<(std::ostream &os, mapping_type type) { return os << mapping_type_str(type); }

View file

@ -722,6 +722,15 @@ void pulse::handle_message(void *quorumnet_state, pulse::message const &msg)
cryptonote::quorumnet_pulse_relay_message_to_quorum(quorumnet_state, msg, context.prepare_for_round.quorum, context.prepare_for_round.participant == sn_type::producer);
}
// TODO(doyle): Update pulse::perpare_for_round with this function after the hard fork and sanity check it on testnet.
bool pulse::convert_time_to_round(pulse::time_point const &time, pulse::time_point const &r0_timestamp, uint8_t *round)
{
auto const time_since_round_started = time <= r0_timestamp ? std::chrono::seconds(0) : (time - r0_timestamp);
size_t result_usize = time_since_round_started / service_nodes::PULSE_ROUND_TIME;
if (round) *round = static_cast<uint8_t>(result_usize);
return result_usize <= 255;
}
bool pulse::get_round_timings(cryptonote::Blockchain const &blockchain, uint64_t block_height, uint64_t prev_timestamp, pulse::timings &times)
{
times = {};

View file

@ -99,6 +99,11 @@ struct timings
pulse::time_point miner_fallback_timestamp;
};
// Calculate the current Pulse round active depending on the 'time' elapsed since round 0 started for a block.
// r0_timestamp: The timestamp that round 0 starts at for the desired block (this timestamp can be calculated via 'pulse::get_round_timings').
// round: (Optional) Set to the round that is currently active when the function returns true.
// return: False when enough 'time' has elapsed such that Pulse round has overflowed 255 and Pulse blocks are no longer possible to generate.
bool convert_time_to_round(pulse::time_point const &time, pulse::time_point const &r0_timestamp, uint8_t *round);
bool get_round_timings(cryptonote::Blockchain const &blockchain, uint64_t height, uint64_t prev_timestamp, pulse::timings &times);
} // namespace pulse

View file

@ -1768,6 +1768,12 @@ namespace service_nodes
return get_pulse_entropy_for_next_block(db, top_block, pulse_round);
}
std::vector<crypto::hash> get_pulse_entropy_for_next_block(cryptonote::BlockchainDB const &db,
uint8_t pulse_round)
{
return get_pulse_entropy_for_next_block(db, db.get_top_block(), pulse_round);
}
service_nodes::quorum generate_pulse_quorum(cryptonote::network_type nettype,
crypto::public_key const &block_leader,
uint8_t hf_version,

View file

@ -687,6 +687,9 @@ namespace service_nodes
// The pulse entropy is generated for the next block after the top_block passed in.
std::vector<crypto::hash> get_pulse_entropy_for_next_block(cryptonote::BlockchainDB const &db, cryptonote::block const &top_block, uint8_t pulse_round);
std::vector<crypto::hash> get_pulse_entropy_for_next_block(cryptonote::BlockchainDB const &db, crypto::hash const &top_hash, uint8_t pulse_round);
// Same as above, but uses the current blockchain top block and defaults to round 0 if not
// specified.
std::vector<crypto::hash> get_pulse_entropy_for_next_block(cryptonote::BlockchainDB const &db, uint8_t pulse_round = 0);
payout service_node_info_to_payout(crypto::public_key const &key, service_node_info const &info);

View file

@ -76,23 +76,26 @@ static uint16_t parse_public_rpc_port(const boost::program_options::variables_ma
if (!public_node)
return 0;
std::string rpc_port_str;
const auto &restricted_rpc_port = cryptonote::rpc::http_server::arg_rpc_restricted_bind_port;
if (!command_line::is_arg_defaulted(vm, restricted_rpc_port))
rpc_port_str = command_line::get_arg(vm, restricted_rpc_port);
else if (command_line::get_arg(vm, cryptonote::rpc::http_server::arg_restricted_rpc))
rpc_port_str = command_line::get_arg(vm, cryptonote::rpc::http_server::arg_rpc_bind_port);
else
throw std::runtime_error("restricted RPC mode is required for --" + std::string{public_node_arg.name});
uint16_t rpc_port = 0;
const auto &arg_rpc_restricted_bind_port = cryptonote::rpc::http_server::arg_rpc_restricted_bind_port;
const auto &arg_rpc_bind_port = cryptonote::rpc::http_server::arg_rpc_bind_port;
const auto &arg_restricted_rpc = cryptonote::rpc::http_server::arg_restricted_rpc;
uint16_t rpc_port;
if (!epee::string_tools::get_xtype_from_string(rpc_port, rpc_port_str))
throw std::runtime_error("invalid RPC port " + rpc_port_str);
bool specified_restricted_port = !command_line::is_arg_defaulted(vm, arg_rpc_restricted_bind_port);
if (specified_restricted_port)
rpc_port = command_line::get_arg(vm, arg_rpc_restricted_bind_port);
else if (command_line::get_arg(vm, arg_restricted_rpc))
rpc_port = command_line::get_arg(vm, arg_rpc_bind_port);
else
throw std::runtime_error("Restricted RPC is required for --"s + public_node_arg.name + ", specify a restricted port via --" + arg_rpc_restricted_bind_port.name + " or restrict server via --" + arg_restricted_rpc.name);
if (rpc_port == 0)
throw std::runtime_error("Please specify a non-zero port for restricted rpc via --"s + (specified_restricted_port ? arg_rpc_restricted_bind_port.name : arg_rpc_bind_port.name));
const auto rpc_bind_address = command_line::get_arg(vm, cryptonote::rpc_args::descriptors().rpc_bind_ip);
const auto address = net::get_network_address(rpc_bind_address, rpc_port);
if (!address)
throw std::runtime_error("failed to parse RPC bind address");
throw std::runtime_error("Failed to parse RPC bind address "s + rpc_bind_address + ":" + std::to_string(rpc_port));
if (address->get_zone() != epee::net_utils::zone::public_)
throw std::runtime_error(std::string(zone_to_string(address->get_zone()))
+ " network zone is not supported, please check RPC server bind address");

View file

@ -39,6 +39,7 @@
#include <type_traits>
#include <variant>
#include <lokimq/base64.h>
#include "crypto/crypto.h"
#include "cryptonote_basic/tx_extra.h"
#include "cryptonote_core/loki_name_system.h"
#include "cryptonote_core/pulse.h"
@ -2644,12 +2645,33 @@ namespace cryptonote { namespace rpc {
throw rpc_error{ERROR_WRONG_PARAM,
"Quorum type specifies an invalid value: " + std::to_string(req.quorum_type)};
auto requested_type = [&req](service_nodes::quorum_type type) {
return req.quorum_type == GET_QUORUM_STATE::ALL_QUORUMS_SENTINEL_VALUE ||
req.quorum_type == static_cast<uint8_t>(type);
};
bool latest = false;
uint64_t latest_ob = 0, latest_cp = 0, latest_bl = 0;
uint64_t start = req.start_height, end = req.end_height;
uint64_t curr_height = m_core.get_blockchain_storage().get_current_blockchain_height();
if (start == GET_QUORUM_STATE::HEIGHT_SENTINEL_VALUE &&
end == GET_QUORUM_STATE::HEIGHT_SENTINEL_VALUE)
{
start = m_core.get_blockchain_storage().get_current_blockchain_height() - 1;
end = start + 1;
latest = true;
// Our start block for the latest quorum of each type depends on the type being requested:
// obligations: top block
// checkpoint: last block with height divisible by CHECKPOINT_INTERVAL (=4)
// blink: last block with height divisible by BLINK_QUORUM_INTERVAL (=5)
// pulse: current height (i.e. top block height + 1)
uint64_t top_height = curr_height - 1;
latest_ob = top_height;
latest_cp = std::min(start, top_height - top_height % service_nodes::CHECKPOINT_INTERVAL);
latest_bl = std::min(start, top_height - top_height % service_nodes::BLINK_QUORUM_INTERVAL);
if (requested_type(service_nodes::quorum_type::checkpointing))
start = std::min(start, latest_cp);
if (requested_type(service_nodes::quorum_type::blink))
start = std::min(start, latest_bl);
end = curr_height;
}
else if (start == GET_QUORUM_STATE::HEIGHT_SENTINEL_VALUE)
{
@ -2663,16 +2685,14 @@ namespace cryptonote { namespace rpc {
else
{
if (end > start) end++;
else
{
if (end != 0)
end--;
}
else if (end != 0) end--;
}
uint64_t curr_height = m_core.get_blockchain_storage().get_current_blockchain_height();
start = std::min(curr_height, start);
end = std::min(curr_height, end);
// We can also provide the pulse quorum for the current block being produced, so if asked for
// that make a note.
bool add_curr_pulse = (latest || end > curr_height) && requested_type(service_nodes::quorum_type::pulse);
end = std::min(curr_height, end);
uint64_t count = (start > end) ? start - end : end - start;
if (!context.admin && count > GET_QUORUM_STATE::MAX_COUNT)
@ -2700,18 +2720,21 @@ namespace cryptonote { namespace rpc {
for (int quorum_int = (int)start_quorum_iterator; quorum_int <= (int)end_quorum_iterator; quorum_int++)
{
auto type = static_cast<service_nodes::quorum_type>(quorum_int);
if (latest)
{ // Latest quorum requested, so skip if this is isn't the latest height for *this* quorum type
if (type == service_nodes::quorum_type::obligations && height != latest_ob) continue;
if (type == service_nodes::quorum_type::checkpointing && height != latest_cp) continue;
if (type == service_nodes::quorum_type::blink && height != latest_bl) continue;
if (type == service_nodes::quorum_type::pulse) continue;
}
if (std::shared_ptr<const service_nodes::quorum> quorum = m_core.get_quorum(type, height, true /*include_old*/))
{
GET_QUORUM_STATE::quorum_for_height entry = {};
auto& entry = res.quorums.emplace_back();
entry.height = height;
entry.quorum_type = static_cast<uint8_t>(quorum_int);
entry.quorum.validators = hexify(quorum->validators);
entry.quorum.workers = hexify(quorum->workers);
entry.quorum.validators.reserve(quorum->validators.size());
entry.quorum.workers.reserve(quorum->workers.size());
for (crypto::public_key const &key : quorum->validators) entry.quorum.validators.push_back(epee::string_tools::pod_to_hex(key));
for (crypto::public_key const &key : quorum->workers) entry.quorum.workers.push_back(epee::string_tools::pod_to_hex(key));
res.quorums.push_back(entry);
at_least_one_succeeded = true;
}
}
@ -2721,6 +2744,34 @@ namespace cryptonote { namespace rpc {
else height--;
}
if (uint8_t hf_version; add_curr_pulse
&& (hf_version = m_core.get_hard_fork_version(curr_height)) >= network_version_16_pulse)
{
cryptonote::Blockchain const &blockchain = m_core.get_blockchain_storage();
cryptonote::block_header const &top_header = blockchain.get_db().get_block_header_from_height(curr_height - 1);
pulse::timings next_timings = {};
uint8_t pulse_round = 0;
if (pulse::get_round_timings(blockchain, curr_height, top_header.timestamp, next_timings) &&
pulse::convert_time_to_round(pulse::clock::now(), next_timings.r0_timestamp, &pulse_round))
{
auto entropy = service_nodes::get_pulse_entropy_for_next_block(blockchain.get_db(), pulse_round);
auto& sn_list = m_core.get_service_node_list();
auto quorum = generate_pulse_quorum(m_core.get_nettype(), sn_list.get_block_leader().key, hf_version, sn_list.active_service_nodes_infos(), entropy, pulse_round);
if (verify_pulse_quorum_sizes(quorum))
{
auto& entry = res.quorums.emplace_back();
entry.height = curr_height;
entry.quorum_type = static_cast<uint8_t>(service_nodes::quorum_type::pulse);
entry.quorum.validators = hexify(quorum.validators);
entry.quorum.workers = hexify(quorum.workers);
at_least_one_succeeded = true;
}
}
}
if (!at_least_one_succeeded)
throw rpc_error{ERROR_WRONG_PARAM, "Failed to query any quorums at all"};
@ -3414,7 +3465,9 @@ namespace cryptonote { namespace rpc {
}
lns::name_system_db &db = m_core.get_blockchain_storage().name_system_db();
auto height = m_core.get_current_blockchain_height();
std::optional<uint64_t> height;
if (!req.include_expired) height = m_core.get_current_blockchain_height();
std::vector<lns::mapping_record> records = db.get_mappings_by_owners(owners, height);
for (auto &record : records)
{

View file

@ -1337,6 +1337,7 @@ KV_SERIALIZE_MAP_CODE_END()
KV_SERIALIZE_MAP_CODE_BEGIN(LNS_OWNERS_TO_NAMES::request)
KV_SERIALIZE(entries)
KV_SERIALIZE(include_expired)
KV_SERIALIZE_MAP_CODE_END()
@ -1348,6 +1349,7 @@ KV_SERIALIZE_MAP_CODE_BEGIN(LNS_OWNERS_TO_NAMES::response_entry)
KV_SERIALIZE(backup_owner)
KV_SERIALIZE(encrypted_value)
KV_SERIALIZE(update_height)
KV_SERIALIZE(expiration_height)
KV_SERIALIZE(txid)
KV_SERIALIZE_MAP_CODE_END()

View file

@ -1822,7 +1822,7 @@ namespace rpc {
LOKI_RPC_DOC_INTROSPECT
// Get the quorum state which is the list of public keys of the nodes who are voting, and the list of public keys of the nodes who are being tested.
// Accesses the list of public keys of the nodes who are participating or being tested in a quorum.
struct GET_QUORUM_STATE : PUBLIC
{
static constexpr auto names() { return NAMES("get_quorum_state"); }
@ -1832,17 +1832,17 @@ namespace rpc {
static constexpr uint8_t ALL_QUORUMS_SENTINEL_VALUE = 255;
struct request
{
uint64_t start_height; // (Optional): Start height, omit both start and end height to request the latest quorum
uint64_t start_height; // (Optional): Start height, omit both start and end height to request the latest quorum. Note that "latest" means different heights for different types of quorums as not all quorums exist at every block heights.
uint64_t end_height; // (Optional): End height, omit both start and end height to request the latest quorum
uint8_t quorum_type; // (Optional): Set value to request a specific quorum, 0 = Obligation, 1 = Checkpointing, 255 = all quorums, default is all quorums;
uint8_t quorum_type; // (Optional): Set value to request a specific quorum, 0 = Obligation, 1 = Checkpointing, 2 = Blink, 3 = Pulse, 255 = all quorums, default is all quorums. For Pulse quorums, requesting the blockchain height (or latest) returns the primary pulse quorum responsible for the next block; for heights with blocks this returns the actual quorum, which may be a backup quorum if the primary quorum did not produce in time.
KV_MAP_SERIALIZABLE
};
struct quorum_t
{
std::vector<std::string> validators; // Public key of the service node
std::vector<std::string> workers; // Public key of the service node
std::vector<std::string> validators; // List of service node public keys in the quorum. For obligations quorums these are the testing nodes; for checkpoint and blink these are the participating nodes (there are no workers); for Pulse blink quorums these are the block signers.
std::vector<std::string> workers; // Public key of the quorum workers. For obligations quorums these are the nodes being tested; for Pulse quorums this is the block producer. Checkpoint and Blink quorums do not populate this field.
KV_MAP_SERIALIZABLE
@ -2463,6 +2463,7 @@ namespace rpc {
struct request
{
std::vector<std::string> entries; // The owner's public key to find all Loki Name Service entries for.
bool include_expired; // Optional: if provided and true, include entries in the results even if they are expired
KV_MAP_SERIALIZABLE
};

View file

@ -249,6 +249,7 @@ namespace cryptonote::rpc {
auto& res = data->res;
res.writeHeader("Server", data->http.server_header());
res.writeHeader("Content-Type", data->call->is_binary ? "application/octet-stream"sv : "application/json"sv);
if (data->http.closing()) res.writeHeader("Connection", "close");
for (const auto& [name, value] : data->extra_headers)
res.writeHeader(name, value);
@ -256,6 +257,7 @@ namespace cryptonote::rpc {
res.write(piece);
res.end();
if (data->http.closing()) res.close();
});
});
}
@ -600,6 +602,8 @@ namespace cryptonote::rpc {
for (auto* s : m_listen_socks)
us_listen_socket_close(/*ssl=*/false, s);
m_closing = true;
{
// Destroy any pending long poll connections as well
MTRACE("closing pending long poll requests");

View file

@ -48,8 +48,10 @@ namespace cryptonote::rpc {
res.writeHeader("Server", m_server_header);
res.writeHeader("WWW-Authenticate", *www_auth);
res.writeHeader("Content-Type", "text/plain");
if (m_closing) res.writeHeader("Connection", "close");
if (req.getMethod() != "HEAD"sv)
res.end("Login required\n");
if (m_closing) res.close();
return false;
}
return true;
@ -63,10 +65,12 @@ namespace cryptonote::rpc {
res.writeStatus(std::to_string(code.first) + " " + std::string{code.second});
res.writeHeader("Server", m_server_header);
res.writeHeader("Content-Type", "text/plain");
if (m_closing) res.writeHeader("Connection", "close");
if (body)
res.end(*body);
else
res.end(std::string{code.second} + "\n");
if (m_closing) res.close();
}
// Similar to the above, but for JSON errors (which are 200 OK + error embedded in JSON)
@ -85,7 +89,9 @@ namespace cryptonote::rpc {
res.writeStatus("200 OK"sv);
res.writeHeader("Server", m_server_header);
res.writeHeader("Content-Type", "application/json");
if (m_closing) res.writeHeader("Connection", "close");
res.end(body);
if (m_closing) res.close();
}
std::string http_server_base::get_remote_address(HttpResponse& res) {

View file

@ -52,6 +52,8 @@ namespace cryptonote::rpc {
const std::string& server_header() { return m_server_header; }
bool closing() const { return m_closing; }
static constexpr http_response_code
HTTP_OK{200, "OK"sv},
HTTP_BAD_REQUEST{400, "Bad Request"sv},
@ -80,6 +82,9 @@ namespace cryptonote::rpc {
// Access-Control-Allow-Origin header values; if one of these match the incoming Origin header
// we return it in the ACAO header; otherwise (or if this is empty) we omit the header entirely.
std::unordered_set<std::string> m_cors;
// Will be set to true when we're trying to shut down which closes any connections as we reply
// to them. Should only be read/write from inside the uWS loop.
bool m_closing = false;
// If true then always reply with 'Access-Control-Allow-Origin: *' to allow anything.
bool m_cors_any = false;
};

View file

@ -6466,17 +6466,15 @@ bool simple_wallet::lns_buy_mapping(std::vector<std::string> args)
std::string const &name = args[0];
std::string const &value = args[1];
lns::mapping_type type;
if (auto t = guess_lns_type(*m_wallet, typestr, name, value))
type = *t;
else return false;
std::optional<lns::mapping_type> type = guess_lns_type(*m_wallet, typestr, name, value);
if (!type) return false;
SCOPED_WALLET_UNLOCK();
std::string reason;
std::vector<tools::wallet2::pending_tx> ptx_vector;
try
{
ptx_vector = m_wallet->lns_create_buy_mapping_tx(type,
ptx_vector = m_wallet->lns_create_buy_mapping_tx(*type,
owner.size() ? &owner : nullptr,
backup_owner.size() ? &backup_owner : nullptr,
name,
@ -6498,15 +6496,15 @@ bool simple_wallet::lns_buy_mapping(std::vector<std::string> args)
dsts.push_back(info);
std::cout << std::endl << tr("Buying Loki Name System Record") << std::endl << std::endl;
if (type == lns::mapping_type::session)
if (*type == lns::mapping_type::session)
std::cout << boost::format(tr("Session Name: %s")) % name << std::endl;
else if (lns::is_lokinet_type(type))
else if (lns::is_lokinet_type(*type))
{
std::cout << boost::format(tr("Lokinet Name: %s")) % name << std::endl;
int years =
type == lns::mapping_type::lokinet_10years ? 10 :
type == lns::mapping_type::lokinet_5years ? 5 :
type == lns::mapping_type::lokinet_2years ? 2 :
*type == lns::mapping_type::lokinet_10years ? 10 :
*type == lns::mapping_type::lokinet_5years ? 5 :
*type == lns::mapping_type::lokinet_2years ? 2 :
1;
int blocks = BLOCKS_EXPECTED_IN_DAYS(years * lns::REGISTRATION_YEAR_DAYS);
std::cout << boost::format(tr("Registration: %d years (%d blocks)")) % years % blocks << "\n";
@ -6528,12 +6526,9 @@ bool simple_wallet::lns_buy_mapping(std::vector<std::string> args)
//Save the LNS record to the wallet cache
std::string name_hash_str = lns::name_to_base64_hash(name);
tools::wallet2::lns_detail detail = {
type,
*type,
name,
name_hash_str,
value,
owner.size() ? owner : m_wallet->get_subaddress_as_str({m_current_subaddress_account, 0}),
backup_owner.size() ? backup_owner : ""};
name_hash_str};
m_wallet->set_lns_cache_record(detail);
}
catch (const std::exception &e)
@ -6737,10 +6732,7 @@ bool simple_wallet::lns_update_mapping(std::vector<std::string> args)
tools::wallet2::lns_detail detail = {
type,
name,
name_hash_str,
value,
owner.size() ? owner : m_wallet->get_subaddress_as_str({m_current_subaddress_account, 0}),
backup_owner.size() ? backup_owner : ""};
name_hash_str};
m_wallet->set_lns_cache_record(detail);
}
@ -6925,8 +6917,6 @@ bool simple_wallet::lns_print_name_to_owners(std::vector<std::string> args)
return false;
}
std::unordered_map<std::string, tools::wallet2::lns_detail> cache = m_wallet->get_lns_cache();
// Print any skipped (i.e. not registered) results:
for (size_t i = last_index + 1; i < mapping.entry_index; i++)
fail_msg_writer() << args[i] << " not found\n";
@ -6944,8 +6934,6 @@ bool simple_wallet::lns_print_name_to_owners(std::vector<std::string> args)
return false;
}
std::unordered_map<std::string,tools::wallet2::lns_detail>::const_iterator got = cache.find (lns::name_to_base64_hash(name));
auto writer = tools::msg_writer();
writer
<< "Name: " << name
@ -6958,8 +6946,6 @@ bool simple_wallet::lns_print_name_to_owners(std::vector<std::string> args)
<< "\n Last updated height: " << mapping.update_height;
if (mapping.expiration_height) writer
<< "\n Expiration height: " << *mapping.expiration_height;
if ( got != cache.end() ) writer
<< "\n Value: " << got->second.value;
writer
<< "\n Encrypted value: " << enc_hex;
writer
@ -6969,10 +6955,7 @@ bool simple_wallet::lns_print_name_to_owners(std::vector<std::string> args)
{
static_cast<lns::mapping_type>(mapping.type),
name,
request.entries[0].name_hash,
value.to_readable_value(m_wallet->nettype(), static_cast<lns::mapping_type>(mapping.type)),
mapping.owner,
mapping.backup_owner.value_or(NULL_STR)};
request.entries[0].name_hash};
m_wallet->set_lns_cache_record(detail);
}
for (size_t i = last_index + 1; i < args.size(); i++)
@ -7036,6 +7019,7 @@ bool simple_wallet::lns_print_owners_to_names(const std::vector<std::string>& ar
}
auto nettype = m_wallet->nettype();
for (size_t i = 0; i < rpc_results.size(); i++)
{
auto const &rpc = rpc_results[i];
@ -7052,15 +7036,27 @@ bool simple_wallet::lns_print_owners_to_names(const std::vector<std::string>& ar
continue;
}
auto got = cache.find(entry.name_hash);
std::string_view name;
std::string value;
if (auto got = cache.find(entry.name_hash); got != cache.end())
{
name = got->second.name;
lns::mapping_value mv;
if (lns::mapping_value::validate_encrypted(entry.type, lokimq::from_hex(entry.encrypted_value), &mv)
&& mv.decrypt(name, entry.type))
value = mv.to_readable_value(nettype, entry.type);
}
auto writer = tools::msg_writer();
writer
<< "Name (hashed): " << entry.name_hash;
if ( got != cache.end() ) writer
<< "\n Name: " << got->second.name;
if (!name.empty()) writer
<< "\n Name: " << name;
writer
<< "\n Type: " << entry.type;
if (!value.empty()) writer
<< "\n Value: " << value;
writer
<< "\n Type: " << entry.type
<< "\n Owner: " << *owner;
if (entry.backup_owner) writer
<< "\n Backup owner: " << *entry.backup_owner;
@ -7068,8 +7064,6 @@ bool simple_wallet::lns_print_owners_to_names(const std::vector<std::string>& ar
<< "\n Last updated height: " << entry.update_height;
if (entry.expiration_height) writer
<< "\n Expiration height: " << *entry.expiration_height;
if ( got != cache.end() ) writer
<< "\n Value: " << got->second.value;
writer
<< "\n Encrypted value: " << entry.encrypted_value;
}

View file

@ -370,23 +370,18 @@ std::unique_ptr<tools::wallet2> make_basic(const boost::program_options::variabl
if (command_line::is_arg_defaulted(vm, opts.daemon_host) && command_line::is_arg_defaulted(vm, opts.daemon_port) && command_line::is_arg_defaulted(vm, opts.daemon_address))
daemon_address = tools::wallet2::get_default_daemon_address();
std::string default_protocol = "http://";
// Deprecated --daemon-ssl option: prepend https:// if there is no protocol on the daemon address
if (command_line::get_arg(vm, opts.daemon_ssl) == "enabled") {
default_protocol = "https://";
THROW_WALLET_EXCEPTION_IF(tools::starts_with(daemon_address, "http://"), tools::error::wallet_internal_error,
"Deprecated --daemon-ssl=enabled option conflicts with http://... daemon URL");
}
if (daemon_address.empty())
{
daemon_address = (daemon_host.empty() ? "localhost" : daemon_host) + ':' +
std::to_string(daemon_port > 0 ? daemon_port : get_config(nettype).RPC_DEFAULT_PORT);
}
if (!std::regex_search(daemon_address, protocol_re))
{
daemon_address.insert(0, default_protocol);
// Deprecated --daemon-ssl option: prepend https:// if there is no protocol on the daemon address
if (command_line::get_arg(vm, opts.daemon_ssl) == "enabled") {
THROW_WALLET_EXCEPTION_IF(tools::starts_with(daemon_address, "http://"), tools::error::wallet_internal_error,
"Deprecated --daemon-ssl=enabled option conflicts with http://... daemon URL");
if (!std::regex_search(daemon_address, protocol_re))
daemon_address.insert(0, "https://"sv);
}
std::string proxy;
@ -1222,9 +1217,13 @@ std::string wallet2::get_default_daemon_address() {
//----------------------------------------------------------------------------------------------------
bool wallet2::set_daemon(std::string daemon_address, std::optional<tools::login> daemon_login, std::string proxy, bool trusted_daemon)
{
// If we're given a raw address, prepend http
// If we're given a raw address, prepend http, and (possibly) append the default port
if (!tools::starts_with(daemon_address, "http://") && !tools::starts_with(daemon_address, "https://"))
{
if (auto pos = daemon_address.find(':'); pos == std::string::npos)
daemon_address += ":" + std::to_string(cryptonote::get_config(m_nettype).RPC_DEFAULT_PORT);
daemon_address.insert(0, "http://"sv);
}
bool localhost = false;
try {
@ -8837,28 +8836,19 @@ std::vector<wallet2::pending_tx> wallet2::lns_create_buy_mapping_tx(lns::mapping
return result;
}
std::vector<wallet2::pending_tx> wallet2::lns_create_buy_mapping_tx(std::string const &type,
std::string const *owner,
std::string const *backup_owner,
std::string const &name,
std::string const &value,
std::string *reason,
uint32_t priority,
uint32_t account_index,
std::set<uint32_t> subaddr_indices)
std::optional<lns::mapping_type> wallet2::lns_validate_type(std::string_view type, lns::lns_tx_type lns_action, std::string *reason)
{
std::optional<uint8_t> hf_version = get_hard_fork_version();
if (!hf_version)
{
if (reason) *reason = ERR_MSG_NETWORK_VERSION_QUERY_FAILED;
return {};
return std::nullopt;
}
lns::mapping_type mapping_type;
if (!lns::validate_mapping_type(type, *hf_version, lns::lns_tx_type::buy, &mapping_type, reason))
return {};
if (!lns::validate_mapping_type(type, *hf_version, lns_action, &mapping_type, reason))
return std::nullopt;
std::vector<wallet2::pending_tx> result = lns_create_buy_mapping_tx(mapping_type, owner, backup_owner, name, value, reason, priority, account_index, subaddr_indices);
return result;
return mapping_type;
}
std::vector<wallet2::pending_tx> wallet2::lns_create_renewal_tx(
@ -8903,29 +8893,6 @@ std::vector<wallet2::pending_tx> wallet2::lns_create_renewal_tx(
return result;
}
std::vector<wallet2::pending_tx> wallet2::lns_create_renewal_tx(
std::string const &type,
std::string const &name,
std::string *reason,
uint32_t priority,
uint32_t account_index,
std::set<uint32_t> subaddr_indices,
std::vector<cryptonote::rpc::LNS_NAMES_TO_OWNERS::response_entry> *response
)
{
lns::mapping_type mapping_type = lns::mapping_type::session;
std::optional<uint8_t> hf_version = get_hard_fork_version();
if (!hf_version)
{
if (reason) *reason = ERR_MSG_NETWORK_VERSION_QUERY_FAILED;
return {};
}
if (!lns::validate_mapping_type(type, *hf_version, lns::lns_tx_type::renew, &mapping_type, reason))
return {};
return lns_create_renewal_tx(mapping_type, name, reason, priority, account_index, subaddr_indices, response);
}
std::vector<wallet2::pending_tx> wallet2::lns_create_update_mapping_tx(lns::mapping_type type,
std::string name,
@ -8986,32 +8953,6 @@ std::vector<wallet2::pending_tx> wallet2::lns_create_update_mapping_tx(lns::mapp
return result;
}
std::vector<wallet2::pending_tx> wallet2::lns_create_update_mapping_tx(std::string const &type,
std::string const &name,
std::string const *value,
std::string const *owner,
std::string const *backup_owner,
std::string const *signature,
std::string *reason,
uint32_t priority,
uint32_t account_index,
std::set<uint32_t> subaddr_indices,
std::vector<cryptonote::rpc::LNS_NAMES_TO_OWNERS::response_entry> *response)
{
lns::mapping_type mapping_type = lns::mapping_type::session;
std::optional<uint8_t> hf_version = get_hard_fork_version();
if (!hf_version)
{
if (reason) *reason = ERR_MSG_NETWORK_VERSION_QUERY_FAILED;
return {};
}
if (!lns::validate_mapping_type(type, *hf_version, lns::lns_tx_type::update, &mapping_type, reason))
return {};
std::vector<wallet2::pending_tx> result = lns_create_update_mapping_tx(mapping_type, name, value, owner, backup_owner, signature, reason, priority, account_index, subaddr_indices, response);
return result;
}
bool wallet2::lock_keys_file()
{
if (m_wallet_file.empty())

View file

@ -802,9 +802,6 @@ private:
lns::mapping_type type;
std::string name;
std::string hashed_name;
std::string value;
std::string owner;
std::string backup_owner;
};
std::unordered_map<std::string, lns_detail> lns_records_cache;
@ -1357,17 +1354,20 @@ private:
pending_tx ptx;
};
request_stake_unlock_result can_request_stake_unlock(const crypto::public_key &sn_key);
// Attempts to convert the LNS type string to a mapping type (checking the current hard fork).
// If type isn't valid then returns std::nullopt and sets the failure reason in `reason` (if not
// nullptr).
std::optional<lns::mapping_type> lns_validate_type(std::string_view type, lns::lns_tx_type lns_action, std::string *reason);
std::vector<pending_tx> lns_create_buy_mapping_tx(lns::mapping_type type, std::string const *owner, std::string const *backup_owner, std::string name, std::string const &value, std::string *reason, uint32_t priority = 0, uint32_t account_index = 0, std::set<uint32_t> subaddr_indices = {});
std::vector<pending_tx> lns_create_buy_mapping_tx(std::string const &type, std::string const *owner, std::string const *backup_owner, std::string const &name, std::string const &value, std::string *reason, uint32_t priority = 0, uint32_t account_index = 0, std::set<uint32_t> subaddr_indices = {});
// signature: (Optional) If set, use the signature given, otherwise by default derive the signature from the wallet spend key as an ed25519 key.
// The signature is derived from the hash of the previous txid blob and previous value blob of the mapping. By default this is signed using the wallet's spend key as an ed25519 keypair.
std::vector<pending_tx> lns_create_update_mapping_tx(lns::mapping_type type, std::string name, std::string const *value, std::string const *owner, std::string const *backup_owner, std::string const *signature, std::string *reason, uint32_t priority = 0, uint32_t account_index = 0, std::set<uint32_t> subaddr_indices = {}, std::vector<cryptonote::rpc::LNS_NAMES_TO_OWNERS::response_entry> *response = {});
std::vector<pending_tx> lns_create_update_mapping_tx(std::string const &type, std::string const &name, std::string const *value, std::string const *owner, std::string const *backup_owner, std::string const *signature, std::string *reason, uint32_t priority = 0, uint32_t account_index = 0, std::set<uint32_t> subaddr_indices = {}, std::vector<cryptonote::rpc::LNS_NAMES_TO_OWNERS::response_entry> *response = {});
// LNS renewal (for lokinet registrations, not for session/wallet)
std::vector<pending_tx> lns_create_renewal_tx(lns::mapping_type type, std::string name, std::string *reason, uint32_t priority = 0, uint32_t account_index = 0, std::set<uint32_t> subaddr_indices = {}, std::vector<cryptonote::rpc::LNS_NAMES_TO_OWNERS::response_entry> *response = {});
std::vector<pending_tx> lns_create_renewal_tx(std::string const &type, std::string const &name, std::string *reason, uint32_t priority = 0, uint32_t account_index = 0, std::set<uint32_t> subaddr_indices = {}, std::vector<cryptonote::rpc::LNS_NAMES_TO_OWNERS::response_entry> *response = {});
// Generate just the signature required for putting into lns_update_mapping command in the wallet
bool lns_make_update_mapping_signature(lns::mapping_type type, std::string name, std::string const *value, std::string const *owner, std::string const *backup_owner, lns::generic_signature &signature, uint32_t account_index = 0, std::string *reason = nullptr);
@ -1684,18 +1684,21 @@ BOOST_CLASS_VERSION(tools::wallet2::unconfirmed_transfer_details, 9)
BOOST_CLASS_VERSION(tools::wallet2::confirmed_transfer_details, 8)
BOOST_CLASS_VERSION(tools::wallet2::address_book_row, 18)
BOOST_CLASS_VERSION(tools::wallet2::reserve_proof_entry, 0)
BOOST_CLASS_VERSION(tools::wallet2::lns_detail, 1)
namespace boost::serialization
{
template <class Archive>
inline void serialize(Archive &a, tools::wallet2::lns_detail &x, const boost::serialization::version_type ver)
void serialize(Archive &a, tools::wallet2::lns_detail &x, const unsigned int ver)
{
a & x.type;
a & x.name;
a & x.hashed_name;
a & x.value;
a & x.owner;
a & x.backup_owner;
if (ver < 1)
{ // Old fields, no longer used:
std::string value, owner, backup_owner;
a & value & owner & backup_owner;
}
}
template <class Archive>

View file

@ -39,7 +39,7 @@
#include <chrono>
#include <exception>
#include "wallet/wallet_rpc_server_error_codes.h"
#include "wallet_rpc_server_error_codes.h"
#include "wallet_rpc_server.h"
#include "wallet/wallet_args.h"
#include "common/command_line.h"
@ -311,8 +311,10 @@ namespace tools
res.writeHeader("Content-Type", "application/json");
for (const auto& [name, value] : extra_headers)
res.writeHeader(name, value);
if (closing()) res.writeHeader("Connection", "close");
res.end(result);
if (closing()) res.close();
});
}
@ -415,6 +417,7 @@ namespace tools
// Stopped: close the sockets, cancel the long poll, and rejoin the threads
for (auto* s : m_listen_socks)
us_listen_socket_close(/*ssl=*/false, s);
m_closing = true;
stop_long_poll_thread();
@ -3057,7 +3060,11 @@ namespace {
LNS_BUY_MAPPING::response res{};
std::string reason;
std::vector<wallet2::pending_tx> ptx_vector = m_wallet->lns_create_buy_mapping_tx(req.type,
auto type = m_wallet->lns_validate_type(req.type, lns::lns_tx_type::buy, &reason);
if (!type)
throw wallet_rpc_error{error_code::TX_NOT_POSSIBLE, "Invalid LNS buy type: " + reason};
std::vector<wallet2::pending_tx> ptx_vector = m_wallet->lns_create_buy_mapping_tx(*type,
req.owner.size() ? &req.owner : nullptr,
req.backup_owner.size() ? &req.backup_owner : nullptr,
req.name,
@ -3072,12 +3079,9 @@ namespace {
//Save the LNS record to the wallet cache
std::string name_hash_str = lns::name_to_base64_hash(req.name);
tools::wallet2::lns_detail detail = {
lns::mapping_type::session,
*type,
req.name,
name_hash_str,
req.value,
req.owner.size() ? req.owner : m_wallet->get_subaddress_as_str({req.account_index, 0}),
req.backup_owner.size() ? req.backup_owner : ""};
name_hash_str};
m_wallet->set_lns_cache_record(detail);
fill_response( ptx_vector,
@ -3104,11 +3108,15 @@ namespace {
LNS_RENEW_MAPPING::response res{};
std::string reason;
auto type = m_wallet->lns_validate_type(req.type, lns::lns_tx_type::renew, &reason);
if (!type)
throw wallet_rpc_error{error_code::TX_NOT_POSSIBLE, "Invalid LNS renewal type: " + reason};
std::vector<wallet2::pending_tx> ptx_vector = m_wallet->lns_create_renewal_tx(
req.type, req.name, &reason, req.priority, req.account_index, req.subaddr_indices);
*type, req.name, &reason, req.priority, req.account_index, req.subaddr_indices);
if (ptx_vector.empty())
throw wallet_rpc_error{error_code::TX_NOT_POSSIBLE, "Failed to create LNS transaction: " + reason};
throw wallet_rpc_error{error_code::TX_NOT_POSSIBLE, "Failed to create LNS renewal transaction: " + reason};
fill_response( ptx_vector,
req.get_tx_key,
@ -3134,8 +3142,12 @@ namespace {
LNS_UPDATE_MAPPING::response res{};
std::string reason;
auto type = m_wallet->lns_validate_type(req.type, lns::lns_tx_type::update, &reason);
if (!type)
throw wallet_rpc_error{error_code::TX_NOT_POSSIBLE, "Invalid LNS update type: " + reason};
std::vector<wallet2::pending_tx> ptx_vector =
m_wallet->lns_create_update_mapping_tx(req.type,
m_wallet->lns_create_update_mapping_tx(*type,
req.name,
req.value.empty() ? nullptr : &req.value,
req.owner.empty() ? nullptr : &req.owner,
@ -3153,12 +3165,9 @@ namespace {
std::string name_hash_str = lns::name_to_base64_hash(req.name);
m_wallet->delete_lns_cache_record(name_hash_str);
tools::wallet2::lns_detail detail = {
lns::mapping_type::session,
*type,
req.name,
name_hash_str,
req.value,
req.owner.size() ? req.owner : m_wallet->get_subaddress_as_str({req.account_index, 0}),
req.backup_owner.size() ? req.backup_owner : ""};
name_hash_str};
m_wallet->set_lns_cache_record(detail);
fill_response( ptx_vector,
@ -3230,12 +3239,14 @@ namespace {
require_open();
LNS_KNOWN_NAMES::response res{};
std::vector<lns::mapping_type> entry_types;
auto cache = m_wallet->get_lns_cache();
res.known_names.reserve(cache.size());
entry_types.reserve(cache.size());
for (auto& [name, details] : m_wallet->get_lns_cache())
{
auto& entry = res.known_names.emplace_back();
auto type = details.type;
auto& type = entry_types.emplace_back(details.type);
if (type > lns::mapping_type::lokinet && type <= lns::mapping_type::lokinet_10years)
type = lns::mapping_type::lokinet;
entry.type = lns::mapping_type_str(type);
@ -3243,6 +3254,75 @@ namespace {
entry.name = details.name;
}
auto nettype = m_wallet->nettype();
rpc::LNS_NAMES_TO_OWNERS::request lookup_req{};
lookup_req.include_expired = req.include_expired;
uint64_t curr_height = req.include_expired ? m_wallet->get_blockchain_current_height() : 0;
// Query lokid for the full record info
for (auto it = res.known_names.begin(); it != res.known_names.end(); )
{
const size_t num_entries = std::distance(it, res.known_names.end());
const auto end = num_entries < rpc::LNS_NAMES_TO_OWNERS::MAX_REQUEST_ENTRIES
? res.known_names.end()
: it + rpc::LNS_NAMES_TO_OWNERS::MAX_REQUEST_ENTRIES;
lookup_req.entries.clear();
lookup_req.entries.reserve(std::distance(it, end));
for (auto it2 = it; it2 != end; it2++)
{
auto& e = lookup_req.entries.emplace_back();
e.name_hash = it2->hashed;
e.types.push_back(static_cast<uint16_t>(entry_types[std::distance(res.known_names.begin(), it2)]));
}
if (auto [success, records] = m_wallet->lns_names_to_owners(lookup_req); success)
{
size_t type_offset = std::distance(res.known_names.begin(), it);
for (auto& rec : records)
{
if (rec.entry_index >= num_entries)
{
MWARNING("Got back invalid entry_index " << rec.entry_index << " for a request for " << num_entries << " entries");
continue;
}
auto& res_e = *(it + rec.entry_index);
res_e.owner = std::move(rec.owner);
res_e.backup_owner = std::move(rec.backup_owner);
res_e.encrypted_value = std::move(rec.encrypted_value);
res_e.update_height = rec.update_height;
res_e.expiration_height = rec.expiration_height;
if (req.include_expired && res_e.expiration_height)
res_e.expired = *res_e.expiration_height < curr_height;
res_e.txid = std::move(rec.txid);
if (req.decrypt && !res_e.encrypted_value.empty() && lokimq::is_hex(res_e.encrypted_value))
{
lns::mapping_value value;
const auto type = entry_types[type_offset + rec.entry_index];
std::string errmsg;
if (lns::mapping_value::validate_encrypted(type, lokimq::from_hex(res_e.encrypted_value), &value, &errmsg)
&& value.decrypt(res_e.name, type))
res_e.value = value.to_readable_value(nettype, type);
else
MWARNING("Failed to decrypt LNS value for " << res_e.name << (errmsg.empty() ? ""s : ": " + errmsg));
}
}
}
it = end;
}
// Erase anything we didn't get a response for (it will have update_height of 0)
res.known_names.erase(std::remove_if(res.known_names.begin(), res.known_names.end(),
[](const auto& n) { return n.update_height == 0; }),
res.known_names.end());
// Now sort whatever we got back
std::sort(res.known_names.begin(), res.known_names.end(),
[](const auto& a, const auto& b) { return std::make_pair(a.name, a.type) < std::make_pair(b.name, b.type); });
return res;
}

View file

@ -1209,10 +1209,24 @@ KV_SERIALIZE_MAP_CODE_BEGIN(LNS_HASH_NAME::response)
KV_SERIALIZE_MAP_CODE_END()
KV_SERIALIZE_MAP_CODE_BEGIN(LNS_KNOWN_NAMES::known_name)
KV_SERIALIZE_MAP_CODE_BEGIN(LNS_KNOWN_NAMES::request)
KV_SERIALIZE(decrypt)
KV_SERIALIZE(include_expired)
KV_SERIALIZE_MAP_CODE_END()
KV_SERIALIZE_MAP_CODE_BEGIN(LNS_KNOWN_NAMES::known_record)
KV_SERIALIZE(type)
KV_SERIALIZE(hashed)
KV_SERIALIZE(name)
KV_SERIALIZE(owner)
KV_SERIALIZE(backup_owner)
KV_SERIALIZE(encrypted_value)
KV_SERIALIZE(value)
KV_SERIALIZE(update_height)
KV_SERIALIZE(expiration_height)
KV_SERIALIZE(expired)
KV_SERIALIZE(txid)
KV_SERIALIZE_MAP_CODE_END()

View file

@ -2361,24 +2361,39 @@ This command is only required if the open wallet is one of the owners of a LNS r
};
LOKI_RPC_DOC_INTROSPECT
// Returns a list of known, plain-text LNS names that this wallet knows about.
// Returns a list of known, plain-text LNS names along with record details for names that this
// wallet knows about. This can optionally decrypt the LNS value as well, or else just return the
// encrypted value.
struct LNS_KNOWN_NAMES : RPC_COMMAND
{
static constexpr auto names() { return NAMES("lns_known_names"); }
struct known_name
struct known_record
{
std::string type; // The mapping type, "session" or "lokinet".
std::string hashed; // The hashed name (in base64)
std::string name; // The plaintext name
std::string type; // The mapping type, "session" or "lokinet".
std::string hashed; // The hashed name (in base64)
std::string name; // The plaintext name
std::string owner; // The public key that purchased the Loki Name Service entry.
std::optional<std::string> backup_owner; // The backup public key or wallet that the owner specified when purchasing the Loki Name Service entry. Omitted if no backup owner.
std::string encrypted_value; // The encrypted value that the name maps to, in hex.
std::optional<std::string> value; // Decrypted value that that name maps to. Only provided if `decrypt: true` was specified in the request.
uint64_t update_height; // The last height that this Loki Name Service entry was updated on the Blockchain.
std::optional<uint64_t> expiration_height; // For records that expire, this will be set to the expiration block height.
std::optional<bool> expired; // Indicates whether the record has expired. Only included in the response if "include_expired" is specified in the request.
std::string txid; // The txid of the mapping's most recent update or purchase.
KV_MAP_SERIALIZABLE
};
struct request {
bool decrypt; // If true (default false) then also decrypt and include the `value` field
bool include_expired; // If true (default false) then also include expired records
KV_MAP_SERIALIZABLE
};
struct request : EMPTY {};
struct response
{
std::vector<known_name> known_names; // List of (unhashed) name info known to this wallet
std::vector<known_record> known_names; // List of records known to this wallet
KV_MAP_SERIALIZABLE
};
@ -2526,6 +2541,7 @@ This command is only required if the open wallet is one of the owners of a LNS r
SET_LOG_CATEGORIES,
LNS_BUY_MAPPING,
LNS_UPDATE_MAPPING,
LNS_RENEW_MAPPING,
LNS_MAKE_UPDATE_SIGNATURE,
LNS_HASH_NAME,
LNS_KNOWN_NAMES,