multisig: add flag to skip refresh after multisig import
What changed, and why it matters
This change adds an optional flag to Monero's multisig wallet import process that lets callers skip the automatic wallet refresh and spent-output rescan. By default the refresh still happens, so existing behavior is preserved. The patch is a usability/performance improvement rather than a fix for an active security flaw, though skipping refresh could in theory leave a wallet with slightly stale balance information if used carelessly.
No immediate action required. Operators using the new `refresh_after_import=false` flag in multisig workflows should ensure they refresh and rescan spent outputs separately before relying on balance or spend status.
Security signals we found
New optional RPC parameter changes post-import behavior
Skipping refresh can leave spent-output status unverified
Default behavior unchanged, reducing regression risk
Evidence from the diff
The commit introduces a refresh_after_import boolean parameter to wallet2::import_multisig() (default true) and exposes it in the import_multisig_info wallet RPC. When set to false, the wallet skips the refresh(false) call and the daemon trust/spent-status update block. The default remains true, so backward-compatible behavior is maintained. RPC request serialization uses KV_SERIALIZE_OPT(refresh_after_import, true) so omitted fields default to refreshing.
Changed components
src/wallet/wallet2.cppsrc/wallet/wallet2.hsrc/wallet/wallet_rpc_server.cppsrc/wallet/wallet_rpc_server_commands_defs.hInspect captured patch +21 / −14
diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp
index d7ecd39..a2195bc 100644
--- a/src/wallet/wallet2.cpp
+++ b/src/wallet/wallet2.cpp
@@ -14631,7 +14631,7 @@ void wallet2::update_multisig_rescan_info(const std::vector<std::vector<rct::key
m_key_images[td.m_key_image] = n;
}
//----------------------------------------------------------------------------------------------------
-size_t wallet2::import_multisig(std::vector<cryptonote::blobdata> blobs)
+size_t wallet2::import_multisig(std::vector<cryptonote::blobdata> blobs, bool refresh_after_import)
{
CHECK_AND_ASSERT_THROW_MES(m_multisig, "Wallet is not multisig");
@@ -14749,8 +14749,8 @@ size_t wallet2::import_multisig(std::vector<cryptonote::blobdata> blobs)
update_multisig_rescan_info(m_multisig_rescan_k, m_multisig_rescan_info, n);
}
-
- refresh(false);
+ if (refresh_after_import)
+ refresh(false);
return n_outputs;
}
diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h
index ded9342..36e5082 100644
--- a/src/wallet/wallet2.h
+++ b/src/wallet/wallet2.h
@@ -999,9 +999,11 @@ private:
cryptonote::blobdata export_multisig();
/*!
* Import a set of multisig info from multisig partners
+ * \param info Multisig info from other participants
+ * \param refresh_after_import Whether to refresh the wallet and rescan spent outputs after importing
* \return the number of inputs which were imported
*/
- size_t import_multisig(std::vector<cryptonote::blobdata> info);
+ size_t import_multisig(std::vector<cryptonote::blobdata> info, bool refresh_after_import = true);
/*!
* \brief Rewrites to the wallet file for wallet upgrade (doesn't generate key, assumes it's already there)
* \param wallet_name Name of wallet file (should exist)
diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp
index c038f81..121cf0a 100644
--- a/src/wallet/wallet_rpc_server.cpp
+++ b/src/wallet/wallet_rpc_server.cpp
@@ -4472,7 +4472,7 @@ namespace tools
try
{
- res.n_outputs = m_wallet->import_multisig(info);
+ res.n_outputs = m_wallet->import_multisig(info, req.refresh_after_import);
}
catch (const std::exception &e)
{
@@ -4481,21 +4481,24 @@ namespace tools
return false;
}
- if (m_wallet->is_trusted_daemon())
+ if (req.refresh_after_import)
{
- try
+ if (m_wallet->is_trusted_daemon())
{
- m_wallet->rescan_spent();
+ try
+ {
+ m_wallet->rescan_spent();
+ }
+ catch (const std::exception &e)
+ {
+ er.message = std::string("Success, but failed to update spent status after import multisig info: ") + e.what();
+ }
}
- catch (const std::exception &e)
+ else
{
- er.message = std::string("Success, but failed to update spent status after import multisig info: ") + e.what();
+ er.message = "Success, but cannot update spent status after import multisig info as daemon is untrusted";
}
}
- else
- {
- er.message = "Success, but cannot update spent status after import multisig info as daemon is untrusted";
- }
return true;
}
diff --git a/src/wallet/wallet_rpc_server_commands_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h
index e6332d2..3662630 100644
--- a/src/wallet/wallet_rpc_server_commands_defs.h
+++ b/src/wallet/wallet_rpc_server_commands_defs.h
@@ -2456,9 +2456,11 @@ namespace wallet_rpc
struct request_t
{
std::vector<std::string> info;
+ bool refresh_after_import;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(info)
+ KV_SERIALIZE_OPT(refresh_after_import, true)
END_KV_SERIALIZE_MAP()
};
typedef epee::misc_utils::struct_init<request_t> request;
Why this scored 18/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.