aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/blockchain_db/lmdb/db_lmdb.cpp10
-rw-r--r--src/common/download.cpp10
-rw-r--r--src/crypto/slow-hash.c140
-rw-r--r--src/cryptonote_protocol/cryptonote_protocol_handler.inl1
-rw-r--r--src/device/device_ledger.hpp2
-rw-r--r--src/device_trezor/device_trezor.cpp4
-rw-r--r--src/net/socks.cpp5
-rw-r--r--src/simplewallet/simplewallet.cpp14
-rw-r--r--src/wallet/api/wallet.cpp2
-rw-r--r--src/wallet/api/wallet_manager.cpp5
-rw-r--r--src/wallet/api/wallet_manager.h2
-rw-r--r--src/wallet/wallet2.cpp35
-rw-r--r--src/wallet/wallet2.h4
-rw-r--r--src/wallet/wallet_rpc_helpers.h1
14 files changed, 145 insertions, 90 deletions
diff --git a/src/blockchain_db/lmdb/db_lmdb.cpp b/src/blockchain_db/lmdb/db_lmdb.cpp
index c059a3e29..de2700ad0 100644
--- a/src/blockchain_db/lmdb/db_lmdb.cpp
+++ b/src/blockchain_db/lmdb/db_lmdb.cpp
@@ -1276,11 +1276,11 @@ BlockchainLMDB::~BlockchainLMDB()
// batch transaction shouldn't be active at this point. If it is, consider it aborted.
if (m_batch_active)
{
- try { batch_abort(); }
+ try { BlockchainLMDB::batch_abort(); }
catch (...) { /* ignore */ }
}
if (m_open)
- close();
+ BlockchainLMDB::close();
}
BlockchainLMDB::BlockchainLMDB(bool batch_transactions): BlockchainDB()
@@ -1578,9 +1578,9 @@ void BlockchainLMDB::close()
if (m_batch_active)
{
LOG_PRINT_L3("close() first calling batch_abort() due to active batch transaction");
- batch_abort();
+ BlockchainLMDB::batch_abort();
}
- this->sync();
+ BlockchainLMDB::sync();
m_tinfo.reset();
// FIXME: not yet thread safe!!! Use with care.
@@ -1593,7 +1593,7 @@ void BlockchainLMDB::sync()
LOG_PRINT_L3("BlockchainLMDB::" << __func__);
check_open();
- if (is_read_only())
+ if (BlockchainLMDB::is_read_only())
return;
// Does nothing unless LMDB environment was opened with MDB_NOSYNC or in part
diff --git a/src/common/download.cpp b/src/common/download.cpp
index 3dd9c3976..c3dfa43d5 100644
--- a/src/common/download.cpp
+++ b/src/common/download.cpp
@@ -53,7 +53,7 @@ namespace tools
download_thread_control(const std::string &path, const std::string &uri, std::function<void(const std::string&, const std::string&, bool)> result_cb, std::function<bool(const std::string&, const std::string&, size_t, ssize_t)> progress_cb):
path(path), uri(uri), result_cb(result_cb), progress_cb(progress_cb), stop(false), stopped(false), success(false) {}
- ~download_thread_control() { if (thread.joinable()) thread.detach(); }
+ ~download_thread_control() { if (thread.joinable()) { thread.detach(); thread = {}; } }
};
static void download_thread(download_async_handle control)
@@ -293,9 +293,13 @@ namespace tools
{
boost::lock_guard<boost::mutex> lock(control->mutex);
if (control->stopped)
+ {
+ control->thread = {};
return true;
+ }
}
control->thread.join();
+ control->thread = {};
return true;
}
@@ -305,10 +309,14 @@ namespace tools
{
boost::lock_guard<boost::mutex> lock(control->mutex);
if (control->stopped)
+ {
+ control->thread = {};
return true;
+ }
control->stop = true;
}
control->thread.join();
+ control->thread = {};
return true;
}
}
diff --git a/src/crypto/slow-hash.c b/src/crypto/slow-hash.c
index 5a773f3cf..53628ab18 100644
--- a/src/crypto/slow-hash.c
+++ b/src/crypto/slow-hash.c
@@ -89,6 +89,28 @@ static inline int use_v4_jit(void)
#endif
}
+#if defined(__x86_64__) || defined(__aarch64__)
+static inline int force_software_aes(void)
+{
+ static int use = -1;
+
+ if (use != -1)
+ return use;
+
+ const char *env = getenv("MONERO_USE_SOFTWARE_AES");
+ if (!env) {
+ use = 0;
+ }
+ else if (!strcmp(env, "0") || !strcmp(env, "no")) {
+ use = 0;
+ }
+ else {
+ use = 1;
+ }
+ return use;
+}
+#endif
+
#define VARIANT1_1(p) \
do if (variant == 1) \
{ \
@@ -498,25 +520,6 @@ STATIC INLINE void xor64(uint64_t *a, const uint64_t b)
* @return true if the CPU supports AES, false otherwise
*/
-STATIC INLINE int force_software_aes(void)
-{
- static int use = -1;
-
- if (use != -1)
- return use;
-
- const char *env = getenv("MONERO_USE_SOFTWARE_AES");
- if (!env) {
- use = 0;
- }
- else if (!strcmp(env, "0") || !strcmp(env, "no")) {
- use = 0;
- }
- else {
- use = 1;
- }
- return use;
-}
STATIC INLINE int check_aes_hw(void)
{
@@ -1060,6 +1063,23 @@ union cn_slow_hash_state
* and moving between vector and regular registers stalls the pipeline.
*/
#include <arm_neon.h>
+#ifndef __APPLE__
+#include <sys/auxv.h>
+#include <asm/hwcap.h>
+#endif
+
+STATIC INLINE int check_aes_hw(void)
+{
+#ifdef __APPLE__
+ return 1;
+#else
+ static int supported = -1;
+
+ if(supported < 0)
+ supported = (getauxval(AT_HWCAP) & HWCAP_AES) != 0;
+ return supported;
+#endif
+}
#define TOTALBLOCKS (MEMORY / AES_BLOCK_SIZE)
@@ -1156,7 +1176,6 @@ __asm__(
STATIC INLINE void aes_pseudo_round(const uint8_t *in, uint8_t *out, const uint8_t *expandedKey, int nblocks)
{
const uint8x16_t *k = (const uint8x16_t *)expandedKey, zero = {0};
- uint8x16_t tmp;
int i;
for (i=0; i<nblocks; i++)
@@ -1191,7 +1210,6 @@ STATIC INLINE void aes_pseudo_round_xor(const uint8_t *in, uint8_t *out, const u
{
const uint8x16_t *k = (const uint8x16_t *)expandedKey;
const uint8x16_t *x = (const uint8x16_t *)xor;
- uint8x16_t tmp;
int i;
for (i=0; i<nblocks; i++)
@@ -1244,6 +1262,12 @@ STATIC INLINE void aligned_free(void *ptr)
}
#endif /* FORCE_USE_HEAP */
+STATIC INLINE void xor_blocks(uint8_t* a, const uint8_t* b)
+{
+ U64(a)[0] ^= U64(b)[0];
+ U64(a)[1] ^= U64(b)[1];
+}
+
void cn_slow_hash(const void *data, size_t length, char *hash, int variant, int prehashed, uint64_t height)
{
RDATA_ALIGN16 uint8_t expandedKey[240];
@@ -1264,6 +1288,8 @@ void cn_slow_hash(const void *data, size_t length, char *hash, int variant, int
size_t i, j;
uint64_t *p = NULL;
+ oaes_ctx *aes_ctx = NULL;
+ int useAes = !force_software_aes() && check_aes_hw();
static void (*const extra_hashes[4])(const void *, size_t, char *) =
{
@@ -1287,11 +1313,26 @@ void cn_slow_hash(const void *data, size_t length, char *hash, int variant, int
* the 2MB large random access buffer.
*/
- aes_expand_key(state.hs.b, expandedKey);
- for(i = 0; i < MEMORY / INIT_SIZE_BYTE; i++)
+ if(useAes)
{
- aes_pseudo_round(text, text, expandedKey, INIT_SIZE_BLK);
- memcpy(&local_hp_state[i * INIT_SIZE_BYTE], text, INIT_SIZE_BYTE);
+ aes_expand_key(state.hs.b, expandedKey);
+ for(i = 0; i < MEMORY / INIT_SIZE_BYTE; i++)
+ {
+ aes_pseudo_round(text, text, expandedKey, INIT_SIZE_BLK);
+ memcpy(&local_hp_state[i * INIT_SIZE_BYTE], text, INIT_SIZE_BYTE);
+ }
+ }
+ else
+ {
+ aes_ctx = (oaes_ctx *) oaes_alloc();
+ oaes_key_import_data(aes_ctx, state.hs.b, AES_KEY_SIZE);
+ for(i = 0; i < MEMORY / INIT_SIZE_BYTE; i++)
+ {
+ for(j = 0; j < INIT_SIZE_BLK; j++)
+ aesb_pseudo_round(&text[AES_BLOCK_SIZE * j], &text[AES_BLOCK_SIZE * j], aes_ctx->key->exp_data);
+
+ memcpy(&local_hp_state[i * INIT_SIZE_BYTE], text, INIT_SIZE_BYTE);
+ }
}
U64(a)[0] = U64(&state.k[0])[0] ^ U64(&state.k[32])[0];
@@ -1307,13 +1348,26 @@ void cn_slow_hash(const void *data, size_t length, char *hash, int variant, int
_b = vld1q_u8((const uint8_t *)b);
_b1 = vld1q_u8(((const uint8_t *)b) + AES_BLOCK_SIZE);
- for(i = 0; i < ITER / 2; i++)
+ if(useAes)
+ {
+ for(i = 0; i < ITER / 2; i++)
+ {
+ pre_aes();
+ _c = vaeseq_u8(_c, zero);
+ _c = vaesmcq_u8(_c);
+ _c = veorq_u8(_c, _a);
+ post_aes();
+ }
+ }
+ else
{
- pre_aes();
- _c = vaeseq_u8(_c, zero);
- _c = vaesmcq_u8(_c);
- _c = veorq_u8(_c, _a);
- post_aes();
+ for(i = 0; i < ITER / 2; i++)
+ {
+ pre_aes();
+ aesb_single_round((uint8_t *) &_c, (uint8_t *) &_c, (uint8_t *) &_a);
+ post_aes();
+ }
+
}
/* CryptoNight Step 4: Sequentially pass through the mixing buffer and use 10 rounds
@@ -1322,11 +1376,27 @@ void cn_slow_hash(const void *data, size_t length, char *hash, int variant, int
memcpy(text, state.init, INIT_SIZE_BYTE);
- aes_expand_key(&state.hs.b[32], expandedKey);
- for(i = 0; i < MEMORY / INIT_SIZE_BYTE; i++)
+ if(useAes)
+ {
+ aes_expand_key(&state.hs.b[32], expandedKey);
+ for(i = 0; i < MEMORY / INIT_SIZE_BYTE; i++)
+ {
+ // add the xor to the pseudo round
+ aes_pseudo_round_xor(text, text, expandedKey, &local_hp_state[i * INIT_SIZE_BYTE], INIT_SIZE_BLK);
+ }
+ }
+ else
{
- // add the xor to the pseudo round
- aes_pseudo_round_xor(text, text, expandedKey, &local_hp_state[i * INIT_SIZE_BYTE], INIT_SIZE_BLK);
+ oaes_key_import_data(aes_ctx, &state.hs.b[32], AES_KEY_SIZE);
+ for(i = 0; i < MEMORY / INIT_SIZE_BYTE; i++)
+ {
+ for(j = 0; j < INIT_SIZE_BLK; j++)
+ {
+ xor_blocks(&text[j * AES_BLOCK_SIZE], &local_hp_state[i * INIT_SIZE_BYTE + j * AES_BLOCK_SIZE]);
+ aesb_pseudo_round(&text[AES_BLOCK_SIZE * j], &text[AES_BLOCK_SIZE * j], aes_ctx->key->exp_data);
+ }
+ }
+ oaes_free((OAES_CTX **) &aes_ctx);
}
/* CryptoNight Step 5: Apply Keccak to the state again, and then
diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler.inl b/src/cryptonote_protocol/cryptonote_protocol_handler.inl
index 106253082..39d562fd1 100644
--- a/src/cryptonote_protocol/cryptonote_protocol_handler.inl
+++ b/src/cryptonote_protocol/cryptonote_protocol_handler.inl
@@ -153,6 +153,7 @@ namespace cryptonote
context.m_last_request_time = boost::date_time::not_a_date_time;
context.m_expect_response = 0;
context.m_expect_height = 0;
+ context.m_requested_objects.clear();
context.m_state = cryptonote_connection_context::state_standby; // we'll go back to adding, then (if we can't), download
}
else
diff --git a/src/device/device_ledger.hpp b/src/device/device_ledger.hpp
index 3b6cc505f..590ae41b5 100644
--- a/src/device/device_ledger.hpp
+++ b/src/device/device_ledger.hpp
@@ -166,8 +166,6 @@ namespace hw {
void send_secret(const unsigned char sec[32], int &offset);
void receive_secret(unsigned char sec[32], int &offset);
- // hw running mode
- device_mode mode;
bool tx_in_progress;
// map public destination key to ephemeral destination key
diff --git a/src/device_trezor/device_trezor.cpp b/src/device_trezor/device_trezor.cpp
index 03e8bbba4..0545f3f26 100644
--- a/src/device_trezor/device_trezor.cpp
+++ b/src/device_trezor/device_trezor.cpp
@@ -66,8 +66,8 @@ namespace trezor {
device_trezor::~device_trezor() {
try {
- disconnect();
- release();
+ device_trezor::disconnect();
+ device_trezor::release();
} catch(std::exception const& e){
MWARNING("Could not disconnect and release: " << e.what());
}
diff --git a/src/net/socks.cpp b/src/net/socks.cpp
index c2330bd41..6463e669e 100644
--- a/src/net/socks.cpp
+++ b/src/net/socks.cpp
@@ -321,8 +321,9 @@ namespace socks
{
if (self && self->proxy_.is_open())
{
- self->proxy_.shutdown(boost::asio::ip::tcp::socket::shutdown_both);
- self->proxy_.close();
+ boost::system::error_code ec;
+ self->proxy_.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
+ self->proxy_.close(ec);
}
});
}
diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp
index dc031b36c..fd784c5ae 100644
--- a/src/simplewallet/simplewallet.cpp
+++ b/src/simplewallet/simplewallet.cpp
@@ -6061,6 +6061,7 @@ bool simple_wallet::show_incoming_transfers(const std::vector<std::string>& args
auto local_args = args;
LOCK_IDLE_SCOPE();
+ std::set<uint32_t> subaddr_indices;
bool filter = false;
bool available = false;
bool verbose = false;
@@ -6086,6 +6087,11 @@ bool simple_wallet::show_incoming_transfers(const std::vector<std::string>& args
verbose = true;
else if (local_args[0] == "uses")
uses = true;
+ else if (local_args[0].substr(0, 6) == "index=")
+ {
+ if (!parse_subaddress_indices(local_args[0], subaddr_indices))
+ return true;
+ }
else
{
fail_msg_writer() << tr("Invalid keyword: ") << local_args.front();
@@ -6098,14 +6104,6 @@ bool simple_wallet::show_incoming_transfers(const std::vector<std::string>& args
PAUSE_READLINE();
- std::set<uint32_t> subaddr_indices;
- if (local_args.size() > 0 && local_args[0].substr(0, 6) == "index=")
- {
- if (!parse_subaddress_indices(local_args[0], subaddr_indices))
- return true;
- local_args.erase(local_args.begin());
- }
-
if (local_args.size() > 0)
{
PRINT_USAGE(USAGE_INCOMING_TRANSFERS);
diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp
index 0afbda705..989061250 100644
--- a/src/wallet/api/wallet.cpp
+++ b/src/wallet/api/wallet.cpp
@@ -450,7 +450,7 @@ WalletImpl::~WalletImpl()
LOG_PRINT_L1(__FUNCTION__);
m_wallet->callback(NULL);
// Pause refresh thread - prevents refresh from starting again
- pauseRefresh();
+ WalletImpl::pauseRefresh(); // Call the method directly (not polymorphically) to protect against UB in destructor.
// Close wallet - stores cache and stops ongoing refresh operation
close(false); // do not store wallet as part of the closing activities
// Stop refresh thread
diff --git a/src/wallet/api/wallet_manager.cpp b/src/wallet/api/wallet_manager.cpp
index 417a27db5..f5d5e2168 100644
--- a/src/wallet/api/wallet_manager.cpp
+++ b/src/wallet/api/wallet_manager.cpp
@@ -49,6 +49,11 @@ namespace epee {
namespace Monero {
+WalletManagerImpl::WalletManagerImpl()
+{
+ tools::set_strict_default_file_permissions(true);
+}
+
Wallet *WalletManagerImpl::createWallet(const std::string &path, const std::string &password,
const std::string &language, NetworkType nettype, uint64_t kdf_rounds)
{
diff --git a/src/wallet/api/wallet_manager.h b/src/wallet/api/wallet_manager.h
index cf3056a17..1e8cff877 100644
--- a/src/wallet/api/wallet_manager.h
+++ b/src/wallet/api/wallet_manager.h
@@ -95,7 +95,7 @@ public:
bool setProxy(const std::string &address) override;
private:
- WalletManagerImpl() {}
+ WalletManagerImpl();
friend struct WalletManagerFactory;
net::http::client m_http_client;
std::string m_errorString;
diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp
index 3b59267b2..2a190add5 100644
--- a/src/wallet/wallet2.cpp
+++ b/src/wallet/wallet2.cpp
@@ -146,7 +146,7 @@ using namespace cryptonote;
#define IGNORE_LONG_PAYMENT_ID_FROM_BLOCK_VERSION 12
#define DEFAULT_UNLOCK_TIME (CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE * DIFFICULTY_TARGET_V2)
-#define RECENT_SPEND_WINDOW (50 * DIFFICULTY_TARGET_V2)
+#define RECENT_SPEND_WINDOW (15 * DIFFICULTY_TARGET_V2)
static const std::string MULTISIG_SIGNATURE_MAGIC = "SigMultisigPkV1";
static const std::string MULTISIG_EXTRA_INFO_MAGIC = "MultisigxV1";
@@ -314,7 +314,6 @@ void do_prepare_file_names(const std::string& file_path, std::string& keys_file,
{
keys_file = file_path;
wallet_file = file_path;
- boost::system::error_code e;
if(string_tools::get_extension(keys_file) == "keys")
{//provided keys file name
wallet_file = string_tools::cut_off_extension(wallet_file);
@@ -1024,13 +1023,7 @@ gamma_picker::gamma_picker(const std::vector<uint64_t> &rct_offsets, double shap
end = rct_offsets.data() + rct_offsets.size() - CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE;
num_rct_outputs = *(end - 1);
THROW_WALLET_EXCEPTION_IF(num_rct_outputs == 0, error::wallet_internal_error, "No rct outputs");
- THROW_WALLET_EXCEPTION_IF(outputs_to_consider == 0, error::wallet_internal_error, "No rct outputs to consider");
- average_output_time = DIFFICULTY_TARGET_V2 * blocks_to_consider / outputs_to_consider; // this assumes constant target over the whole rct range
- if (average_output_time == 0) {
- // TODO: apply this to all cases; do so alongside a hard fork, where all clients will update at the same time, preventing anonymity puddle formation
- average_output_time = DIFFICULTY_TARGET_V2 * blocks_to_consider / static_cast<double>(outputs_to_consider);
- }
- THROW_WALLET_EXCEPTION_IF(average_output_time == 0, error::wallet_internal_error, "Average seconds per output cannot be 0.");
+ average_output_time = DIFFICULTY_TARGET_V2 * blocks_to_consider / static_cast<double>(outputs_to_consider); // this assumes constant target over the whole rct range
};
gamma_picker::gamma_picker(const std::vector<uint64_t> &rct_offsets): gamma_picker(rct_offsets, GAMMA_SHAPE, GAMMA_SCALE) {}
@@ -1235,8 +1228,6 @@ wallet2::wallet2(network_type nettype, uint64_t kdf_rounds, bool unattended, std
m_ring_history_saved(false),
m_ringdb(),
m_last_block_reward(0),
- m_encrypt_keys_after_refresh(boost::none),
- m_decrypt_keys_lockers(0),
m_unattended(unattended),
m_devices_registered(false),
m_device_last_key_image_sync(0),
@@ -1888,8 +1879,7 @@ void wallet2::scan_output(const cryptonote::transaction &tx, bool miner_tx, cons
boost::optional<epee::wipeable_string> pwd = m_callback->on_get_password(pool ? "output found in pool" : "output received");
THROW_WALLET_EXCEPTION_IF(!pwd, error::password_needed, tr("Password is needed to compute key image for incoming monero"));
THROW_WALLET_EXCEPTION_IF(!verify_password(*pwd), error::password_needed, tr("Invalid password: password is needed to compute key image for incoming monero"));
- decrypt_keys(*pwd);
- m_encrypt_keys_after_refresh = *pwd;
+ m_encrypt_keys_after_refresh.reset(new wallet_keys_unlocker(*this, m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only, *pwd));
}
}
@@ -3021,11 +3011,7 @@ void wallet2::update_pool_state(std::vector<std::tuple<cryptonote::transaction,
MTRACE("update_pool_state start");
auto keys_reencryptor = epee::misc_utils::create_scope_leave_handler([&, this]() {
- if (m_encrypt_keys_after_refresh)
- {
- encrypt_keys(*m_encrypt_keys_after_refresh);
- m_encrypt_keys_after_refresh = boost::none;
- }
+ m_encrypt_keys_after_refresh.reset();
});
// get the pool state
@@ -3456,11 +3442,7 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo
start_height = 0;
auto keys_reencryptor = epee::misc_utils::create_scope_leave_handler([&, this]() {
- if (m_encrypt_keys_after_refresh)
- {
- encrypt_keys(*m_encrypt_keys_after_refresh);
- m_encrypt_keys_after_refresh = boost::none;
- }
+ m_encrypt_keys_after_refresh.reset();
});
auto scope_exit_handler_hwdev = epee::misc_utils::create_scope_leave_handler([&](){hwdev.computing_key_images(false);});
@@ -4600,18 +4582,12 @@ bool wallet2::verify_password(const std::string& keys_file_name, const epee::wip
void wallet2::encrypt_keys(const crypto::chacha_key &key)
{
- boost::lock_guard<boost::mutex> lock(m_decrypt_keys_lock);
- if (--m_decrypt_keys_lockers) // another lock left ?
- return;
m_account.encrypt_keys(key);
m_account.decrypt_viewkey(key);
}
void wallet2::decrypt_keys(const crypto::chacha_key &key)
{
- boost::lock_guard<boost::mutex> lock(m_decrypt_keys_lock);
- if (m_decrypt_keys_lockers++) // already unlocked ?
- return;
m_account.encrypt_viewkey(key);
m_account.decrypt_keys(key);
}
@@ -7096,7 +7072,6 @@ bool wallet2::load_tx(const std::string &signed_filename, std::vector<tools::wal
bool wallet2::parse_tx_from_str(const std::string &signed_tx_st, std::vector<tools::wallet2::pending_tx> &ptx, std::function<bool(const signed_tx_set &)> accept_func)
{
std::string s = signed_tx_st;
- boost::system::error_code errcode;
signed_tx_set signed_txs;
const size_t magiclen = strlen(SIGNED_TX_PREFIX) - 1;
diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h
index facf9878d..7648becc8 100644
--- a/src/wallet/wallet2.h
+++ b/src/wallet/wallet2.h
@@ -1798,9 +1798,7 @@ private:
crypto::secret_key m_original_view_secret_key;
crypto::chacha_key m_cache_key;
- boost::optional<epee::wipeable_string> m_encrypt_keys_after_refresh;
- boost::mutex m_decrypt_keys_lock;
- unsigned int m_decrypt_keys_lockers;
+ std::shared_ptr<wallet_keys_unlocker> m_encrypt_keys_after_refresh;
bool m_unattended;
bool m_devices_registered;
diff --git a/src/wallet/wallet_rpc_helpers.h b/src/wallet/wallet_rpc_helpers.h
index 35714db03..6f50b6727 100644
--- a/src/wallet/wallet_rpc_helpers.h
+++ b/src/wallet/wallet_rpc_helpers.h
@@ -28,6 +28,7 @@
#pragma once
+#include <limits>
#include <type_traits>
namespace