wallet: background sync with just the view key

- When background syncing, the wallet wipes the spend key
from memory and processes all new transactions. The wallet saves
all receives, spends, and "plausible" spends of receives the
wallet does not know key images for.
- When background sync disabled, the wallet processes all
background synced txs and then clears the background sync cache.
- Adding "plausible" spends to the background sync cache ensures
that the wallet does not need to query the daemon to see if any
received outputs were spent while background sync was enabled.
This would harm privacy especially for users of 3rd party daemons.
- To enable the feature in the CLI wallet, the user can set
background-sync to reuse-wallet-password or
custom-background-password and the wallet automatically syncs in
the background when the wallet locks, then processes all
background synced txs when the wallet is unlocked.
- The custom-background-password option enables the user to
open a distinct background wallet that only has a view key saved
and can be opened/closed/synced separately from the main wallet.
When the main wallet opens, it processes the background wallet's
cache.
- To enable the feature in the RPC wallet, there is a new
`/setup_background_sync` endpoint.
- HW, multsig and view-only wallets cannot background sync.
This commit is contained in:
j-berman 2022-10-13 18:33:33 -07:00
parent cc73fe7116
commit e71c8bf190
20 changed files with 2340 additions and 132 deletions

View file

@ -73,6 +73,54 @@ using namespace epee;
} \
} while(0)
#define CHECK_IF_BACKGROUND_SYNCING() \
do \
{ \
if (!m_wallet) { return not_open(er); } \
if (m_wallet->is_background_wallet()) \
{ \
er.code = WALLET_RPC_ERROR_CODE_IS_BACKGROUND_WALLET; \
er.message = "This command is disabled for background wallets."; \
return false; \
} \
if (m_wallet->is_background_syncing()) \
{ \
er.code = WALLET_RPC_ERROR_CODE_IS_BACKGROUND_SYNCING; \
er.message = "This command is disabled while background syncing. Stop background syncing to use this command."; \
return false; \
} \
} while(0)
#define PRE_VALIDATE_BACKGROUND_SYNC() \
do \
{ \
if (!m_wallet) { return not_open(er); } \
if (m_restricted) \
{ \
er.code = WALLET_RPC_ERROR_CODE_DENIED; \
er.message = "Command unavailable in restricted mode."; \
return false; \
} \
if (m_wallet->key_on_device()) \
{ \
er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; \
er.message = "Command not supported by HW wallet"; \
return false; \
} \
if (m_wallet->get_multisig_status().multisig_is_active) \
{ \
er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; \
er.message = "Multisig wallet cannot enable background sync"; \
return false; \
} \
if (m_wallet->watch_only()) \
{ \
er.code = WALLET_RPC_ERROR_CODE_WATCH_ONLY; \
er.message = "Watch-only wallet cannot enable background sync"; \
return false; \
} \
} while (0)
namespace
{
const command_line::arg_descriptor<std::string, true> arg_rpc_bind_port = {"rpc-bind-port", "Sets bind port for server"};
@ -291,6 +339,9 @@ namespace tools
{
if (!m_wallet)
return;
// Background mining can be toggled from the main wallet
if (m_wallet->is_background_wallet() || m_wallet->is_background_syncing())
return;
tools::wallet2::BackgroundMiningSetupType setup = m_wallet->setup_background_mining();
if (setup == tools::wallet2::BackgroundMiningNo)
@ -581,6 +632,7 @@ namespace tools
bool wallet_rpc_server::on_create_address(const wallet_rpc::COMMAND_RPC_CREATE_ADDRESS::request& req, wallet_rpc::COMMAND_RPC_CREATE_ADDRESS::response& res, epee::json_rpc::error& er, const connection_context *ctx)
{
if (!m_wallet) return not_open(er);
CHECK_IF_BACKGROUND_SYNCING();
try
{
if (req.count < 1 || req.count > 65536) {
@ -618,6 +670,7 @@ namespace tools
bool wallet_rpc_server::on_label_address(const wallet_rpc::COMMAND_RPC_LABEL_ADDRESS::request& req, wallet_rpc::COMMAND_RPC_LABEL_ADDRESS::response& res, epee::json_rpc::error& er, const connection_context *ctx)
{
if (!m_wallet) return not_open(er);
CHECK_IF_BACKGROUND_SYNCING();
try
{
m_wallet->set_subaddress_label(req.index, req.label);
@ -680,6 +733,7 @@ namespace tools
bool wallet_rpc_server::on_create_account(const wallet_rpc::COMMAND_RPC_CREATE_ACCOUNT::request& req, wallet_rpc::COMMAND_RPC_CREATE_ACCOUNT::response& res, epee::json_rpc::error& er, const connection_context *ctx)
{
if (!m_wallet) return not_open(er);
CHECK_IF_BACKGROUND_SYNCING();
try
{
m_wallet->add_subaddress_account(req.label);
@ -697,6 +751,7 @@ namespace tools
bool wallet_rpc_server::on_label_account(const wallet_rpc::COMMAND_RPC_LABEL_ACCOUNT::request& req, wallet_rpc::COMMAND_RPC_LABEL_ACCOUNT::response& res, epee::json_rpc::error& er, const connection_context *ctx)
{
if (!m_wallet) return not_open(er);
CHECK_IF_BACKGROUND_SYNCING();
try
{
m_wallet->set_subaddress_label({req.account_index, 0}, req.label);
@ -712,6 +767,7 @@ namespace tools
bool wallet_rpc_server::on_get_account_tags(const wallet_rpc::COMMAND_RPC_GET_ACCOUNT_TAGS::request& req, wallet_rpc::COMMAND_RPC_GET_ACCOUNT_TAGS::response& res, epee::json_rpc::error& er, const connection_context *ctx)
{
if (!m_wallet) return not_open(er);
CHECK_IF_BACKGROUND_SYNCING();
const std::pair<std::map<std::string, std::string>, std::vector<std::string>> account_tags = m_wallet->get_account_tags();
for (const std::pair<const std::string, std::string>& p : account_tags.first)
{
@ -731,6 +787,7 @@ namespace tools
bool wallet_rpc_server::on_tag_accounts(const wallet_rpc::COMMAND_RPC_TAG_ACCOUNTS::request& req, wallet_rpc::COMMAND_RPC_TAG_ACCOUNTS::response& res, epee::json_rpc::error& er, const connection_context *ctx)
{
if (!m_wallet) return not_open(er);
CHECK_IF_BACKGROUND_SYNCING();
try
{
m_wallet->set_account_tag(req.accounts, req.tag);
@ -746,6 +803,7 @@ namespace tools
bool wallet_rpc_server::on_untag_accounts(const wallet_rpc::COMMAND_RPC_UNTAG_ACCOUNTS::request& req, wallet_rpc::COMMAND_RPC_UNTAG_ACCOUNTS::response& res, epee::json_rpc::error& er, const connection_context *ctx)
{
if (!m_wallet) return not_open(er);
CHECK_IF_BACKGROUND_SYNCING();
try
{
m_wallet->set_account_tag(req.accounts, "");
@ -761,6 +819,7 @@ namespace tools
bool wallet_rpc_server::on_set_account_tag_description(const wallet_rpc::COMMAND_RPC_SET_ACCOUNT_TAG_DESCRIPTION::request& req, wallet_rpc::COMMAND_RPC_SET_ACCOUNT_TAG_DESCRIPTION::response& res, epee::json_rpc::error& er, const connection_context *ctx)
{
if (!m_wallet) return not_open(er);
CHECK_IF_BACKGROUND_SYNCING();
try
{
m_wallet->set_account_tag_description(req.tag, req.description);
@ -791,6 +850,7 @@ namespace tools
bool wallet_rpc_server::on_freeze(const wallet_rpc::COMMAND_RPC_FREEZE::request& req, wallet_rpc::COMMAND_RPC_FREEZE::response& res, epee::json_rpc::error& er, const connection_context *ctx)
{
if (!m_wallet) return not_open(er);
CHECK_IF_BACKGROUND_SYNCING();
try
{
if (req.key_image.empty())
@ -819,6 +879,7 @@ namespace tools
bool wallet_rpc_server::on_thaw(const wallet_rpc::COMMAND_RPC_THAW::request& req, wallet_rpc::COMMAND_RPC_THAW::response& res, epee::json_rpc::error& er, const connection_context *ctx)
{
if (!m_wallet) return not_open(er);
CHECK_IF_BACKGROUND_SYNCING();
try
{
if (req.key_image.empty())
@ -847,6 +908,7 @@ namespace tools
bool wallet_rpc_server::on_frozen(const wallet_rpc::COMMAND_RPC_FROZEN::request& req, wallet_rpc::COMMAND_RPC_FROZEN::response& res, epee::json_rpc::error& er, const connection_context *ctx)
{
if (!m_wallet) return not_open(er);
CHECK_IF_BACKGROUND_SYNCING();
try
{
if (req.key_image.empty())
@ -874,6 +936,8 @@ namespace tools
//------------------------------------------------------------------------------------------------------------------------------
bool wallet_rpc_server::validate_transfer(const std::list<wallet_rpc::transfer_destination>& destinations, const std::string& payment_id, std::vector<cryptonote::tx_destination_entry>& dsts, std::vector<uint8_t>& extra, bool at_least_one_destination, epee::json_rpc::error& er)
{
CHECK_IF_BACKGROUND_SYNCING();
crypto::hash8 integrated_payment_id = crypto::null_hash8;
std::string extra_nonce;
for (auto it = destinations.begin(); it != destinations.end(); it++)
@ -1203,6 +1267,7 @@ namespace tools
}
CHECK_MULTISIG_ENABLED();
CHECK_IF_BACKGROUND_SYNCING();
cryptonote::blobdata blob;
if (!epee::string_tools::parse_hexstr_to_binbuff(req.unsigned_txset, blob))
@ -1284,6 +1349,7 @@ namespace tools
er.message = "command not supported by watch-only wallet";
return false;
}
CHECK_IF_BACKGROUND_SYNCING();
if(req.unsigned_txset.empty() && req.multisig_txset.empty())
{
er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
@ -1553,6 +1619,7 @@ namespace tools
}
CHECK_MULTISIG_ENABLED();
CHECK_IF_BACKGROUND_SYNCING();
try
{
@ -2115,6 +2182,7 @@ namespace tools
er.message = "The wallet is watch-only. Cannot retrieve seed.";
return false;
}
CHECK_IF_BACKGROUND_SYNCING();
if (!m_wallet->is_deterministic())
{
er.code = WALLET_RPC_ERROR_CODE_NON_DETERMINISTIC;
@ -2143,6 +2211,7 @@ namespace tools
er.message = "The wallet is watch-only. Cannot retrieve spend key.";
return false;
}
CHECK_IF_BACKGROUND_SYNCING();
epee::wipeable_string key = epee::to_hex::wipeable_string(m_wallet->get_account().get_keys().m_spend_secret_key);
res.key = std::string(key.data(), key.size());
}
@ -2164,6 +2233,7 @@ namespace tools
er.message = "Command unavailable in restricted mode.";
return false;
}
CHECK_IF_BACKGROUND_SYNCING();
try
{
@ -2177,6 +2247,79 @@ namespace tools
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
bool wallet_rpc_server::on_setup_background_sync(const wallet_rpc::COMMAND_RPC_SETUP_BACKGROUND_SYNC::request& req, wallet_rpc::COMMAND_RPC_SETUP_BACKGROUND_SYNC::response& res, epee::json_rpc::error& er, const connection_context *ctx)
{
try
{
PRE_VALIDATE_BACKGROUND_SYNC();
const tools::wallet2::BackgroundSyncType background_sync_type = tools::wallet2::background_sync_type_from_str(req.background_sync_type);
boost::optional<epee::wipeable_string> background_cache_password = boost::none;
if (background_sync_type == tools::wallet2::BackgroundSyncCustomPassword)
background_cache_password = boost::optional<epee::wipeable_string>(req.background_cache_password);
m_wallet->setup_background_sync(background_sync_type, req.wallet_password, background_cache_password);
}
catch (...)
{
handle_rpc_exception(std::current_exception(), er, WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR);
return false;
}
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
bool wallet_rpc_server::on_start_background_sync(const wallet_rpc::COMMAND_RPC_START_BACKGROUND_SYNC::request& req, wallet_rpc::COMMAND_RPC_START_BACKGROUND_SYNC::response& res, epee::json_rpc::error& er, const connection_context *ctx)
{
try
{
PRE_VALIDATE_BACKGROUND_SYNC();
m_wallet->start_background_sync();
}
catch (...)
{
handle_rpc_exception(std::current_exception(), er, WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR);
return false;
}
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
bool wallet_rpc_server::on_stop_background_sync(const wallet_rpc::COMMAND_RPC_STOP_BACKGROUND_SYNC::request& req, wallet_rpc::COMMAND_RPC_STOP_BACKGROUND_SYNC::response& res, epee::json_rpc::error& er, const connection_context *ctx)
{
try
{
PRE_VALIDATE_BACKGROUND_SYNC();
crypto::secret_key spend_secret_key = crypto::null_skey;
// Load the spend key from seed if seed is provided
if (!req.seed.empty())
{
crypto::secret_key recovery_key;
std::string language;
if (!crypto::ElectrumWords::words_to_bytes(req.seed, recovery_key, language))
{
er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
er.message = "Electrum-style word list failed verification";
return false;
}
if (!req.seed_offset.empty())
recovery_key = cryptonote::decrypt_key(recovery_key, req.seed_offset);
// generate spend key
cryptonote::account_base account;
account.generate(recovery_key, true, false);
spend_secret_key = account.get_keys().m_spend_secret_key;
}
m_wallet->stop_background_sync(req.wallet_password, spend_secret_key);
}
catch (...)
{
handle_rpc_exception(std::current_exception(), er, WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR);
return false;
}
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
bool wallet_rpc_server::on_sign(const wallet_rpc::COMMAND_RPC_SIGN::request& req, wallet_rpc::COMMAND_RPC_SIGN::response& res, epee::json_rpc::error& er, const connection_context *ctx)
{
if (!m_wallet) return not_open(er);
@ -2186,6 +2329,7 @@ namespace tools
er.message = "Command unavailable in restricted mode.";
return false;
}
CHECK_IF_BACKGROUND_SYNCING();
tools::wallet2::message_signature_type_t signature_type = tools::wallet2::sign_with_spend_key;
if (req.signature_type == "spend" || req.signature_type == "")
@ -2278,6 +2422,7 @@ namespace tools
er.message = "Command unavailable in restricted mode.";
return false;
}
CHECK_IF_BACKGROUND_SYNCING();
if (req.txids.size() != req.notes.size())
{
@ -2350,6 +2495,7 @@ namespace tools
er.message = "Command unavailable in restricted mode.";
return false;
}
CHECK_IF_BACKGROUND_SYNCING();
m_wallet->set_attribute(req.key, req.value);
@ -2377,6 +2523,7 @@ namespace tools
bool wallet_rpc_server::on_get_tx_key(const wallet_rpc::COMMAND_RPC_GET_TX_KEY::request& req, wallet_rpc::COMMAND_RPC_GET_TX_KEY::response& res, epee::json_rpc::error& er, const connection_context *ctx)
{
if (!m_wallet) return not_open(er);
CHECK_IF_BACKGROUND_SYNCING();
crypto::hash txid;
if (!epee::string_tools::hex_to_pod(req.txid, txid))
@ -2468,6 +2615,7 @@ namespace tools
bool wallet_rpc_server::on_get_tx_proof(const wallet_rpc::COMMAND_RPC_GET_TX_PROOF::request& req, wallet_rpc::COMMAND_RPC_GET_TX_PROOF::response& res, epee::json_rpc::error& er, const connection_context *ctx)
{
if (!m_wallet) return not_open(er);
CHECK_IF_BACKGROUND_SYNCING();
crypto::hash txid;
if (!epee::string_tools::hex_to_pod(req.txid, txid))
@ -2584,6 +2732,7 @@ namespace tools
bool wallet_rpc_server::on_get_reserve_proof(const wallet_rpc::COMMAND_RPC_GET_RESERVE_PROOF::request& req, wallet_rpc::COMMAND_RPC_GET_RESERVE_PROOF::response& res, epee::json_rpc::error& er, const connection_context *ctx)
{
if (!m_wallet) return not_open(er);
CHECK_IF_BACKGROUND_SYNCING();
boost::optional<std::pair<uint32_t, uint64_t>> account_minreserve;
if (!req.all)
@ -2826,6 +2975,7 @@ namespace tools
er.message = "command not supported by HW wallet";
return false;
}
CHECK_IF_BACKGROUND_SYNCING();
try
{
@ -2855,6 +3005,7 @@ namespace tools
er.message = "command not supported by HW wallet";
return false;
}
CHECK_IF_BACKGROUND_SYNCING();
cryptonote::blobdata blob;
if (!epee::string_tools::parse_hexstr_to_binbuff(req.outputs_data_hex, blob))
@ -2880,6 +3031,7 @@ namespace tools
bool wallet_rpc_server::on_export_key_images(const wallet_rpc::COMMAND_RPC_EXPORT_KEY_IMAGES::request& req, wallet_rpc::COMMAND_RPC_EXPORT_KEY_IMAGES::response& res, epee::json_rpc::error& er, const connection_context *ctx)
{
if (!m_wallet) return not_open(er);
CHECK_IF_BACKGROUND_SYNCING();
try
{
std::pair<uint64_t, std::vector<std::pair<crypto::key_image, crypto::signature>>> ski = m_wallet->export_key_images(req.all);
@ -2916,6 +3068,7 @@ namespace tools
er.message = "This command requires a trusted daemon.";
return false;
}
CHECK_IF_BACKGROUND_SYNCING();
try
{
std::vector<std::pair<crypto::key_image, crypto::signature>> ski;
@ -2984,6 +3137,7 @@ namespace tools
bool wallet_rpc_server::on_get_address_book(const wallet_rpc::COMMAND_RPC_GET_ADDRESS_BOOK_ENTRY::request& req, wallet_rpc::COMMAND_RPC_GET_ADDRESS_BOOK_ENTRY::response& res, epee::json_rpc::error& er, const connection_context *ctx)
{
if (!m_wallet) return not_open(er);
CHECK_IF_BACKGROUND_SYNCING();
const auto ab = m_wallet->get_address_book();
if (req.entries.empty())
{
@ -3029,6 +3183,7 @@ namespace tools
er.message = "Command unavailable in restricted mode.";
return false;
}
CHECK_IF_BACKGROUND_SYNCING();
cryptonote::address_parse_info info;
er.message = "";
@ -3071,6 +3226,7 @@ namespace tools
er.message = "Command unavailable in restricted mode.";
return false;
}
CHECK_IF_BACKGROUND_SYNCING();
const auto ab = m_wallet->get_address_book();
if (req.index >= ab.size())
@ -3133,6 +3289,7 @@ namespace tools
er.message = "Command unavailable in restricted mode.";
return false;
}
CHECK_IF_BACKGROUND_SYNCING();
const auto ab = m_wallet->get_address_book();
if (req.index >= ab.size())
@ -3203,6 +3360,7 @@ namespace tools
er.message = "Command unavailable in restricted mode.";
return false;
}
CHECK_IF_BACKGROUND_SYNCING();
std::unordered_set<crypto::hash> txids;
std::list<std::string>::const_iterator i = req.txids.begin();
@ -3242,6 +3400,7 @@ namespace tools
er.message = "Command unavailable in restricted mode.";
return false;
}
CHECK_IF_BACKGROUND_SYNCING();
try
{
m_wallet->rescan_spent();
@ -3506,6 +3665,7 @@ namespace tools
er.message = "Command unavailable in restricted mode.";
return false;
}
CHECK_IF_BACKGROUND_SYNCING();
if (m_wallet->verify_password(req.old_password))
{
try
@ -4039,6 +4199,7 @@ namespace tools
er.message = "wallet is watch-only and cannot be made multisig";
return false;
}
CHECK_IF_BACKGROUND_SYNCING();
res.multisig_info = m_wallet->get_multisig_first_kex_msg();
return true;
@ -4066,6 +4227,7 @@ namespace tools
er.message = "wallet is watch-only and cannot be made multisig";
return false;
}
CHECK_IF_BACKGROUND_SYNCING();
try
{