Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
da3eb74
interfaces: expose shared-masternode state on MnEntry
PastaPastaPasta Aug 12, 2026
3f1f450
qt: recognize shared masternodes in the masternode list
PastaPastaPasta Aug 12, 2026
d6a2b35
qt: add ProTxSender, a worker-thread bridge for masternode RPCs
PastaPastaPasta Aug 12, 2026
d30cdd4
qt: add masternode and evonode registration wizard
PastaPastaPasta Aug 12, 2026
fa31903
qt: add update service, update registrar and revoke dialogs
PastaPastaPasta Aug 12, 2026
6d9faa2
qt: add shared masternode creation session dialog
PastaPastaPasta Aug 12, 2026
fe91649
qt: add shared masternode maintenance and dissolution dialogs
PastaPastaPasta Aug 12, 2026
7e23325
qt: test the shared masternode session engine
PastaPastaPasta Aug 12, 2026
5e1425a
qt: bind the shared-session envelope to the transaction it signs
PastaPastaPasta Aug 12, 2026
862a90d
qt: harden the shared masternode creation dialog
PastaPastaPasta Aug 12, 2026
c8f4862
qt: fix dissolution height staleness and shared-dialog dismissal
PastaPastaPasta Aug 12, 2026
e05c27f
qt: fix fee-source balances, pre-v24 evonode service update, mid-RPC …
PastaPastaPasta Aug 12, 2026
91f2d67
qt: fix shared button gate, stale reconcile fields, shared owner filter
PastaPastaPasta Aug 12, 2026
1604a68
doc: release notes for masternode management in the GUI
PastaPastaPasta Aug 12, 2026
be81078
wallet: store masternode operator keys
PastaPastaPasta Aug 12, 2026
4ade5ed
qt: name masternode transactions in the transaction list
PastaPastaPasta Aug 12, 2026
8577723
qt: restructure the registration wizard's pages
PastaPastaPasta Aug 12, 2026
33e5b8c
doc: note operator key storage and masternode transaction types
PastaPastaPasta Aug 12, 2026
b7443d6
wallet: derive masternode operator keys from the HD seed
PastaPastaPasta Aug 12, 2026
b33e1a7
qt: offer a derived operator key and a way to see it again
PastaPastaPasta Aug 12, 2026
3a76d4b
doc: note the derived operator key and Show Operator Key
PastaPastaPasta Aug 12, 2026
668ee6b
wallet: derive operator keys only, and handle several masternodes
PastaPastaPasta Aug 12, 2026
cb766fd
qt: follow the wallet to derive-only operator keys
PastaPastaPasta Aug 12, 2026
27dcc97
doc: operator keys are derived, one index per masternode
PastaPastaPasta Aug 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions doc/release-notes-decentralized-mn-ui.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Masternode management in the GUI

The Masternodes tab in dash-qt gains a full set of management flows, so a
masternode, evonode or shared masternode can be created and maintained from the
wallet without hand-assembling `protx` commands in the debug console.

## Recognition

- Shared masternodes appear in the list with the type "Shared", have their own
entry in the type filter (the "Regular" filter now shows only single-owner
masternodes), and match the text filter by their share owner, refund and
reward addresses. A masternode is recognized as owned when the wallet holds
any of its share owner keys.
- The details dialog shows the collateral share table with per-share amounts and
addresses, the shares belonging to this wallet, the early-exit penalty terms
and the remaining early period.

## Registration

- A wizard registers a masternode (1,000 DASH) or evonode (4,000 DASH) using one
of three collateral paths: funding the collateral from the wallet, using an
existing collateral output already held by the wallet, or external collateral
held in another wallet or on a hardware device (prepare, sign the message
out-of-band, then submit).
- Owner and voting addresses are generated from the wallet, so its backup covers
them. The operator BLS key is **derived from the wallet's recovery phrase** at
`m/9'/coin'/3'/3'/index` — the same derivation the Dash mobile wallets use, so
the recovery phrase alone restores it. Each masternode gets its own index, and
keys already registered on-chain are skipped, so a wallet restored from its
phrase never reuses another masternode's key. Wallets with no recovery phrase
(imported descriptors, raw seeds, pre-HD wallets) generate a key that is shown
once for the owner to save, and a hosting provider's public key can be supplied
instead. In every case the matching `masternodeblsprivkey` line for the
masternode server's `dash.conf` is shown.
- **Show Operator Key** in the masternode list's context menu shows the operator
secret again for any masternode whose key this wallet holds, whether derived or
stored.

## Transaction history

