Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 8 additions & 0 deletions doc/release-notes-7594.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Wallet
------

* Mnemonic-backed wallets can now derive and recover masternode operator BLS
keys using the DashSync-compatible `m/9'/coin'/3'/3'/index` path. A successful
registration can record the public key's derivation index without storing the
operator secret separately; the recovery phrase remains the backup. Existing
operator keys and wallets without a BIP39 recovery phrase are unchanged.
8 changes: 8 additions & 0 deletions doc/release-notes-7600.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
RPC changes
-----------

- Normal and Evo `protx` registration and maintenance commands now share a
typed provider-transaction implementation with other wallet frontends. RPC
names and successful result formats are unchanged. Transactions whose wallet
inputs cannot be signed completely now fail with a wallet error instead of
returning or attempting to broadcast a partially signed transaction. (#7600)
5 changes: 5 additions & 0 deletions src/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ BITCOIN_CORE_H = \
evo/mnhftx.h \
evo/netinfo.h \
evo/providertx.h \
evo/providertx_service.h \
evo/simplifiedmns.h \
evo/smldiff.h \
evo/specialtx.h \
Expand Down Expand Up @@ -280,7 +281,9 @@ BITCOIN_CORE_H = \
interfaces/handler.h \
interfaces/init.h \
interfaces/ipc.h \
interfaces/masternode_operator.h \
interfaces/node.h \
interfaces/providertx.h \
interfaces/wallet.h \
kernel/blockmanager_opts.h \
kernel/chain.h \
Expand Down Expand Up @@ -476,6 +479,7 @@ BITCOIN_CORE_H = \
wallet/hdchain.h \
wallet/ismine.h \
wallet/load.h \
wallet/masternode_operator.h \
wallet/receive.h \
wallet/rpc/util.h \
wallet/rpc/wallet.h \
Expand Down Expand Up @@ -539,6 +543,7 @@ libbitcoin_node_a_SOURCES = \
evo/mnauth.cpp \
evo/mnhftx.cpp \
evo/providertx.cpp \
evo/providertx_service.cpp \
evo/simplifiedmns.cpp \
evo/smldiff.cpp \
evo/specialtx.cpp \
Expand Down
1 change: 1 addition & 0 deletions src/Makefile.test.include
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ if ENABLE_WALLET
BITCOIN_TESTS += \
wallet/test/bip39_tests.cpp \
wallet/test/coinjoin_tests.cpp \
wallet/test/masternode_operator_tests.cpp \
wallet/test/psbt_wallet_tests.cpp \
wallet/test/spend_tests.cpp \
wallet/test/wallet_tests.cpp \
Expand Down
2 changes: 2 additions & 0 deletions src/bls/bls.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

#ifndef BUILD_BITCOIN_INTERNAL
#include <support/allocators/mt_pooled_secure.h>
#include <support/cleanse.h>
#endif

#include <cassert>
Expand Down Expand Up @@ -73,6 +74,7 @@ void CBLSSecretKey::MakeNewKey()
} catch (...) {
}
}
memory_cleanse(buf, sizeof(buf));
fValid = true;
cachedHash.SetNull();
}
Expand Down
97 changes: 86 additions & 11 deletions src/evo/providertx.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -79,30 +79,105 @@ bool IsPayoutListKeySafe(const MasternodePayoutShares& payouts, const CTxDestina
return true;
}

