Merge pull request #1001 from loki-project/dev

Merge v6.1.1 to master
This commit is contained in:
Doyle 2020-01-07 17:16:01 +11:00 committed by GitHub
commit 41949a3081
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 392 additions and 98 deletions

View File

@ -61,6 +61,7 @@ enum struct lmdb_version
{
v4 = 4,
v5, // alt_block_data_1_t => alt_block_data_t: Alt block data has boolean for if the block was checkpointed
v6, // remigrate voter_to_signature struct due to alignment change
_count
};
@ -398,6 +399,7 @@ struct blk_checkpoint_header
uint64_t num_signatures;
};
static_assert(sizeof(blk_checkpoint_header) == 2*sizeof(uint64_t) + sizeof(crypto::hash), "blk_checkpoint_header has unexpected padding");
static_assert(sizeof(service_nodes::voter_to_signature) == sizeof(uint16_t) + 6 /*padding*/ + sizeof(crypto::signature), "Unexpected padding/struct size change. DB checkpoint signature entries need to be re-migrated to the new size");
typedef struct blk_height {
crypto::hash bh_hash;
@ -3861,9 +3863,14 @@ uint64_t BlockchainLMDB::add_block(const std::pair<block, blobdata>& blk, size_t
return ++m_height;
}
void BlockchainLMDB::update_block_checkpoint(checkpoint_t const &checkpoint)
struct checkpoint_mdb_buffer
{
char data[sizeof(blk_checkpoint_header) + (sizeof(service_nodes::voter_to_signature) * service_nodes::CHECKPOINT_QUORUM_SIZE)];
size_t len;
};
static bool convert_checkpoint_into_buffer(checkpoint_t const &checkpoint, checkpoint_mdb_buffer &result)
{
LOG_PRINT_L3("BlockchainLMDB::" << __func__);
blk_checkpoint_header header = {};
header.height = checkpoint.height;
header.block_hash = checkpoint.block_hash;
@ -3872,19 +3879,16 @@ void BlockchainLMDB::update_block_checkpoint(checkpoint_t const &checkpoint)
native_to_little_inplace(header.height);
native_to_little_inplace(header.num_signatures);
size_t const MAX_BYTES_REQUIRED = sizeof(header) + (sizeof(*checkpoint.signatures.data()) * service_nodes::CHECKPOINT_QUORUM_SIZE);
uint8_t buffer[MAX_BYTES_REQUIRED];
size_t const bytes_for_signatures = sizeof(*checkpoint.signatures.data()) * header.num_signatures;
size_t const actual_bytes_used = sizeof(header) + bytes_for_signatures;
if (actual_bytes_used > MAX_BYTES_REQUIRED)
size_t const bytes_for_signatures = sizeof(*checkpoint.signatures.data()) * checkpoint.signatures.size();
result.len = sizeof(header) + bytes_for_signatures;
if (result.len > sizeof(result.data))
{
LOG_PRINT_L0("Unexpected pre-calculated maximum number of bytes: " << MAX_BYTES_REQUIRED << ", is insufficient to store signatures requiring: " << actual_bytes_used << " bytes");
assert(actual_bytes_used <= MAX_BYTES_REQUIRED);
return;
LOG_PRINT_L0("Unexpected pre-calculated maximum number of bytes: " << sizeof(result.data) << ", is insufficient to store signatures requiring: " << result.len << " bytes");
assert(result.len <= sizeof(result.data));
return false;
}
uint8_t *buffer_ptr = buffer;
char *buffer_ptr = result.data;
memcpy(buffer_ptr, (void *)&header, sizeof(header));
buffer_ptr += sizeof(header);
@ -3893,23 +3897,33 @@ void BlockchainLMDB::update_block_checkpoint(checkpoint_t const &checkpoint)
// Bounds check memcpy
{
uint8_t const *end = buffer + MAX_BYTES_REQUIRED;
char const *end = result.data + sizeof(result.data);
if (buffer_ptr > end)
{
LOG_PRINT_L0("Unexpected memcpy bounds overflow on update_block_checkpoint");
assert(buffer_ptr <= end);
return;
return false;
}
}
return true;
}
void BlockchainLMDB::update_block_checkpoint(checkpoint_t const &checkpoint)
{
LOG_PRINT_L3("BlockchainLMDB::" << __func__);
checkpoint_mdb_buffer buffer = {};
convert_checkpoint_into_buffer(checkpoint, buffer);
check_open();
mdb_txn_cursors *m_cursors = &m_wcursors;
CURSOR(block_checkpoints);
MDB_val_set(key, checkpoint.height);
MDB_val value = {};
value.mv_size = actual_bytes_used;
value.mv_data = buffer;
value.mv_size = buffer.len;
value.mv_data = buffer.data;
int ret = mdb_cursor_put(m_cursors->block_checkpoints, &key, &value, 0);
if (ret)
throw0(DB_ERROR(lmdb_error("Failed to update block checkpoint in db transaction: ", ret).c_str()));
@ -3947,7 +3961,6 @@ static checkpoint_t convert_mdb_val_to_checkpoint(MDB_val const value)
reinterpret_cast<service_nodes::voter_to_signature *>(static_cast<uint8_t *>(value.mv_data) + sizeof(*header));
auto num_sigs = little_to_native(header->num_signatures);
result.height = little_to_native(header->height);
result.type = (num_sigs > 0) ? checkpoint_type::service_node : checkpoint_type::hardcoded;
result.block_hash = header->block_hash;
@ -5869,6 +5882,115 @@ void BlockchainLMDB::migrate_4_5(cryptonote::network_type nettype)
throw0(DB_ERROR(lmdb_error("Failed to update version for the db: ", result).c_str()));
}
void BlockchainLMDB::migrate_5_6()
{
LOG_PRINT_L3("BlockchainLMDB::" << __func__);
MGINFO_YELLOW("Migrating blockchain from DB version 5 to 6 - this may take a while:");
mdb_txn_safe txn(false);
{
int result = mdb_txn_begin(m_env, NULL, 0, txn);
if (result) throw0(DB_ERROR(lmdb_error("Failed to create a transaction for the db: ", result).c_str()));
}
if (auto res = mdb_dbi_open(txn, LMDB_BLOCK_CHECKPOINTS, 0, &m_block_checkpoints)) return;
MDB_cursor *cursor;
if (auto ret = mdb_cursor_open(txn, m_block_checkpoints, &cursor))
throw0(DB_ERROR(lmdb_error("Failed to open a cursor for block checkpoints: ", ret).c_str()));
struct unaligned_signature
{
char c[32];
char r[32];
};
struct unaligned_voter_to_signature
{
uint16_t voter_index;
unaligned_signature signature;
};
// NOTE: Iterate through all checkpoints in the DB. Convert them into
// a checkpoint, and compare the expected size of the payload
// (header+signatures) in the DB. If they don't match with the current
// expected size, the checkpoint was stored when signatures were not aligned.
// If we detect this, then we re-interpret the data as the unaligned version
// of the voter_to_signature. Save that information to an aligned version and
// re-store it back into the DB.
for (MDB_cursor_op op = MDB_FIRST;; op = MDB_NEXT)
{
MDB_val key, val;
int ret = mdb_cursor_get(cursor, &key, &val, op);
if (ret == MDB_NOTFOUND) break;
if (ret) throw0(DB_ERROR(lmdb_error("Failed to enumerate block checkpoints: ", ret).c_str()));
// NOTE: We don't have to check blk_checkpoint_header alignment even though
// crypto::hash became aligned due to the pre-existing static assertion for
// unexpected padding
auto const *header = static_cast<blk_checkpoint_header const *>(val.mv_data);
auto num_sigs = little_to_native(header->num_signatures);
auto const *aligned_signatures = reinterpret_cast<service_nodes::voter_to_signature *>(static_cast<uint8_t *>(val.mv_data) + sizeof(*header));
if (num_sigs == 0) continue; // NOTE: Hardcoded checkpoints
checkpoint_t checkpoint = {};
checkpoint.height = little_to_native(header->height);
checkpoint.type = (num_sigs > 0) ? checkpoint_type::service_node : checkpoint_type::hardcoded;
checkpoint.block_hash = header->block_hash;
bool unaligned_checkpoint = false;
{
std::array<int, service_nodes::CHECKPOINT_QUORUM_SIZE> vote_set = {};
for (size_t i = 0; i < num_sigs; i++)
{
auto const &entry = aligned_signatures[i];
size_t const actual_num_bytes_for_signatures = val.mv_size - sizeof(*header);
size_t const expected_num_bytes_for_signatures = sizeof(service_nodes::voter_to_signature) * num_sigs;
if (actual_num_bytes_for_signatures != expected_num_bytes_for_signatures)
{
unaligned_checkpoint = true;
break;
}
}
}
if (unaligned_checkpoint)
{
auto const *unaligned_signatures = reinterpret_cast<unaligned_voter_to_signature *>(static_cast<uint8_t *>(val.mv_data) + sizeof(*header));
for (size_t i = 0; i < num_sigs; i++)
{
auto const &unaligned = unaligned_signatures[i];
service_nodes::voter_to_signature aligned = {};
aligned.voter_index = unaligned.voter_index;
memcpy(aligned.signature.c.data, unaligned.signature.c, sizeof(aligned.signature.c));
memcpy(aligned.signature.r.data, unaligned.signature.r, sizeof(aligned.signature.r));
checkpoint.signatures.push_back(aligned);
}
}
else
{
break;
}
checkpoint_mdb_buffer buffer = {};
if (!convert_checkpoint_into_buffer(checkpoint, buffer))
throw0(DB_ERROR("Failed to convert migrated checkpoint into buffer"));
val.mv_size = buffer.len;
val.mv_data = buffer.data;
ret = mdb_cursor_put(cursor, &key, &val, MDB_CURRENT);
if (ret) throw0(DB_ERROR(lmdb_error("Failed to update block checkpoint in db migration transaction: ", ret).c_str()));
}
txn.commit();
if (int result = write_db_version(m_env, m_properties, txn, (uint32_t)lmdb_version::v6))
throw0(DB_ERROR(lmdb_error("Failed to update version for the db: ", result).c_str()));
}
void BlockchainLMDB::migrate(const uint32_t oldversion, cryptonote::network_type nettype)
{
switch(oldversion) {
@ -5882,6 +6004,8 @@ void BlockchainLMDB::migrate(const uint32_t oldversion, cryptonote::network_type
migrate_3_4(); /* FALLTHRU */
case 4:
migrate_4_5(nettype); /* FALLTHRU */
case 5:
migrate_5_6(); /* FALLTHRU */
default:
break;
}

View File

@ -426,6 +426,7 @@ private:
void migrate_2_3();
void migrate_3_4();
void migrate_4_5(cryptonote::network_type nettype);
void migrate_5_6();
void cleanup_batch();

View File

@ -141,6 +141,18 @@ namespace crypto {
generate_random_bytes_thread_safe(N, bytes);
}
constexpr size_t SIZE_TS_IN_HASH = sizeof(crypto::hash) / sizeof(size_t);
static_assert(SIZE_TS_IN_HASH * sizeof(size_t) == sizeof(crypto::hash) && alignof(crypto::hash) >= alignof(size_t),
"Expected crypto::hash size/alignment not satisfied");
// Combine hashes together via XORs.
inline void hash_xor(crypto::hash &dest, const crypto::hash &src) {
size_t (&dest_)[SIZE_TS_IN_HASH] = reinterpret_cast<size_t (&)[SIZE_TS_IN_HASH]>(dest);
const size_t (&src_)[SIZE_TS_IN_HASH] = reinterpret_cast<const size_t (&)[SIZE_TS_IN_HASH]>(src);
for (size_t i = 0; i < SIZE_TS_IN_HASH; ++i)
dest_[i] ^= src_[i];
}
/* Generate a value filled with random bytes.
*/
template<typename T>

View File

@ -1196,8 +1196,9 @@ namespace cryptonote
{
// Caller needs to do this around both this *and* parse_incoming_txs
//auto lock = incoming_tx_lock();
uint8_t version = m_blockchain_storage.get_current_hard_fork_version();
bool ok = true;
uint8_t version = m_blockchain_storage.get_current_hard_fork_version();
bool ok = true;
bool tx_pool_changed = false;
if (blink_rollback_height)
*blink_rollback_height = 0;
tx_pool_options tx_opts;
@ -1223,7 +1224,10 @@ namespace cryptonote
local_opts = &tx_opts;
}
if (m_mempool.add_tx(info.tx, info.tx_hash, *info.blob, weight, info.tvc, *local_opts, version, blink_rollback_height))
{
tx_pool_changed |= info.tvc.m_added_to_pool;
MDEBUG("tx added: " << info.tx_hash);
}
else
{
ok = false;
@ -1234,6 +1238,7 @@ namespace cryptonote
}
}
if (tx_pool_changed) m_long_poll_wake_up_clients.notify_all();
return ok;
}
//-----------------------------------------------------------------------------------------------

View File

@ -963,7 +963,9 @@ namespace cryptonote
*/
bool relay_txpool_transactions();
private:
std::mutex m_long_poll_mutex;
std::condition_variable m_long_poll_wake_up_clients;
private:
/**
* @copydoc Blockchain::add_new_block
@ -1162,6 +1164,7 @@ namespace cryptonote
bool m_pad_transactions;
std::shared_ptr<tools::Notify> m_block_rate_notify;
};
}

View File

@ -129,6 +129,7 @@ namespace service_nodes
voter_to_signature() = default;
voter_to_signature(quorum_vote_t const &vote) : voter_index(vote.index_in_group), signature(vote.signature) { }
uint16_t voter_index;
char padding[6];
crypto::signature signature;
BEGIN_SERIALIZE()

View File

@ -545,17 +545,6 @@ namespace cryptonote
tx_hashes.end());
}
constexpr size_t SIZE_TS_IN_HASH = sizeof(crypto::hash) / sizeof(size_t);
static_assert(SIZE_TS_IN_HASH * sizeof(size_t) == sizeof(crypto::hash) && alignof(crypto::hash) >= alignof(size_t),
"Expected crypto::hash size/alignment not satisfied");
static void hash_xor(crypto::hash &checksum, const crypto::hash &x) {
size_t (&cs)[SIZE_TS_IN_HASH] = reinterpret_cast<size_t (&)[SIZE_TS_IN_HASH]>(checksum);
const size_t (&xs)[SIZE_TS_IN_HASH] = reinterpret_cast<const size_t (&)[SIZE_TS_IN_HASH]>(x);
for (size_t i = 0; i < SIZE_TS_IN_HASH; ++i)
cs[i] ^= xs[i];
}
std::pair<std::vector<crypto::hash>, std::vector<uint64_t>> tx_memory_pool::get_blink_hashes_and_mined_heights() const
{
std::pair<std::vector<crypto::hash>, std::vector<uint64_t>> hnh;
@ -619,7 +608,7 @@ namespace cryptonote
if (it == result.end() || it->first != heights[i])
result.emplace_hint(it, heights[i], hashes[i]);
else
hash_xor(it->second, hashes[i]);
crypto::hash_xor(it->second, hashes[i]);
}
return result;
}

View File

@ -72,7 +72,7 @@ public:
void run()
{
MGINFO("Starting " << m_description << " RPC server...");
if (!m_server.run(2, false))
if (!m_server.run(m_server.m_max_long_poll_connections + cryptonote::core_rpc_server::DEFAULT_RPC_THREADS, false /*wait - for all threads in the pool to exit when terminating*/))
{
throw std::runtime_error("Failed to start " + m_description + " RPC server.");
}

