Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
fdbc06b
refactor: removed unused CMasternodePayoutShare ctor
knst Jun 26, 2026
a0be501
refactor: let compiler to auto-generate operator== and != for Mastern…
knst Jun 26, 2026
b6f59dd
refactor: rename CMasternodePayoutShare to MasternodePayoutShare acco…
knst Jun 26, 2026
c01a1ed
fix!: merge v3 and v4 protx versions so far as they introduced by the…
knst Jun 26, 2026
8b9c2e8
fix: empty payouts after v4->v3 merge
knst Jul 7, 2026
1996a73
refactor: simplify calls of GetOwnerPayouts by templating class
knst Jun 30, 2026
f6802a9
refactor: simplify implementation of regression tests evo_trivialvali…
knst May 1, 2026
f573cf2
fix!: use the same version along all protx special transactions
knst Apr 30, 2026
e990114
test: rename feature_dip3_v19 functional test to feature_protx_version
knst Jun 23, 2026
1c41191
test: address review comments about preserving v2 / v3 version for dm…
knst Jun 24, 2026
f771b92
refactor: simplify implementation of providertx versioning
knst Apr 30, 2026
8cb7d3a
fix!: remove restrictions for v1 -> v3 protx updates as irrelevant step
knst Jul 3, 2026
f82af9f
test: for migration v1->v3 protx
knst Jul 3, 2026
d63c69b
fix: preserve masternode state version on ProUpRevTx after v24
knst Jul 3, 2026
fdc6e6a
test: for preserving masternode state version on ProUpRevTx
knst Jul 5, 2026
9a86b08
test: scriptpayout after migration to v3 protx
knst Jul 6, 2026
48d3735
test: added regression tests for legacy -> basic migration
knst Jul 17, 2026
94f3129
fix: proper migration for legacy bls keys to the protx v>=2
knst Jul 17, 2026
5f66192
fixup
knst Jul 17, 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
4 changes: 2 additions & 2 deletions src/common/bloom.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ bool CBloomFilter::CheckSpecialTransactionMatchesAndUpdate(const CTransaction &t
switch(tx.nType) {
case(TRANSACTION_PROVIDER_REGISTER): {
if (const auto opt_proTx = GetTxPayload<CProRegTx>(tx)) {
const auto owner_payouts = GetOwnerPayouts(opt_proTx->nVersion, opt_proTx->scriptPayout, opt_proTx->payouts);
const auto owner_payouts = GetOwnerPayouts(*opt_proTx);
const bool found_payout = std::any_of(owner_payouts.begin(), owner_payouts.end(),
[&](const auto& payout) { return CheckScript(payout.scriptPayout); });
if(contains(opt_proTx->collateralOutpoint) ||
Expand Down Expand Up @@ -163,7 +163,7 @@ bool CBloomFilter::CheckSpecialTransactionMatchesAndUpdate(const CTransaction &t
if (const auto opt_proTx = GetTxPayload<CProUpRegTx>(tx)) {
if(contains(opt_proTx->proTxHash))
return true;
const auto owner_payouts = GetOwnerPayouts(opt_proTx->nVersion, opt_proTx->scriptPayout, opt_proTx->payouts);
const auto owner_payouts = GetOwnerPayouts(*opt_proTx);
const bool found_payout = std::any_of(owner_payouts.begin(), owner_payouts.end(),
[&](const auto& payout) { return CheckScript(payout.scriptPayout); });
if(contains(opt_proTx->keyIDVoting) ||
Expand Down
8 changes: 4 additions & 4 deletions src/evo/core_write.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ UniValue CDeterministicMNState::ToJson(MnType nType) const
}

CTxDestination dest;
if (nVersion >= ProTxVersion::MultiPayout) {
if (nVersion >= ProTxVersion::ExtAddr) {
obj.pushKV("payouts", PayoutListToJson(payouts));
} else if (ExtractDestination(scriptPayout, dest)) {
obj.pushKV("payoutAddress", EncodeDestination(dest));
Expand Down Expand Up @@ -335,7 +335,7 @@ UniValue CProRegTx::ToJson() const
ret.pushKV("addresses", GetNetInfoWithLegacyFields(*this, nType));
ret.pushKV("ownerAddress", EncodeDestination(PKHash(keyIDOwner)));
ret.pushKV("votingAddress", EncodeDestination(PKHash(keyIDVoting)));
if (nVersion >= ProTxVersion::MultiPayout) {
if (nVersion >= ProTxVersion::ExtAddr) {
ret.pushKV("payouts", PayoutListToJson(payouts));
} else if (CTxDestination dest; ExtractDestination(scriptPayout, dest)) {
ret.pushKV("payoutAddress", EncodeDestination(dest));
Expand Down Expand Up @@ -373,7 +373,7 @@ UniValue CProUpRegTx::ToJson() const
ret.pushKV("version", nVersion);
ret.pushKV("proTxHash", proTxHash.ToString());
ret.pushKV("votingAddress", EncodeDestination(PKHash(keyIDVoting)));
if (nVersion >= ProTxVersion::MultiPayout) {
if (nVersion >= ProTxVersion::ExtAddr) {
ret.pushKV("payouts", PayoutListToJson(payouts));
} else if (CTxDestination dest; ExtractDestination(scriptPayout, dest)) {
ret.pushKV("payoutAddress", EncodeDestination(dest));
Expand Down Expand Up @@ -582,7 +582,7 @@ UniValue CSimplifiedMNListEntry::ToJson(bool extended) const

if (extended) {
CTxDestination dest;
if (nVersion >= ProTxVersion::MultiPayout) {
if (nVersion >= ProTxVersion::ExtAddr) {
obj.pushKV("payouts", PayoutListToJson(payouts));
} else if (ExtractDestination(scriptPayout, dest)) {
obj.pushKV("payoutAddress", EncodeDestination(dest));
Expand Down
2 changes: 1 addition & 1 deletion src/evo/deterministicmns.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ CSimplifiedMNListEntry CDeterministicMN::to_sml_entry() const
const CDeterministicMNState& state{*pdmnState};
return CSimplifiedMNListEntry(proTxHash, state.confirmedHash, state.netInfo, state.pubKeyOperator,
state.keyIDVoting, !state.IsBanned(), state.platformHTTPPort, state.platformNodeID,
state.scriptPayout, GetOwnerPayouts(state.nVersion, state.scriptPayout, state.payouts),
state.scriptPayout, GetOwnerPayouts(state),
state.scriptOperatorPayout, state.nVersion, nType);
}

Expand Down
12 changes: 11 additions & 1 deletion src/evo/deterministicmns.h
Original file line number Diff line number Diff line change
Expand Up @@ -551,7 +551,17 @@ class CDeterministicMNList
template <typename T>
[[nodiscard]] bool UpdateUniqueProperty(const CDeterministicMN& dmn, const T& oldValue, const T& newValue)
{
if (oldValue == newValue) {
// A BLS operator key can keep the same point while its serialized encoding (legacy<->basic)
// changes on a version transition; the map is keyed by GetUniquePropertyHash(), so only that
// hash reveals that the entry must be re-keyed. Detect this for the operator key alone and keep
// the plain value comparison for every other unique property, so unrelated paths are untouched.
// The encoding change is produced solely by SetStateVersion() under the v24-gated version bump,
// so pre-v24 blocks are unaffected.
if constexpr (std::is_same_v<std::decay_t<T>, CBLSLazyPublicKey>) {
if (GetUniquePropertyHash(oldValue) == GetUniquePropertyHash(newValue)) {
return true;
}
} else if (oldValue == newValue) {
return true;
}
static const T nullValue{};
Expand Down
2 changes: 1 addition & 1 deletion src/evo/dmnstate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ std::string CDeterministicMNState::ToString() const
if (ExtractDestination(scriptPayout, dest)) {
payoutAddress = EncodeDestination(dest);
}
const auto owner_payouts = GetOwnerPayouts(nVersion, scriptPayout, payouts);
const auto owner_payouts = GetOwnerPayouts(*this);
const std::string payoutList = PayoutListToString(owner_payouts);
if (ExtractDestination(scriptOperatorPayout, dest)) {
operatorPayoutAddress = EncodeDestination(dest);
Expand Down
2 changes: 1 addition & 1 deletion src/evo/dmnstate.h
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ class CDeterministicMNState
obj.keyIDVoting,
NetInfoSerWrapper(const_cast<std::shared_ptr<NetInfoInterface>&>(obj.netInfo),
obj.nVersion >= ProTxVersion::ExtAddr));
if (obj.nVersion >= ProTxVersion::MultiPayout) {
if (obj.nVersion >= ProTxVersion::ExtAddr) {
READWRITE(obj.payouts);
} else {
READWRITE(obj.scriptPayout);
Expand Down
2 changes: 1 addition & 1 deletion src/evo/netinfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
#include <evo/netinfo.h>

#include <chainparams.h>
#include <evo/providertx.h>
#include <evo/types.h>
#include <netbase.h>
#include <span.h>
#include <util/check.h>
Expand Down
63 changes: 15 additions & 48 deletions src/evo/providertx.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,38 +13,9 @@
#include <hash.h>
#include <script/standard.h>
#include <tinyformat.h>
#include <validation.h>

#include <set>

namespace ProTxVersion {
template <typename T>
[[nodiscard]] uint16_t GetMaxFromDeployment(gsl::not_null<const CBlockIndex*> pindexPrev,
const ChainstateManager& chainman, std::optional<bool> is_basic_override)
{
constexpr bool is_extaddr_eligible{std::is_same_v<std::decay_t<T>, CProRegTx> || std::is_same_v<std::decay_t<T>, CProUpServTx>};
constexpr bool is_multipayout_eligible{std::is_same_v<std::decay_t<T>, CProRegTx> || std::is_same_v<std::decay_t<T>, CProUpRegTx>};
const bool is_v24_active{DeploymentActiveAfter(pindexPrev, chainman, Consensus::DEPLOYMENT_V24)};
return ProTxVersion::GetMax(
is_basic_override ? *is_basic_override
: DeploymentActiveAfter(pindexPrev, chainman.GetConsensus(), Consensus::DEPLOYMENT_V19),
is_extaddr_eligible ? is_v24_active : false,
is_multipayout_eligible ? is_v24_active : false);
}
template uint16_t GetMaxFromDeployment<CProRegTx>(gsl::not_null<const CBlockIndex*> pindexPrev,
const ChainstateManager& chainman,
std::optional<bool> is_basic_override);
template uint16_t GetMaxFromDeployment<CProUpServTx>(gsl::not_null<const CBlockIndex*> pindexPrev,
const ChainstateManager& chainman,
std::optional<bool> is_basic_override);
template uint16_t GetMaxFromDeployment<CProUpRegTx>(gsl::not_null<const CBlockIndex*> pindexPrev,
const ChainstateManager& chainman,
std::optional<bool> is_basic_override);
template uint16_t GetMaxFromDeployment<CProUpRevTx>(gsl::not_null<const CBlockIndex*> pindexPrev,
const ChainstateManager& chainman,
std::optional<bool> is_basic_override);
} // namespace ProTxVersion

static bool IsValidPayoutScript(const CScript& script)
{
return script.IsPayToPublicKeyHash() || script.IsPayToScriptHash();
Expand All @@ -60,7 +31,7 @@ bool IsPayoutListTriviallyValid(const MasternodePayoutShares& payouts, const CKe
uint32_t total_reward{0};
std::set<CScript> seen_scripts;
for (const auto& payout : payouts) {
if (payout.reward < CMasternodePayoutShare::MIN_REWARD || payout.reward > CMasternodePayoutShare::MAX_REWARD) {
if (payout.reward < MasternodePayoutShare::MIN_REWARD || payout.reward > MasternodePayoutShare::MAX_REWARD) {
return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-payout-reward");
}
total_reward += payout.reward;
Expand All @@ -82,7 +53,7 @@ bool IsPayoutListTriviallyValid(const MasternodePayoutShares& payouts, const CKe
}
}

if (total_reward != CMasternodePayoutShare::MAX_REWARD) {
if (total_reward != MasternodePayoutShare::MAX_REWARD) {
return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-payout-reward-sum");
}
return true;
Expand Down Expand Up @@ -132,10 +103,9 @@ bool IsNetInfoTriviallyValid(const ProTx& proTx, TxValidationState& state)
return true;
}

bool CProRegTx::IsTriviallyValid(gsl::not_null<const CBlockIndex*> pindexPrev, const ChainstateManager& chainman,
TxValidationState& state) const
bool CProRegTx::IsTriviallyValid(TxValidationState& state) const
{
if (nVersion == 0 || nVersion > ProTxVersion::GetMaxFromDeployment<decltype(*this)>(pindexPrev, chainman)) {
if (nVersion == 0 || nVersion > ProTxVersion::ExtAddr) {
return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-protx-version");
}
if (nVersion < ProTxVersion::BasicBLS && nType == MnType::Evo) {
Expand All @@ -154,7 +124,7 @@ bool CProRegTx::IsTriviallyValid(gsl::not_null<const CBlockIndex*> pindexPrev, c
if (pubKeyOperator.IsLegacy() != (nVersion == ProTxVersion::LegacyBLS)) {
return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-operator-pubkey");
}
const auto owner_payouts = GetOwnerPayouts(nVersion, scriptPayout, payouts);
const auto owner_payouts = GetOwnerPayouts(*this);
if (!IsPayoutListTriviallyValid(owner_payouts, keyIDOwner, keyIDVoting, state)) return false;
if (netInfo->CanStorePlatform() != (nVersion >= ProTxVersion::ExtAddr)) {
return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-protx-netinfo-version");
Expand Down Expand Up @@ -183,7 +153,7 @@ std::string CProRegTx::MakeSignString() const
// We only include the important stuff in the string form...

CTxDestination dest;
const std::string strPayout = nVersion >= ProTxVersion::MultiPayout
const std::string strPayout = nVersion >= ProTxVersion::ExtAddr
? PayoutListToString(payouts)
: (ExtractDestination(scriptPayout, dest) ? EncodeDestination(dest) : HexStr(scriptPayout));

Expand All @@ -200,7 +170,7 @@ std::string CProRegTx::MakeSignString() const

std::string CProRegTx::ToString() const
{
const std::string payee = PayoutListToString(GetOwnerPayouts(nVersion, scriptPayout, payouts));
const std::string payee = PayoutListToString(GetOwnerPayouts(*this));

return strprintf("CProRegTx(nVersion=%d, nType=%d, collateralOutpoint=%s, netInfo=%s, nOperatorReward=%f, "
"ownerAddress=%s, pubKeyOperator=%s, votingAddress=%s, scriptPayout=%s, platformNodeID=%s%s)\n",
Expand All @@ -212,10 +182,9 @@ std::string CProRegTx::ToString() const
: strprintf(", platformP2PPort=%d, platformHTTPPort=%d", platformP2PPort, platformHTTPPort)));
}

bool CProUpServTx::IsTriviallyValid(gsl::not_null<const CBlockIndex*> pindexPrev, const ChainstateManager& chainman,
TxValidationState& state) const
bool CProUpServTx::IsTriviallyValid(TxValidationState& state) const
{
if (nVersion == 0 || nVersion > ProTxVersion::GetMaxFromDeployment<decltype(*this)>(pindexPrev, chainman)) {
if (nVersion == 0 || nVersion > ProTxVersion::ExtAddr) {
return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-protx-version");
}
if (nVersion < ProTxVersion::BasicBLS && nType == MnType::Evo) {
Expand Down Expand Up @@ -257,10 +226,9 @@ std::string CProUpServTx::ToString() const
: strprintf(", platformP2PPort=%d, platformHTTPPort=%d", platformP2PPort, platformHTTPPort)));
}

bool CProUpRegTx::IsTriviallyValid(gsl::not_null<const CBlockIndex*> pindexPrev, const ChainstateManager& chainman,
TxValidationState& state) const
bool CProUpRegTx::IsTriviallyValid(TxValidationState& state) const
{
if (nVersion == 0 || nVersion > ProTxVersion::GetMaxFromDeployment<decltype(*this)>(pindexPrev, chainman)) {
if (nVersion == 0 || nVersion > ProTxVersion::ExtAddr) {
return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-protx-version");
}
if (nMode != 0) {
Expand All @@ -273,22 +241,21 @@ bool CProUpRegTx::IsTriviallyValid(gsl::not_null<const CBlockIndex*> pindexPrev,
if (pubKeyOperator.IsLegacy() != (nVersion == ProTxVersion::LegacyBLS)) {
return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-operator-pubkey");
}
if (!IsPayoutListTriviallyValid(GetOwnerPayouts(nVersion, scriptPayout, payouts), CKeyID{}, keyIDVoting, state)) return false;
if (!IsPayoutListTriviallyValid(GetOwnerPayouts(*this), CKeyID{}, keyIDVoting, state)) return false;
return true;
}

std::string CProUpRegTx::ToString() const
{
const std::string payee = PayoutListToString(GetOwnerPayouts(nVersion, scriptPayout, payouts));
const std::string payee = PayoutListToString(GetOwnerPayouts(*this));

return strprintf("CProUpRegTx(nVersion=%d, proTxHash=%s, pubKeyOperator=%s, votingAddress=%s, payoutAddress=%s)",
nVersion, proTxHash.ToString(), pubKeyOperator.ToString(), EncodeDestination(PKHash(keyIDVoting)), payee);
}

bool CProUpRevTx::IsTriviallyValid(gsl::not_null<const CBlockIndex*> pindexPrev, const ChainstateManager& chainman,
TxValidationState& state) const
bool CProUpRevTx::IsTriviallyValid(TxValidationState& state) const
{
if (nVersion == 0 || nVersion > ProTxVersion::GetMaxFromDeployment<decltype(*this)>(pindexPrev, chainman)) {
if (nVersion == 0 || nVersion > ProTxVersion::ExtAddr) {
return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-protx-version");
}

Expand Down
Loading
Loading