template <typename ProTx>
bool IsNetInfoTriviallyValid(const ProTx& proTx, TxValidationState& state)
static bool IsNetInfoTriviallyValid(const std::shared_ptr<NetInfoInterface>& net_info, MnType type, TxValidationState& state)
{
if (!proTx.netInfo->HasEntries(NetInfoPurpose::CORE_P2P)) {
if (!net_info->HasEntries(NetInfoPurpose::CORE_P2P)) {
// Mandatory for all nodes
return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-netinfo-empty");
}
if (proTx.nType == MnType::Regular) {
if (type == MnType::Regular) {
// Regular nodes shouldn't populate Platform-specific fields
if (proTx.netInfo->HasEntries(NetInfoPurpose::PLATFORM_HTTPS) ||
proTx.netInfo->HasEntries(NetInfoPurpose::PLATFORM_P2P)) {
if (net_info->HasEntries(NetInfoPurpose::PLATFORM_HTTPS) || net_info->HasEntries(NetInfoPurpose::PLATFORM_P2P)) {
return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-netinfo-bad");
}
}
if (proTx.netInfo->CanStorePlatform() && proTx.nType == MnType::Evo) {
if (net_info->CanStorePlatform() && type == MnType::Evo) {
// Platform fields are mandatory for EvoNodes
if (!proTx.netInfo->HasEntries(NetInfoPurpose::PLATFORM_HTTPS) ||
!proTx.netInfo->HasEntries(NetInfoPurpose::PLATFORM_P2P)) {
if (!net_info->HasEntries(NetInfoPurpose::PLATFORM_HTTPS) || !net_info->HasEntries(NetInfoPurpose::PLATFORM_P2P)) {
return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-netinfo-empty");
}
}
return true;
}

static bool CheckNetInfo(const NetInfoInterface& net_info, TxValidationState& state)
{
switch (net_info.Validate()) {
case NetInfoStatus::BadAddress:
return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-netinfo-addr");
case NetInfoStatus::BadPort:
return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-netinfo-port");
case NetInfoStatus::BadType:
return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-netinfo-addr-type");
case NetInfoStatus::NotRoutable:
return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-netinfo-addr-unroutable");
case NetInfoStatus::Malformed:
return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-netinfo-bad");
case NetInfoStatus::Success:
return true;
case NetInfoStatus::BadInput:
return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-netinfo-entry");
case NetInfoStatus::Duplicate:
return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-dup-netinfo-entry");
case NetInfoStatus::MaxLimit:
return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-netinfo-maxlimit");
}
assert(false);
}

bool CheckProviderNetworkFields(const std::shared_ptr<NetInfoInterface>& net_info, MnType type, uint16_t version,
const uint160* platform_node_id, uint16_t platform_p2p_port,
uint16_t platform_http_port, bool allow_empty, TxValidationState& state)
{
if (!net_info || net_info->CanStorePlatform() != (version >= ProTxVersion::ExtAddr)) {
return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-protx-netinfo-version");
}
if (net_info->IsEmpty()) {
if (!allow_empty) {
return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-netinfo-empty");
}
} else {
if (!IsNetInfoTriviallyValid(net_info, type, state) || !CheckNetInfo(*net_info, state)) {
return false;
}
}

if (type != MnType::Evo) return true;
if (platform_node_id && platform_node_id->IsNull()) {
return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-platform-nodeid");
}
if (version >= ProTxVersion::ExtAddr) {
if (platform_p2p_port != 0) {
return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-platform-p2p-port");
}
if (platform_http_port != 0) {
return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-platform-http-port");
}
return true;
}

if (::IsNodeOnMainnet()) {
if (platform_p2p_port != ::MainParams().GetDefaultPlatformP2PPort()) {
return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-platform-p2p-port");
}
if (platform_http_port != ::MainParams().GetDefaultPlatformHTTPPort()) {
return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-platform-http-port");
}
}
if (platform_p2p_port == ::MainParams().GetDefaultPort()) {
return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-platform-p2p-port");
}
if (platform_http_port == ::MainParams().GetDefaultPort()) {
return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-platform-http-port");
}

const uint16_t core_port{net_info->GetPrimary().GetPort()};
if (platform_p2p_port == platform_http_port || platform_p2p_port == core_port || platform_http_port == core_port) {
return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-platform-dup-ports");
}
return true;
}

bool CProRegTx::IsTriviallyValid(TxValidationState& state) const
{
if (nVersion == 0 || nVersion > ProTxVersion::ExtAddr) {
Expand All @@ -129,7 +204,7 @@ bool CProRegTx::IsTriviallyValid(TxValidationState& state) const
if (netInfo->CanStorePlatform() != (nVersion >= ProTxVersion::ExtAddr)) {
return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-protx-netinfo-version");
}
if (!netInfo->IsEmpty() && !IsNetInfoTriviallyValid(*this, state)) {
if (!netInfo->IsEmpty() && !IsNetInfoTriviallyValid(netInfo, nType, state)) {
// pass the state returned by the function above
return false;
}
Expand Down Expand Up @@ -199,7 +274,7 @@ bool CProUpServTx::IsTriviallyValid(TxValidationState& state) const
if (netInfo->IsEmpty()) {
return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-netinfo-empty");
}
if (!IsNetInfoTriviallyValid(*this, state)) {
if (!IsNetInfoTriviallyValid(netInfo, nType, state)) {
// pass the state returned by the function above
return false;
}
Expand Down
6 changes: 6 additions & 0 deletions src/evo/providertx.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@ template<class T>
[[nodiscard]] std::string PayoutListToString(const MasternodePayoutShares& payouts);
[[nodiscard]] UniValue PayoutListToJson(const MasternodePayoutShares& payouts);

/** Validate all provider network fields using the same rules as special transaction validation.
* Pass nullptr for platform_node_id when validating endpoint input separately from the rest of a payload. */
[[nodiscard]] bool CheckProviderNetworkFields(const std::shared_ptr<NetInfoInterface>& net_info, MnType type,
uint16_t version, const uint160* platform_node_id, uint16_t platform_p2p_port,
uint16_t platform_http_port, bool allow_empty, TxValidationState& state);

class CProRegTx
{
public:
Expand Down
Loading
Loading