- Dash special transactions now have their own types in the transaction list:
Masternode Registration, Masternode Update and Masternode Dissolution, with a
matching entry in the type filter. A registration previously appeared as a
"Payment to yourself" whose amount was only the network fee.

## Maintenance

- Update Service, Update Registrar and Revoke are available from the list's
context menu, with the evonode platform fields where applicable. Update
Registrar is not offered for shared masternodes, which rotate their keys
unanimously instead.

## Shared masternodes (requires v24)

- Creating a shared masternode is a resumable session: participants pass a
session file between their wallets to agree the share table and terms, each
contributes one funding output equal to their share, and each signs. Every
signature and every imported copy is verified against the transaction actually
being registered before it is accepted, so the terms shown always match the
terms signed.
- Shared masternodes can change a share's reward address, rotate their operator
and voting keys unanimously, and dissolve either unilaterally (with a live
payout and penalty preview) or unanimously (penalty-free). Each participant can
also generate standby dissolution transactions that never expire, to recover
their principal without cooperation if a key is lost.

Shared-masternode features are shown only once the v24 hard fork is active on the
connected network.
21 changes: 21 additions & 0 deletions src/Makefile.qt.include
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,11 @@ QT_MOC_CPP = \
qt/moc_intro.cpp \
qt/moc_macdockiconhandler.cpp \
qt/moc_macnotificationhandler.cpp \
qt/moc_masternodedialogs.cpp \
qt/moc_masternodelist.cpp \
qt/moc_masternodemodel.cpp \
qt/moc_masternodewidgets.cpp \
qt/moc_masternodewizard.cpp \
qt/moc_mnemonicverificationdialog.cpp \
qt/moc_modaloverlay.cpp \
qt/moc_networkwidget.cpp \
Expand All @@ -89,6 +92,7 @@ QT_MOC_CPP = \
qt/moc_proposallist.cpp \
qt/moc_proposalmodel.cpp \
qt/moc_proposalresume.cpp \
qt/moc_protxsender.cpp \
qt/moc_psbtoperationsdialog.cpp \
qt/moc_qrdialog.cpp \
qt/moc_qrimagewidget.cpp \
Expand All @@ -99,6 +103,8 @@ QT_MOC_CPP = \
qt/moc_recentrequeststablemodel.cpp \
qt/moc_rpcconsole.cpp \
qt/moc_sendcoinsdialog.cpp \
qt/moc_sharedmncreatedialog.cpp \
qt/moc_sharedmndialogs.cpp \
qt/moc_sendcoinsentry.cpp \
qt/moc_signverifymessagedialog.cpp \
qt/moc_splashscreen.cpp \
Expand All @@ -123,6 +129,7 @@ QT_MOC = \
qt/bitcoinamountfield.moc \
qt/intro.moc \
qt/overviewpage.moc \
qt/protxsender.moc \
qt/rpcconsole.moc

QT_QRC_CPP = qt/qrc_bitcoin.cpp
Expand Down Expand Up @@ -158,9 +165,13 @@ BITCOIN_QT_H = \
qt/macdockiconhandler.h \
qt/macnotificationhandler.h \
qt/macos_appnap.h \
qt/masternodedialogs.h \
qt/masternodelist.h \
qt/masternodemodel.h \
qt/masternodewidgets.h \
qt/masternodewizard.h \
qt/mnemonicverificationdialog.h \
qt/mnsharesession.h \
qt/modaloverlay.h \
qt/networkstyle.h \
qt/networkwidget.h \
Expand All @@ -177,6 +188,7 @@ BITCOIN_QT_H = \
qt/proposallist.h \
qt/proposalmodel.h \
qt/proposalresume.h \
qt/protxsender.h \
qt/psbtoperationsdialog.h \
qt/qrdialog.h \
qt/qrimagewidget.h \
Expand All @@ -189,6 +201,8 @@ BITCOIN_QT_H = \
qt/sendcoinsdialog.h \
qt/sendcoinsentry.h \
qt/sendcoinsrecipient.h \
qt/sharedmncreatedialog.h \
qt/sharedmndialogs.h \
qt/signverifymessagedialog.h \
qt/splashscreen.h \
qt/trafficgraphdata.h \
Expand Down Expand Up @@ -302,14 +316,19 @@ BITCOIN_QT_WALLET_CPP = \
qt/createwalletdialog.cpp \
qt/descriptiondialog.cpp \
qt/editaddressdialog.cpp \
qt/masternodedialogs.cpp \
qt/masternodelist.cpp \
qt/masternodewidgets.cpp \
qt/masternodewizard.cpp \
qt/mnemonicverificationdialog.cpp \
qt/mnsharesession.cpp \
qt/openuridialog.cpp \
qt/overviewpage.cpp \
qt/paymentserver.cpp \
qt/proposalcreate.cpp \
qt/proposallist.cpp \
qt/proposalresume.cpp \
qt/protxsender.cpp \
qt/psbtoperationsdialog.cpp \
qt/qrdialog.cpp \
qt/qrimagewidget.cpp \
Expand All @@ -318,6 +337,8 @@ BITCOIN_QT_WALLET_CPP = \
qt/recentrequeststablemodel.cpp \
qt/sendcoinsdialog.cpp \
qt/sendcoinsentry.cpp \
qt/sharedmncreatedialog.cpp \
qt/sharedmndialogs.cpp \
qt/signverifymessagedialog.cpp \
qt/transactiondesc.cpp \
qt/transactionfilterproxy.cpp \
Expand Down
3 changes: 3 additions & 0 deletions src/Makefile.qttest.include
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,14 @@ TEST_QT_MOC_CPP = \
if ENABLE_WALLET
TEST_QT_MOC_CPP += \
qt/test/moc_addressbooktests.cpp \
qt/test/moc_mnsharesessiontests.cpp \
qt/test/moc_wallettests.cpp
endif # ENABLE_WALLET