View File

@ -48,6 +48,7 @@
#include <ctime>
#include <string>
#include <numeric>
#include <stack>
#undef LOKI_DEFAULT_LOG_CATEGORY
#define LOKI_DEFAULT_LOG_CATEGORY "daemon"

View File

@ -58,6 +58,7 @@ using namespace epee;
#include "core_rpc_server_error_codes.h"
#include "p2p/net_node.h"
#include "version.h"
#include "wallet/wallet2.h"
#undef LOKI_DEFAULT_LOG_CATEGORY
#define LOKI_DEFAULT_LOG_CATEGORY "daemon.rpc"
@ -85,6 +86,7 @@ namespace cryptonote
command_line::add_arg(desc, arg_restricted_rpc);
command_line::add_arg(desc, arg_bootstrap_daemon_address);
command_line::add_arg(desc, arg_bootstrap_daemon_login);
command_line::add_arg(desc, arg_rpc_long_poll_connections);
cryptonote::rpc_args::init_options(desc, true);
}
//------------------------------------------------------------------------------------------------------------------------------
@ -104,6 +106,7 @@ namespace cryptonote
{
m_restricted = restricted;
m_net_server.set_threads_prefix("RPC");
m_max_long_poll_connections = command_line::get_arg(vm, arg_rpc_long_poll_connections);
auto rpc_config = cryptonote::rpc_args::process(vm, true);
if (!rpc_config)
@ -1126,10 +1129,44 @@ namespace cryptonote
if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_GET_TRANSACTION_POOL_HASHES_BIN>(invoke_http_mode::JON, "/get_transaction_pool_hashes.bin", req, res, r))
return r;
std::vector<crypto::hash> tx_pool_hashes;
const bool restricted = m_restricted && ctx;
const bool request_has_rpc_origin = ctx != NULL;
m_core.get_pool().get_transaction_hashes(res.tx_hashes, !request_has_rpc_origin || !restricted);
res.status = CORE_RPC_STATUS_OK;
m_core.get_pool().get_transaction_hashes(tx_pool_hashes, !request_has_rpc_origin || !restricted);
if (req.long_poll)
{
crypto::hash checksum = {};
for (crypto::hash const &hash : tx_pool_hashes) crypto::hash_xor(checksum, hash);
if (req.tx_pool_checksum == checksum)
{
size_t tx_count_before = tx_pool_hashes.size();
time_t before = time(nullptr);
std::unique_lock<std::mutex> lock(m_core.m_long_poll_mutex);
if ((m_long_poll_active_connections + 1) > m_max_long_poll_connections)
{
res.status = "Too many long polling connections, refusing connection";
return true;
}
m_long_poll_active_connections++;
bool condition_activated = m_core.m_long_poll_wake_up_clients.wait_for(lock, tools::wallet2::rpc_long_poll_timeout, [this, tx_count_before]() {
size_t tx_count_after = m_core.get_pool().get_transactions_count();
return tx_count_before != tx_count_after;
});
m_long_poll_active_connections--;
if (!condition_activated)
{
res.status = CORE_RPC_STATUS_TX_LONG_POLL_TIMED_OUT;
return true;
}
}
}
res.tx_hashes = std::move(tx_pool_hashes);
res.status = CORE_RPC_STATUS_OK;
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
@ -2526,6 +2563,12 @@ namespace cryptonote
//
// Loki
//
const command_line::arg_descriptor<int> core_rpc_server::arg_rpc_long_poll_connections = {
"rpc-long-poll-connections"
, "Number of RPC connections allocated for long polling wallet queries to the TX pool"
, 16
};
bool core_rpc_server::on_get_quorum_state(const COMMAND_RPC_GET_QUORUM_STATE::request& req, COMMAND_RPC_GET_QUORUM_STATE::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx)
{
PERF_TIMER(on_get_quorum_state);

View File

@ -59,7 +59,7 @@ namespace cryptonote
class core_rpc_server: public epee::http_server_impl_base<core_rpc_server>
{
public:
static constexpr int DEFAULT_RPC_THREADS = 2;
static const command_line::arg_descriptor<bool> arg_public_node;
static const command_line::arg_descriptor<std::string, false, true, 2> arg_rpc_bind_port;
static const command_line::arg_descriptor<std::string> arg_rpc_restricted_bind_port;
@ -72,6 +72,7 @@ namespace cryptonote
static const command_line::arg_descriptor<bool> arg_rpc_ssl_allow_any_cert;
static const command_line::arg_descriptor<std::string> arg_bootstrap_daemon_address;
static const command_line::arg_descriptor<std::string> arg_bootstrap_daemon_login;
static const command_line::arg_descriptor<int> arg_rpc_long_poll_connections;
typedef epee::net_utils::connection_context_base connection_context;
@ -323,7 +324,10 @@ namespace cryptonote
}
#endif
int m_max_long_poll_connections;
private:
std::atomic<int> m_long_poll_active_connections;
bool check_core_busy();
bool check_core_ready();

View File

@ -83,6 +83,7 @@ namespace cryptonote
#define CORE_RPC_STATUS_OK "OK"
#define CORE_RPC_STATUS_BUSY "BUSY"
#define CORE_RPC_STATUS_NOT_MINING "NOT MINING"
constexpr char const CORE_RPC_STATUS_TX_LONG_POLL_TIMED_OUT[] = "Long polling client timed out before txpool had an update";
// When making *any* change here, bump minor
// If the change is incompatible, then bump major and set minor to 0
@ -1535,7 +1536,11 @@ namespace cryptonote
{
struct request_t
{
bool long_poll; // Optional: If true, this call is blocking until timeout OR tx pool has changed since the last query. TX pool change is detected by comparing the hash of all the hashes in the tx pool.
crypto::hash tx_pool_checksum; // Optional: If `long_poll` is true the caller must pass the hashes of all their known tx pool hashes, XOR'ed together.
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE_OPT(long_poll, false)
KV_SERIALIZE_VAL_POD_AS_BLOB_OPT(tx_pool_checksum, crypto::hash{})
END_KV_SERIALIZE_MAP()
};
typedef epee::misc_utils::struct_init<request_t> request;

View File

@ -4999,17 +4999,25 @@ boost::optional<epee::wipeable_string> simple_wallet::on_get_password(const char
// can't ask for password from a background thread
if (!m_in_manual_refresh.load(std::memory_order_relaxed))
{
message_writer(console_color_red, false) << boost::format(tr("Password needed (%s) - use the refresh command")) % reason;
m_cmd_binder.print_prompt();
crypto::hash tx_pool_checksum = m_wallet->get_long_poll_tx_pool_checksum();
if (m_password_asked_on_height != m_wallet->get_blockchain_current_height() ||
m_password_asked_on_checksum != tx_pool_checksum)
{
m_password_asked_on_height = m_wallet->get_blockchain_current_height();
m_password_asked_on_checksum = tx_pool_checksum;
message_writer(console_color_red, false) << boost::format(tr("Password needed %s")) % reason;
m_cmd_binder.print_prompt();
}
return boost::none;
}
#ifdef HAVE_READLINE
rdln::suspend_readline pause_readline;
#endif
std::string msg = tr("Enter password");
std::string msg = tr("Enter password ");
if (reason && *reason)
msg += std::string(" (") + reason + ")";
msg += reason;
auto pwd_container = tools::password_container::prompt(false, msg.c_str());
if (!pwd_container)
{
@ -8320,7 +8328,18 @@ bool simple_wallet::run()
refresh_main(0, ResetNone, true);
m_auto_refresh_enabled = m_wallet->auto_refresh();
m_idle_thread = boost::thread([&]{wallet_idle_thread();});
m_idle_thread = boost::thread([&] { wallet_idle_thread(); });
m_long_poll_thread = boost::thread([&] {
for (;;)
{
try
{
if (m_auto_refresh_enabled && m_wallet->long_poll_pool_state())
m_idle_cond.notify_one();
}
catch (...) { }
}
});
message_writer(console_color_green, false) << "Background refresh thread started";
@ -9349,7 +9368,6 @@ bool simple_wallet::show_transfer(const std::vector<std::string> &args)
try
{
m_wallet->update_pool_state();
std::list<std::pair<crypto::hash, tools::wallet2::pool_payment_details>> pool_payments;
m_wallet->get_unconfirmed_payments(pool_payments, m_current_subaddress_account);
for (std::list<std::pair<crypto::hash, tools::wallet2::pool_payment_details>>::const_iterator i = pool_payments.begin(); i != pool_payments.end(); ++i) {

View File

@ -416,6 +416,7 @@ namespace cryptonote
std::atomic<bool> m_idle_run;
boost::thread m_idle_thread;
boost::thread m_long_poll_thread;
boost::mutex m_idle_mutex;
boost::condition_variable m_idle_cond;
@ -425,6 +426,8 @@ namespace cryptonote
uint32_t m_current_subaddress_account;
bool m_long_payment_id_support;
std::atomic<uint64_t> m_password_asked_on_height;
crypto::hash m_password_asked_on_checksum;
// MMS
mms::message_store& get_message_store() const { return m_wallet->get_message_store(); };

View File

@ -1,6 +1,6 @@
#define DEF_LOKI_VERSION_MAJOR 6
#define DEF_LOKI_VERSION_MINOR 1
#define DEF_LOKI_VERSION_PATCH 0
#define DEF_LOKI_VERSION_PATCH 1
#define LOKI_STRINGIFY2(val) #val
#define LOKI_STRINGIFY(val) LOKI_STRINGIFY2(val)

View File

@ -37,7 +37,7 @@ namespace tools
static const std::chrono::seconds rpc_timeout = std::chrono::minutes(3) + std::chrono::seconds(30);
NodeRPCProxy::NodeRPCProxy(epee::net_utils::http::http_simple_client &http_client, boost::recursive_mutex &mutex)
NodeRPCProxy::NodeRPCProxy(epee::net_utils::http::http_simple_client &http_client, std::recursive_mutex &mutex)
: m_http_client(http_client)
, m_daemon_rpc_mutex(mutex)
, m_offline(false)
@ -338,7 +338,7 @@ std::vector<cryptonote::COMMAND_RPC_GET_SERVICE_NODES::response::entry> NodeRPCP
return result;
{
boost::lock_guard<boost::recursive_mutex> lock(m_daemon_rpc_mutex);
std::lock_guard<std::recursive_mutex> lock(m_daemon_rpc_mutex);
if (m_all_service_nodes_cached_height != height && !update_all_service_nodes_cache(height, failed))
return result;
@ -360,7 +360,7 @@ std::vector<cryptonote::COMMAND_RPC_GET_SERVICE_NODES::response::entry> NodeRPCP
return result;
{
boost::lock_guard<boost::recursive_mutex> lock(m_daemon_rpc_mutex);
std::lock_guard<std::recursive_mutex> lock(m_daemon_rpc_mutex);
if (m_contributed_service_nodes_cached_height != height || m_contributed_service_nodes_cached_address != contributor) {
if (m_all_service_nodes_cached_height != height && !update_all_service_nodes_cache(height, failed))
@ -399,7 +399,7 @@ std::vector<cryptonote::COMMAND_RPC_GET_SERVICE_NODE_BLACKLISTED_KEY_IMAGES::ent
return result;
{
boost::lock_guard<boost::recursive_mutex> lock(m_daemon_rpc_mutex);
std::lock_guard<std::recursive_mutex> lock(m_daemon_rpc_mutex);
if (m_service_node_blacklisted_key_images_cached_height != height)
{
cryptonote::COMMAND_RPC_GET_SERVICE_NODE_BLACKLISTED_KEY_IMAGES::request req = {};

View File

@ -29,7 +29,7 @@
#pragma once
#include <string>
#include <boost/thread/mutex.hpp>
#include <mutex>
#include "include_base_utils.h"
#include "net/http_client.h"
#include "rpc/core_rpc_server_commands_defs.h"
@ -40,7 +40,7 @@ namespace tools
class NodeRPCProxy
{
public:
NodeRPCProxy(epee::net_utils::http::http_simple_client &http_client, boost::recursive_mutex &mutex);
NodeRPCProxy(epee::net_utils::http::http_simple_client &http_client, std::recursive_mutex &mutex);
void invalidate();
void set_offline(bool offline) { m_offline = offline; }
@ -65,7 +65,7 @@ private:
boost::optional<std::string> get_info() const;
epee::net_utils::http::http_simple_client &m_http_client;
boost::recursive_mutex &m_daemon_rpc_mutex;
std::recursive_mutex &m_daemon_rpc_mutex;
bool m_offline;
mutable uint64_t m_service_node_blacklisted_key_images_cached_height;

View File

@ -1192,16 +1192,27 @@ std::unique_ptr<wallet2> wallet2::make_dummy(const boost::program_options::varia
//----------------------------------------------------------------------------------------------------
bool wallet2::set_daemon(std::string daemon_address, boost::optional<epee::net_utils::http::login> daemon_login, bool trusted_daemon, epee::net_utils::ssl_options_t ssl_options)
{
boost::lock_guard<boost::recursive_mutex> lock(m_daemon_rpc_mutex);
if(m_http_client.is_connected())
m_http_client.disconnect();
m_daemon_address = std::move(daemon_address);
m_daemon_login = std::move(daemon_login);
m_daemon_login = std::move(daemon_login);
m_trusted_daemon = trusted_daemon;
MINFO("setting daemon to " << get_daemon_address());
return m_http_client.set_server(get_daemon_address(), get_daemon_login(), std::move(ssl_options));
bool result = true;
{
std::lock_guard<std::recursive_mutex> daemon_mutex(m_daemon_rpc_mutex);
if(m_http_client.is_connected())
m_http_client.disconnect();
result &= m_http_client.set_server(get_daemon_address(), get_daemon_login(), ssl_options);
}
{
std::lock_guard<std::recursive_mutex> long_poll_mutex(m_long_poll_mutex);
if(m_long_poll_client.is_connected())
m_long_poll_client.disconnect();
result &= m_long_poll_client.set_server(get_daemon_address(), get_daemon_login(), std::move(ssl_options));
}
return result;
}
//----------------------------------------------------------------------------------------------------
bool wallet2::init(std::string daemon_address, boost::optional<epee::net_utils::http::login> daemon_login, boost::asio::ip::tcp::endpoint proxy, uint64_t upper_transaction_weight_limit, bool trusted_daemon, epee::net_utils::ssl_options_t ssl_options)
@ -1209,7 +1220,10 @@ bool wallet2::init(std::string daemon_address, boost::optional<epee::net_utils::
m_is_initialized = true;
m_upper_transaction_weight_limit = upper_transaction_weight_limit;
if (proxy != boost::asio::ip::tcp::endpoint{})
{
m_long_poll_client.set_connector(net::socks::connector{proxy});
m_http_client.set_connector(net::socks::connector{std::move(proxy)});
}
return set_daemon(daemon_address, daemon_login, trusted_daemon, std::move(ssl_options));
}
//----------------------------------------------------------------------------------------------------
@ -1622,7 +1636,14 @@ void wallet2::scan_output(const cryptonote::transaction &tx, bool miner_tx, cons
CRITICAL_REGION_LOCAL(password_lock);
if (!m_encrypt_keys_after_refresh)
{
boost::optional<epee::wipeable_string> pwd = m_callback->on_get_password(pool ? blink ? "blink output receive in pool" : "output found in pool" : "output received");
char const blink_reason[] = "(blink output received in pool) - use the refresh command";
char const pool_reason[] = "(output received in pool) - use the refresh, then show_transfers command";
char const block_reason[] = "(output received) - use the refresh command";
char const *reason = block_reason;
if (pool) reason = (blink) ? blink_reason : pool_reason;
boost::optional<epee::wipeable_string> pwd = m_callback->on_get_password(reason);
THROW_WALLET_EXCEPTION_IF(!pwd, error::password_needed, tr("Password is needed to compute key image for incoming LOKI"));
THROW_WALLET_EXCEPTION_IF(!verify_password(*pwd), error::password_needed, tr("Invalid password: password is needed to compute key image for incoming LOKI"));
decrypt_keys(*pwd);
@ -2842,11 +2863,46 @@ void wallet2::remove_obsolete_pool_txs(const std::vector<crypto::hash> &tx_hashe
}
}
}
//----------------------------------------------------------------------------------------------------
bool wallet2::long_poll_pool_state()
{
cryptonote::COMMAND_RPC_GET_TRANSACTION_POOL_HASHES_BIN::request req = {};
cryptonote::COMMAND_RPC_GET_TRANSACTION_POOL_HASHES_BIN::response res = {};
req.long_poll = true;
req.tx_pool_checksum = m_long_poll_tx_pool_checksum;
bool r = false;
{
std::lock_guard<decltype(m_long_poll_mutex)> lock(m_long_poll_mutex);
r = epee::net_utils::invoke_http_json("/get_transaction_pool_hashes.bin", req, res, m_long_poll_client, rpc_long_poll_timeout, "GET");
}
THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "get_transaction_pool_hashes.bin");
THROW_WALLET_EXCEPTION_IF(res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_transaction_pool_hashes.bin");
THROW_WALLET_EXCEPTION_IF(res.status != CORE_RPC_STATUS_TX_LONG_POLL_TIMED_OUT &&
res.status != CORE_RPC_STATUS_OK, error::get_tx_pool_error, res.status);
bool result = res.status == CORE_RPC_STATUS_OK;
if (result)
{
m_long_poll_tx_pool_checksum = {};
for (crypto::hash const &hash : res.tx_hashes)
crypto::hash_xor(m_long_poll_tx_pool_checksum, hash);
{
std::lock_guard<decltype(m_long_poll_tx_pool_cache_mutex)> lock(m_long_poll_tx_pool_cache_mutex);
m_long_poll_tx_pool_cache = std::move(res.tx_hashes);
}
}
return result;
}
//----------------------------------------------------------------------------------------------------
void wallet2::update_pool_state(bool refreshed)
{
MTRACE("update_pool_state start");
MTRACE("update_pool_state: take hashes from cache");
std::vector<crypto::hash> tx_hashes;
{
std::lock_guard<decltype(m_long_poll_tx_pool_cache_mutex)> lock(m_long_poll_tx_pool_cache_mutex);
tx_hashes = m_long_poll_tx_pool_cache;
}
auto keys_reencryptor = epee::misc_utils::create_scope_leave_handler([&, this]() {
if (m_encrypt_keys_after_refresh)
@ -2856,24 +2912,13 @@ void wallet2::update_pool_state(bool refreshed)
}
});
// get the pool state
cryptonote::COMMAND_RPC_GET_TRANSACTION_POOL_HASHES_BIN::request req;
cryptonote::COMMAND_RPC_GET_TRANSACTION_POOL_HASHES_BIN::response res;
m_daemon_rpc_mutex.lock();
bool r = invoke_http_json("/get_transaction_pool_hashes.bin", req, res, rpc_timeout);
m_daemon_rpc_mutex.unlock();
THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "get_transaction_pool_hashes.bin");
THROW_WALLET_EXCEPTION_IF(res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_transaction_pool_hashes.bin");
THROW_WALLET_EXCEPTION_IF(res.status != CORE_RPC_STATUS_OK, error::get_tx_pool_error);
MTRACE("update_pool_state got pool");
// remove any pending tx that's not in the pool
std::unordered_map<crypto::hash, wallet2::unconfirmed_transfer_details>::iterator it = m_unconfirmed_txs.begin();
while (it != m_unconfirmed_txs.end())
{
const crypto::hash &txid = it->first;
bool found = false;
for (const auto &it2: res.tx_hashes)
for (const auto &it2: tx_hashes)
{
if (it2 == txid)
{
@ -2929,13 +2974,13 @@ void wallet2::update_pool_state(bool refreshed)
// the in transfers list instead (or nowhere if it just
// disappeared without being mined)
if (refreshed)
remove_obsolete_pool_txs(res.tx_hashes);
remove_obsolete_pool_txs(tx_hashes);
MTRACE("update_pool_state done second loop");
// gather txids of new pool txes to us
std::vector<std::pair<crypto::hash, bool>> txids;
for (const auto &txid: res.tx_hashes)
for (const auto &txid: tx_hashes)
{
bool txid_found_in_up = false;
for (const auto &up: m_unconfirmed_payments)
@ -3006,9 +3051,7 @@ void wallet2::update_pool_state(bool refreshed)
MDEBUG("asking for " << txids.size() << " transactions");
req.decode_as_json = false;
req.prune = true;
m_daemon_rpc_mutex.lock();
bool r = invoke_http_json("/gettransactions", req, res, rpc_timeout);
m_daemon_rpc_mutex.unlock();
MDEBUG("Got " << r << " and " << res.status);
if (r && res.status == CORE_RPC_STATUS_OK)
{
@ -5340,7 +5383,7 @@ bool wallet2::check_connection(uint32_t *version, bool *ssl, uint32_t timeout)
}
{
boost::lock_guard<boost::recursive_mutex> lock(m_daemon_rpc_mutex);
std::lock_guard <decltype(m_daemon_rpc_mutex)> lock(m_daemon_rpc_mutex);
if(!m_http_client.is_connected(ssl))
{
m_node_rpc_proxy.invalidate();
@ -5375,10 +5418,18 @@ void wallet2::set_offline(bool offline)
m_http_client.set_auto_connect(!offline);
if (offline)
{
boost::lock_guard<boost::recursive_mutex> lock(m_daemon_rpc_mutex);
std::lock_guard<std::recursive_mutex> lock(m_daemon_rpc_mutex);
if(m_http_client.is_connected())
m_http_client.disconnect();
}
m_long_poll_client.set_auto_connect(!offline);
if (offline)
{
std::lock_guard<std::recursive_mutex> lock(m_long_poll_mutex);
if(m_long_poll_client.is_connected())
m_long_poll_client.disconnect();
}
}
//----------------------------------------------------------------------------------------------------
bool wallet2::generate_chacha_key_from_secret_keys(crypto::chacha_key &key) const
@ -5998,11 +6049,6 @@ void wallet2::get_transfers(get_transfers_args_t args, std::vector<transfer_view
MDEBUG("Getting transfers of type(s) " << (args.in ? "in " : "") << (args.out ? "out " : "") << (args.pending ? "pending " : "") << (args.failed ? "failed " : "")
<< (args.pool ? "pool " : "") << " for heights in [" << args.min_height << "," << args.max_height << "]");
if ((args.in || args.out || args.pool) && is_connected())
{
update_pool_state();
}
size_t size = 0;
if (args.in)
{
@ -7797,7 +7843,7 @@ bool wallet2::find_and_save_rings(bool force)
req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txs_hashes[s]));
bool r;
{
const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex};
const std::lock_guard<std::recursive_mutex> lock{m_daemon_rpc_mutex};
r = invoke_http_json("/gettransactions", req, res, rpc_timeout);
}
THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "gettransactions");
@ -11426,7 +11472,7 @@ void wallet2::set_tx_key(const crypto::hash &txid, const crypto::secret_key &tx_
COMMAND_RPC_GET_TRANSACTIONS::response res{};
bool r;
{
const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex};
const std::lock_guard<std::recursive_mutex> lock{m_daemon_rpc_mutex};
r = invoke_http_json("/gettransactions", req, res, rpc_timeout);
}
THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "gettransactions");
@ -11476,7 +11522,7 @@ std::string wallet2::get_spend_proof(const crypto::hash &txid, const std::string
COMMAND_RPC_GET_TRANSACTIONS::response res{};
bool r;
{
const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex};
const std::lock_guard<std::recursive_mutex> lock{m_daemon_rpc_mutex};
r = invoke_http_json("/gettransactions", req, res, rpc_timeout);
}
THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "gettransactions");
@ -11538,7 +11584,7 @@ std::string wallet2::get_spend_proof(const crypto::hash &txid, const std::string
COMMAND_RPC_GET_OUTPUTS_BIN::response res{};
bool r;
{
const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex};
const std::lock_guard<std::recursive_mutex> lock{m_daemon_rpc_mutex};
r = invoke_http_bin("/get_outs.bin", req, res, rpc_timeout);
}
THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "get_outs.bin");
@ -11594,7 +11640,7 @@ bool wallet2::check_spend_proof(const crypto::hash &txid, const std::string &mes
COMMAND_RPC_GET_TRANSACTIONS::response res{};
bool r;
{
const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex};
const std::lock_guard<std::recursive_mutex> lock{m_daemon_rpc_mutex};
r = invoke_http_json("/gettransactions", req, res, rpc_timeout);
}
THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "gettransactions");
@ -11667,7 +11713,7 @@ bool wallet2::check_spend_proof(const crypto::hash &txid, const std::string &mes
COMMAND_RPC_GET_OUTPUTS_BIN::response res{};
bool r;
{
const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex};
const std::lock_guard<std::recursive_mutex> lock{m_daemon_rpc_mutex};
r = invoke_http_bin("/get_outs.bin", req, res, rpc_timeout);
}
THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "get_outs.bin");
@ -14306,11 +14352,11 @@ void wallet2::finish_rescan_bc_keep_key_images(uint64_t transfer_height, const c
//----------------------------------------------------------------------------------------------------
uint64_t wallet2::get_bytes_sent() const
{
return m_http_client.get_bytes_sent();
return m_http_client.get_bytes_sent() + m_long_poll_client.get_bytes_sent();
}
//----------------------------------------------------------------------------------------------------
uint64_t wallet2::get_bytes_received() const
{
return m_http_client.get_bytes_received();
return m_http_client.get_bytes_received() + m_long_poll_client.get_bytes_received();
}
}

View File

@ -314,6 +314,7 @@ private:
public:
static constexpr uint32_t BLINK_PRIORITY = 0x626c6e6b; // "blnk"
static constexpr const std::chrono::seconds rpc_timeout = std::chrono::minutes(3) + std::chrono::seconds(30);
static constexpr const auto rpc_long_poll_timeout = std::chrono::seconds(15);
enum RefreshType {
RefreshFull,
@ -1336,6 +1337,22 @@ private:
bool import_key_images(signed_tx_set & signed_tx, size_t offset=0, bool only_selected_transfers=false);
crypto::public_key get_tx_pub_key_from_received_outs(const tools::wallet2::transfer_details &td) const;
crypto::hash get_long_poll_tx_pool_checksum() const
{
std::lock_guard<decltype(m_long_poll_tx_pool_cache_mutex)> lock(m_long_poll_tx_pool_cache_mutex);
return m_long_poll_tx_pool_checksum;
};
// long_poll_pool_state is blocking and does NOT return to the caller until
// the daemon detects a change in the contents of the txpool by comparing
// our last tx pool checksum with theirs.
// This call also takes the long poll mutex and uses it's own individual
// http client that it exclusively owns.
// Returns true if call succeeded, false if the long poll timed out, throws
// if a network error.
bool long_poll_pool_state();
void update_pool_state(bool refreshed = false);
void remove_obsolete_pool_txs(const std::vector<crypto::hash> &tx_hashes);
@ -1415,21 +1432,21 @@ private:
inline bool invoke_http_json(const boost::string_ref uri, const t_request& req, t_response& res, std::chrono::milliseconds timeout = std::chrono::seconds(15), const boost::string_ref http_method = "GET")
{
if (m_offline) return false;
boost::lock_guard<boost::recursive_mutex> lock(m_daemon_rpc_mutex);
std::lock_guard<std::recursive_mutex> lock(m_daemon_rpc_mutex);
return epee::net_utils::invoke_http_json(uri, req, res, m_http_client, timeout, http_method);
}
template<class t_request, class t_response>
inline bool invoke_http_bin(const boost::string_ref uri, const t_request& req, t_response& res, std::chrono::milliseconds timeout = std::chrono::seconds(15), const boost::string_ref http_method = "GET")
{
if (m_offline) return false;
boost::lock_guard<boost::recursive_mutex> lock(m_daemon_rpc_mutex);
std::lock_guard<std::recursive_mutex> lock(m_daemon_rpc_mutex);
return epee::net_utils::invoke_http_bin(uri, req, res, m_http_client, timeout, http_method);
}
template<class t_request, class t_response>
inline bool invoke_http_json_rpc(const boost::string_ref uri, const std::string& method_name, const t_request& req, t_response& res, std::chrono::milliseconds timeout = std::chrono::seconds(15), const boost::string_ref http_method = "GET", const std::string& req_id = "0")
{
if (m_offline) return false;
boost::lock_guard<boost::recursive_mutex> lock(m_daemon_rpc_mutex);
std::lock_guard<std::recursive_mutex> lock(m_daemon_rpc_mutex);
return epee::net_utils::invoke_http_json_rpc(uri, method_name, req, res, m_http_client, timeout, http_method, req_id);
}
@ -1548,6 +1565,7 @@ private:
void finish_rescan_bc_keep_key_images(uint64_t transfer_height, const crypto::hash &hash);
void set_offline(bool offline = true);
private:
/*!
* \brief Stores wallet information to wallet file.
@ -1660,6 +1678,12 @@ private:
std::unordered_map<crypto::hash, crypto::secret_key> m_tx_keys;
std::unordered_map<crypto::hash, std::vector<crypto::secret_key>> m_additional_tx_keys;
std::recursive_mutex m_long_poll_mutex;
epee::net_utils::http::http_simple_client m_long_poll_client;
mutable std::mutex m_long_poll_tx_pool_cache_mutex;
std::vector<crypto::hash> m_long_poll_tx_pool_cache;
crypto::hash m_long_poll_tx_pool_checksum = {};
transfer_container m_transfers;
payment_container m_payments;
std::unordered_map<crypto::key_image, size_t> m_key_images;
@ -1678,7 +1702,7 @@ private:
std::atomic<bool> m_run;
boost::recursive_mutex m_daemon_rpc_mutex;
std::recursive_mutex m_daemon_rpc_mutex;
bool m_trusted_daemon;
i_wallet2_callback* m_callback;

View File

@ -416,8 +416,8 @@ namespace tools
//----------------------------------------------------------------------------------------------------
struct get_tx_pool_error : public refresh_error
{
explicit get_tx_pool_error(std::string&& loc)
: refresh_error(std::move(loc), "Error getting transaction pool")
explicit get_tx_pool_error(std::string&& loc, const std::string &message = "")
: refresh_error(std::move(loc), "Error getting transaction pool " + message)
{
}

View File

@ -120,6 +120,7 @@ namespace tools
m_last_auto_refresh_time = boost::posix_time::microsec_clock::universal_time();
return true;
}, 1000);
m_net_server.add_idle_handler([this](){
if (m_stop.load(std::memory_order_relaxed))
{
@ -129,6 +130,22 @@ namespace tools
return true;
}, 500);
m_net_server.add_idle_handler([this](){
if (m_auto_refresh_period == 0 || !m_wallet)
return true;
try
{
if (m_wallet->long_poll_pool_state())
m_wallet->refresh(m_wallet->is_trusted_daemon());
}
catch (const std::exception &ex)
{
// NOTE: Don't care about error, non fatal.
}
return true;
}, tools::wallet2::rpc_long_poll_timeout.count());
//DO NOT START THIS SERVER IN MORE THEN 1 THREADS WITHOUT REFACTORING
return epee::http_server_impl_base<wallet_rpc_server, connection_context>::run(1, true);
}
@ -2403,8 +2420,6 @@ namespace tools
}
}
m_wallet->update_pool_state();
std::list<std::pair<crypto::hash, tools::wallet2::pool_payment_details>> pool_payments;
m_wallet->get_unconfirmed_payments(pool_payments, req.account_index);
for (std::list<std::pair<crypto::hash, tools::wallet2::pool_payment_details>>::const_iterator i = pool_payments.begin(); i != pool_payments.end(); ++i) {