mirror of
https://github.com/dashpay/dash.git
synced 2024-12-25 03:52:49 +01:00
feat: introduce coinjoinsalt
RPC to allow manipulating per-wallet salt
Co-authored-by: thephez <thephez@users.noreply.github.com> Co-authored-by: UdjinM6 <UdjinM6@users.noreply.github.com>
This commit is contained in:
parent
4fa3d396f4
commit
5943c93bdd
4
doc/release-notes-6093.md
Normal file
4
doc/release-notes-6093.md
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
New functionality
|
||||||
|
-----------
|
||||||
|
|
||||||
|
- A new RPC command, `coinjoinsalt`, allows for manipulating a CoinJoin salt stored in a wallet. `coinjoinsalt get` will fetch an existing salt, `coinjoinsalt set` will allow setting a custom salt and `coinjoinsalt generate` will set a random hash as the new salt.
|
@ -181,6 +181,193 @@ static RPCHelpMan coinjoin_stop()
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static RPCHelpMan coinjoinsalt()
|
||||||
|
{
|
||||||
|
return RPCHelpMan{"coinjoinsalt",
|
||||||
|
"\nAvailable commands:\n"
|
||||||
|
" generate - Generate new CoinJoin salt\n"
|
||||||
|
" get - Fetch existing CoinJoin salt\n"
|
||||||
|
" set - Set new CoinJoin salt\n",
|
||||||
|
{
|
||||||
|
{"command", RPCArg::Type::STR, RPCArg::Optional::NO, "The command to execute"},
|
||||||
|
},
|
||||||
|
RPCResults{},
|
||||||
|
RPCExamples{""},
|
||||||
|
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||||
|
{
|
||||||
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Must be a valid command");
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static RPCHelpMan coinjoinsalt_generate()
|
||||||
|
{
|
||||||
|
return RPCHelpMan{"coinjoinsalt generate",
|
||||||
|
"\nGenerate new CoinJoin salt and store it in the wallet database\n"
|
||||||
|
"Cannot generate new salt if CoinJoin mixing is in process or wallet has private keys disabled.\n",
|
||||||
|
{
|
||||||
|
{"overwrite", RPCArg::Type::BOOL, /* default */ "false", "Generate new salt even if there is an existing salt and/or there is CoinJoin balance"},
|
||||||
|
},
|
||||||
|
RPCResult{
|
||||||
|
RPCResult::Type::BOOL, "", "Status of CoinJoin salt generation and commitment"
|
||||||
|
},
|
||||||
|
RPCExamples{
|
||||||
|
HelpExampleCli("coinjoinsalt generate", "")
|
||||||
|
+ HelpExampleRpc("coinjoinsalt generate", "")
|
||||||
|
},
|
||||||
|
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||||
|
{
|
||||||
|
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
|
||||||
|
if (!wallet) return NullUniValue;
|
||||||
|
|
||||||
|
const auto str_wallet = wallet->GetName();
|
||||||
|
if (wallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
|
||||||
|
throw JSONRPCError(RPC_INVALID_REQUEST,
|
||||||
|
strprintf("Wallet \"%s\" has private keys disabled, cannot perform CoinJoin!", str_wallet));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool enable_overwrite{false}; // Default value
|
||||||
|
if (!request.params[0].isNull()) {
|
||||||
|
enable_overwrite = ParseBoolV(request.params[0], "overwrite");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!enable_overwrite && !wallet->GetCoinJoinSalt().IsNull()) {
|
||||||
|
throw JSONRPCError(RPC_INVALID_REQUEST,
|
||||||
|
strprintf("Wallet \"%s\" already has set CoinJoin salt!", str_wallet));
|
||||||
|
}
|
||||||
|
|
||||||
|
const NodeContext& node = EnsureAnyNodeContext(request.context);
|
||||||
|
if (node.coinjoin_loader != nullptr) {
|
||||||
|
auto cj_clientman = node.coinjoin_loader->walletman().Get(wallet->GetName());
|
||||||
|
if (cj_clientman != nullptr && cj_clientman->IsMixing()) {
|
||||||
|
throw JSONRPCError(RPC_WALLET_ERROR,
|
||||||
|
strprintf("Wallet \"%s\" is currently mixing, cannot change salt!", str_wallet));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto wallet_balance{wallet->GetBalance()};
|
||||||
|
const bool has_balance{(wallet_balance.m_anonymized
|
||||||
|
+ wallet_balance.m_denominated_trusted
|
||||||
|
+ wallet_balance.m_denominated_untrusted_pending) > 0};
|
||||||
|
if (!enable_overwrite && has_balance) {
|
||||||
|
throw JSONRPCError(RPC_WALLET_ERROR,
|
||||||
|
strprintf("Wallet \"%s\" has CoinJoin balance, cannot continue!", str_wallet));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!wallet->SetCoinJoinSalt(GetRandHash())) {
|
||||||
|
throw JSONRPCError(RPC_INVALID_REQUEST,
|
||||||
|
strprintf("Unable to set new CoinJoin salt for wallet \"%s\"!", str_wallet));
|
||||||
|
}
|
||||||
|
|
||||||
|
wallet->ClearCoinJoinRoundsCache();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static RPCHelpMan coinjoinsalt_get()
|
||||||
|
{
|
||||||
|
return RPCHelpMan{"coinjoinsalt get",
|
||||||
|
"\nFetch existing CoinJoin salt\n"
|
||||||
|
"Cannot fetch salt if wallet has private keys disabled.\n",
|
||||||
|
{},
|
||||||
|
RPCResult{
|
||||||
|
RPCResult::Type::STR_HEX, "", "CoinJoin salt"
|
||||||
|
},
|
||||||
|
RPCExamples{
|
||||||
|
HelpExampleCli("coinjoinsalt get", "")
|
||||||
|
+ HelpExampleRpc("coinjoinsalt get", "")
|
||||||
|
},
|
||||||
|
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||||
|
{
|
||||||
|
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
|
||||||
|
if (!wallet) return NullUniValue;
|
||||||
|
|
||||||
|
const auto str_wallet = wallet->GetName();
|
||||||
|
if (wallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
|
||||||
|
throw JSONRPCError(RPC_INVALID_REQUEST,
|
||||||
|
strprintf("Wallet \"%s\" has private keys disabled, cannot perform CoinJoin!", str_wallet));
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto salt{wallet->GetCoinJoinSalt()};
|
||||||
|
if (salt.IsNull()) {
|
||||||
|
throw JSONRPCError(RPC_WALLET_ERROR,
|
||||||
|
strprintf("Wallet \"%s\" has no CoinJoin salt!", str_wallet));
|
||||||
|
}
|
||||||
|
return salt.GetHex();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static RPCHelpMan coinjoinsalt_set()
|
||||||
|
{
|
||||||
|
return RPCHelpMan{"coinjoinsalt set",
|
||||||
|
"\nSet new CoinJoin salt\n"
|
||||||
|
"Cannot set salt if CoinJoin mixing is in process or wallet has private keys disabled.\n"
|
||||||
|
"Will overwrite existing salt. The presence of a CoinJoin balance will cause the wallet to rescan.\n",
|
||||||
|
{
|
||||||
|
{"salt", RPCArg::Type::STR, RPCArg::Optional::NO, "Desired CoinJoin salt value for the wallet"},
|
||||||
|
{"overwrite", RPCArg::Type::BOOL, /* default */ "false", "Overwrite salt even if CoinJoin balance present"},
|
||||||
|
},
|
||||||
|
RPCResult{
|
||||||
|
RPCResult::Type::BOOL, "", "Status of CoinJoin salt change request"
|
||||||
|
},
|
||||||
|
RPCExamples{
|
||||||
|
HelpExampleCli("coinjoinsalt set", "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16")
|
||||||
|
+ HelpExampleRpc("coinjoinsalt set", "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16")
|
||||||
|
},
|
||||||
|
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||||
|
{
|
||||||
|
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
|
||||||
|
if (!wallet) return NullUniValue;
|
||||||
|
|
||||||
|
const auto salt{ParseHashV(request.params[0], "salt")};
|
||||||
|
if (salt == uint256::ZERO) {
|
||||||
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid CoinJoin salt value");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool enable_overwrite{false}; // Default value
|
||||||
|
if (!request.params[1].isNull()) {
|
||||||
|
enable_overwrite = ParseBoolV(request.params[1], "overwrite");
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto str_wallet = wallet->GetName();
|
||||||
|
if (wallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
|
||||||
|
throw JSONRPCError(RPC_INVALID_REQUEST,
|
||||||
|
strprintf("Wallet \"%s\" has private keys disabled, cannot perform CoinJoin!", str_wallet));
|
||||||
|
}
|
||||||
|
|
||||||
|
const NodeContext& node = EnsureAnyNodeContext(request.context);
|
||||||
|
if (node.coinjoin_loader != nullptr) {
|
||||||
|
auto cj_clientman = node.coinjoin_loader->walletman().Get(wallet->GetName());
|
||||||
|
if (cj_clientman != nullptr && cj_clientman->IsMixing()) {
|
||||||
|
throw JSONRPCError(RPC_WALLET_ERROR,
|
||||||
|
strprintf("Wallet \"%s\" is currently mixing, cannot change salt!", str_wallet));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto wallet_balance{wallet->GetBalance()};
|
||||||
|
const bool has_balance{(wallet_balance.m_anonymized
|
||||||
|
+ wallet_balance.m_denominated_trusted
|
||||||
|
+ wallet_balance.m_denominated_untrusted_pending) > 0};
|
||||||
|
if (has_balance && !enable_overwrite) {
|
||||||
|
throw JSONRPCError(RPC_WALLET_ERROR,
|
||||||
|
strprintf("Wallet \"%s\" has CoinJoin balance, cannot continue!", str_wallet));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!wallet->SetCoinJoinSalt(salt)) {
|
||||||
|
throw JSONRPCError(RPC_WALLET_ERROR,
|
||||||
|
strprintf("Unable to set new CoinJoin salt for wallet \"%s\"!", str_wallet));
|
||||||
|
}
|
||||||
|
|
||||||
|
wallet->ClearCoinJoinRoundsCache();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
#endif // ENABLE_WALLET
|
#endif // ENABLE_WALLET
|
||||||
|
|
||||||
static RPCHelpMan getpoolinfo()
|
static RPCHelpMan getpoolinfo()
|
||||||
@ -295,6 +482,10 @@ static const CRPCCommand commands[] =
|
|||||||
{ "dash", &coinjoin_reset, },
|
{ "dash", &coinjoin_reset, },
|
||||||
{ "dash", &coinjoin_start, },
|
{ "dash", &coinjoin_start, },
|
||||||
{ "dash", &coinjoin_stop, },
|
{ "dash", &coinjoin_stop, },
|
||||||
|
{ "dash", &coinjoinsalt, },
|
||||||
|
{ "dash", &coinjoinsalt_generate, },
|
||||||
|
{ "dash", &coinjoinsalt_get, },
|
||||||
|
{ "dash", &coinjoinsalt_set, },
|
||||||
#endif // ENABLE_WALLET
|
#endif // ENABLE_WALLET
|
||||||
};
|
};
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
@ -1448,6 +1448,15 @@ int CWallet::GetCappedOutpointCoinJoinRounds(const COutPoint& outpoint) const
|
|||||||
return realCoinJoinRounds > CCoinJoinClientOptions::GetRounds() ? CCoinJoinClientOptions::GetRounds() : realCoinJoinRounds;
|
return realCoinJoinRounds > CCoinJoinClientOptions::GetRounds() ? CCoinJoinClientOptions::GetRounds() : realCoinJoinRounds;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CWallet::ClearCoinJoinRoundsCache()
|
||||||
|
{
|
||||||
|
LOCK(cs_wallet);
|
||||||
|
mapOutpointRoundsCache.clear();
|
||||||
|
MarkDirty();
|
||||||
|
// Notify UI
|
||||||
|
NotifyTransactionChanged(uint256::ONE, CT_UPDATED);
|
||||||
|
}
|
||||||
|
|
||||||
bool CWallet::IsDenominated(const COutPoint& outpoint) const
|
bool CWallet::IsDenominated(const COutPoint& outpoint) const
|
||||||
{
|
{
|
||||||
LOCK(cs_wallet);
|
LOCK(cs_wallet);
|
||||||
|
@ -747,7 +747,7 @@ private:
|
|||||||
void AddToSpends(const uint256& wtxid) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
void AddToSpends(const uint256& wtxid) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||||
|
|
||||||
std::set<COutPoint> setWalletUTXO;
|
std::set<COutPoint> setWalletUTXO;
|
||||||
mutable std::map<COutPoint, int> mapOutpointRoundsCache;
|
mutable std::map<COutPoint, int> mapOutpointRoundsCache GUARDED_BY(cs_wallet);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a transaction to the wallet, or update it. pIndex and posInBlock should
|
* Add a transaction to the wallet, or update it. pIndex and posInBlock should
|
||||||
@ -995,6 +995,8 @@ public:
|
|||||||
int GetRealOutpointCoinJoinRounds(const COutPoint& outpoint, int nRounds = 0) const;
|
int GetRealOutpointCoinJoinRounds(const COutPoint& outpoint, int nRounds = 0) const;
|
||||||
// respect current settings
|
// respect current settings
|
||||||
int GetCappedOutpointCoinJoinRounds(const COutPoint& outpoint) const;
|
int GetCappedOutpointCoinJoinRounds(const COutPoint& outpoint) const;
|
||||||
|
// drop the internal cache to let Get...Rounds recalculate CJ balance from scratch and notify UI
|
||||||
|
void ClearCoinJoinRoundsCache();
|
||||||
|
|
||||||
bool IsDenominated(const COutPoint& outpoint) const;
|
bool IsDenominated(const COutPoint& outpoint) const;
|
||||||
bool IsFullyMixed(const COutPoint& outpoint) const;
|
bool IsFullyMixed(const COutPoint& outpoint) const;
|
||||||
|
Loading…
Reference in New Issue
Block a user