TEST_QT_H = \
qt/test/addressbooktests.h \
qt/test/apptests.h \
qt/test/mnsharesessiontests.h \
qt/test/optiontests.h \
qt/test/rpcnestedtests.h \
qt/test/uritests.h \
Expand All @@ -45,6 +47,7 @@ qt_test_test_dash_qt_SOURCES = \
if ENABLE_WALLET
qt_test_test_dash_qt_SOURCES += \
qt/test/addressbooktests.cpp \
qt/test/mnsharesessiontests.cpp \
qt/test/wallettests.cpp \
wallet/test/wallet_test_fixture.cpp
endif # ENABLE_WALLET
Expand Down
26 changes: 24 additions & 2 deletions src/interfaces/node.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
#include <net_types.h> // For banmap_t
#include <netaddress.h> // For Network
#include <netbase.h> // For ConnectionDirection
#include <pubkey.h> // For CKeyID
#include <saltedhasher.h> // For StaticSaltedHasher
#include <script/script.h> // For CScript
#include <support/allocators/secure.h> // For SecureString
#include <uint256.h>
#include <util/settings.h> // For util::SettingsValue
Expand All @@ -33,10 +35,8 @@ class CDeterministicMNList;
class CFeeRate;
class CGovernanceObject;
class CGovernanceVote;
class CKeyID;
class CNodeStats;
class Coin;
class CScript;
class CService;
class RPCTimerInterface;
class UniValue;
Expand All @@ -63,6 +63,17 @@ class Loader;
} // namespace CoinJoin
struct BlockTip;

//! One collateral share of a shared masternode (mirrors CCollateralShare)
struct MnShare
{
CAmount amount{0};
CScript scriptRefund;
CScript scriptReward; //!< empty means rewards pay to scriptRefund
CKeyID keyIDOwner;

const CScript& rewardScript() const { return scriptReward.empty() ? scriptRefund : scriptReward; }
};

//! Interface for a masternode entry
class MnEntry
{
Expand All @@ -87,7 +98,15 @@ class MnEntry
virtual const int32_t& getPoSePenalty() const = 0;
virtual const int32_t& getRegisteredHeight() const = 0;
virtual const uint16_t& getOperatorReward() const = 0;
//! Registered operator public key, 48 bytes, basic-scheme serialization
virtual std::vector<unsigned char> getOperatorPubKeyBytes() const = 0;
virtual const uint256& getProTxHash() const = 0;
//! Whether this masternode's collateral is pooled by multiple parties (non-empty share table)
virtual bool isShared() const = 0;
//! The collateral share table; empty unless isShared()
virtual std::vector<MnShare> getShares() const = 0;
virtual const uint32_t& getEarlyPeriodBlocks() const = 0;
virtual const CAmount& getEarlyPenalty() const = 0;
};

using MnEntryCPtr = std::shared_ptr<const MnEntry>;
Expand Down Expand Up @@ -417,6 +436,9 @@ class Node
//! Is loading blocks.
virtual bool isLoadingBlocks() = 0;

//! Whether the v24 hard fork (shared masternodes, extended ProTx addresses) is active at the tip.
virtual bool isV24Active() = 0;

//! Set network active.
virtual void setNetworkActive(bool active) = 0;

Expand Down
23 changes: 23 additions & 0 deletions src/interfaces/wallet.h
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,29 @@ class Wallet
//! Sign special transaction payload
virtual bool signSpecialTxPayload(const uint256& hash, const CKeyID& keyid, std::vector<unsigned char>& vchSig) = 0;

//! Whether this wallet can derive masternode operator keys from its HD seed
virtual bool canDeriveMasternodeOperatorKey() = 0;
//! Derive this wallet's next unused masternode operator key, record the index it
//! used and return the 32-byte secret plus its derivation path. `in_use` holds
//! 48-byte operator public keys that are already taken (every operator key
//! registered on-chain), so a wallet restored from its recovery phrase cannot hand
//! out a key another masternode already uses. Requires an unlocked wallet.
virtual bool deriveMasternodeOperatorKey(const std::vector<std::vector<unsigned char>>& in_use,
std::vector<unsigned char>& secret, std::string& path,
std::string& error) = 0;
//! Whether this wallet can produce the operator secret for this 48-byte public key.
//! Cheap and unlock-free: true when an index record matches it, or optimistically
//! when the wallet can derive at all (the real work happens in
//! getMasternodeOperatorKey, which reports failure precisely).
virtual bool haveMasternodeOperatorKey(const std::vector<unsigned char>& pubkey) = 0;
//! Fetch the operator secret for a 48-byte public key, from the recorded index or by
//! scanning a bounded range of indexes so a wallet restored from its recovery phrase
//! still finds the keys of masternodes it registered. Fills `path` with the
//! derivation path. Requires an unlocked wallet.
virtual bool getMasternodeOperatorKey(const std::vector<unsigned char>& pubkey,
std::vector<unsigned char>& secret, std::string& path,
std::string& error) = 0;

//! Return whether wallet has private key.
virtual bool isSpendable(const CScript& script) = 0;
virtual bool isSpendable(const CTxDestination& dest) = 0;
Expand Down
25 changes: 25 additions & 0 deletions src/node/interfaces.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,25 @@ class MnEntryImpl : public MnEntry
const int32_t& getPoSePenalty() const override { return m_dmn->pdmnState->nPoSePenalty; }
const int32_t& getRegisteredHeight() const override { return m_dmn->pdmnState->nRegisteredHeight; }
const uint16_t& getOperatorReward() const override { return m_dmn->nOperatorReward; }
std::vector<unsigned char> getOperatorPubKeyBytes() const override
{
// The basic scheme is picked explicitly so that the bytes stay the same across a scheme
// activation, which is what lets the wallet compare them against its own operator keys
return m_dmn->pdmnState->pubKeyOperator.Get().ToByteVector(/*specificLegacyScheme=*/false);
}
const uint256& getProTxHash() const override { return m_dmn->proTxHash; }
bool isShared() const override { return m_dmn->pdmnState->IsShared(); }
std::vector<interfaces::MnShare> getShares() const override
{
std::vector<interfaces::MnShare> ret;
ret.reserve(m_dmn->pdmnState->shares.size());
for (const auto& share : m_dmn->pdmnState->shares) {
ret.push_back({share.amount, share.scriptRefund, share.scriptReward, share.keyIDOwner});
}
return ret;
}
const uint32_t& getEarlyPeriodBlocks() const override { return m_dmn->pdmnState->nEarlyPeriodBlocks; }
const CAmount& getEarlyPenalty() const override { return m_dmn->pdmnState->nEarlyPenalty; }
};

class MnListImpl : public MnList
Expand Down Expand Up @@ -946,6 +964,13 @@ class NodeImpl : public Node
return m_context->active_ctx != nullptr;
}
bool isLoadingBlocks() override { return node::fReindex || node::fImporting; }
bool isV24Active() override
{
// Can briefly block the calling (GUI) thread while validation holds
// cs_main; callers that poll should cache the result once it turns true
LOCK(::cs_main);
return DeploymentActiveAfter(chainman().ActiveChain().Tip(), chainman(), Consensus::DEPLOYMENT_V24);
}
void setNetworkActive(bool active) override
{
if (m_context->connman) {
Expand Down
25 changes: 25 additions & 0 deletions src/qt/forms/masternodelist.ui
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@
<string>Evo</string>
</property>
</item>
<item>
<property name="text">
<string>Shared</string>
</property>
</item>
</widget>
</item>
<item>
Expand Down Expand Up @@ -93,6 +98,26 @@
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btnRegisterMasternode">
<property name="toolTip">
<string>Register a new masternode or evonode using this wallet</string>
</property>
<property name="text">
<string>Register Masternode…</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btnSharedMasternode">
<property name="toolTip">
<string>Create a masternode with collateral pooled by multiple parties, or continue a saved session</string>
</property>
<property name="text">
<string>Shared Masternode…</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="0">
Expand Down
Loading
Loading