From c9040177e1d5e2a42eda494328f419d0a987a237 Mon Sep 17 00:00:00 2001 From: ale Date: Thu, 4 Jan 2024 10:00:54 -0500 Subject: [PATCH 01/15] feat(consensus): Implement DIP0026 Expand CProRegTx and CProUpRegTx according to DIP0026 standard, change serialization and deserialization methods in order to take in account the new field, Add consensus rules specified in the DIP0026 (max 32 payees, sum of payment shares cannot be greater than 10000,...) change dmnstate in order to take in account the new field change consensus rules in such a way that masternodes will pay all the payees add multiple payees in the qt masternode table --- src/bloom.cpp | 23 ++++++-- src/bloom.h | 2 + src/evo/deterministicmns.cpp | 13 +++-- src/evo/dmnstate.cpp | 19 ++++--- src/evo/dmnstate.h | 22 +++++--- src/evo/providertx.cpp | 94 ++++++++++++++++++++----------- src/evo/providertx.h | 105 +++++++++++++++++++++++++++-------- src/evo/simplifiedmns.cpp | 17 +++--- src/evo/simplifiedmns.h | 2 +- src/masternode/payments.cpp | 7 ++- src/qt/masternodelist.cpp | 24 +++++--- 11 files changed, 228 insertions(+), 100 deletions(-) diff --git a/src/bloom.cpp b/src/bloom.cpp index 54c9204a2ddd..c2678d2ed425 100644 --- a/src/bloom.cpp +++ b/src/bloom.cpp @@ -110,6 +110,17 @@ bool CBloomFilter::CheckScript(const CScript &script) const return false; } +bool CBloomFilter::CheckPayeeSharesScripts(const std::vector& payoutShares) const +{ + bool scriptCheck = false; + for (const auto& payoutShare : payoutShares) { + if (CheckScript(payoutShare.scriptPayout)) { + scriptCheck = true; + break; + } + } + return scriptCheck; +} // If the transaction is a special transaction that has a registration // transaction hash, test the registration transaction hash. // If the transaction is a special transaction with any public keys or any @@ -128,10 +139,10 @@ bool CBloomFilter::CheckSpecialTransactionMatchesAndUpdate(const CTransaction &t case(TRANSACTION_PROVIDER_REGISTER): { CProRegTx proTx; if (GetTxPayload(tx, proTx)) { - if(contains(proTx.collateralOutpoint) || - contains(proTx.keyIDOwner) || - contains(proTx.keyIDVoting) || - CheckScript(proTx.scriptPayout)) { + if (contains(proTx.collateralOutpoint) || + contains(proTx.keyIDOwner) || + contains(proTx.keyIDVoting) || + CheckPayeeSharesScripts(proTx.payoutShares)) { if ((nFlags & BLOOM_UPDATE_MASK) == BLOOM_UPDATE_ALL) insert(tx.GetHash()); return true; @@ -158,8 +169,8 @@ bool CBloomFilter::CheckSpecialTransactionMatchesAndUpdate(const CTransaction &t if (GetTxPayload(tx, proTx)) { if(contains(proTx.proTxHash)) return true; - if(contains(proTx.keyIDVoting) || - CheckScript(proTx.scriptPayout)) { + if (contains(proTx.keyIDVoting) || + CheckPayeeSharesScripts(proTx.payoutShares)) { if ((nFlags & BLOOM_UPDATE_MASK) == BLOOM_UPDATE_ALL) insert(proTx.proTxHash); return true; diff --git a/src/bloom.h b/src/bloom.h index 29bfb61522e5..a92569750591 100644 --- a/src/bloom.h +++ b/src/bloom.h @@ -15,6 +15,7 @@ class CScript; class CTransaction; class CTxOut; class uint256; +class PayoutShare; //! 20,000 items with fp rate < 0.1% or 10,000 items and <0.0001% static constexpr unsigned int MAX_BLOOM_FILTER_SIZE = 36000; // bytes @@ -56,6 +57,7 @@ class CBloomFilter // Check matches for arbitrary script data elements bool CheckScript(const CScript& script) const; + bool CheckPayeeSharesScripts(const std::vector& payoutShares) const; // Check particular CTxOut helper bool ProcessTxOut(const CTxOut& txout, const uint256& hash, unsigned int index); // Check additional matches for special transactions diff --git a/src/evo/deterministicmns.cpp b/src/evo/deterministicmns.cpp index 73d72b2be639..bb2eb019e5e9 100644 --- a/src/evo/deterministicmns.cpp +++ b/src/evo/deterministicmns.cpp @@ -877,7 +877,7 @@ bool CDeterministicMNManager::BuildNewListFromBlock(const CBlock& block, gsl::no newState->pubKeyOperator = proTx.pubKeyOperator; } newState->keyIDVoting = proTx.keyIDVoting; - newState->scriptPayout = proTx.scriptPayout; + newState->payoutShares = proTx.payoutShares; newList.UpdateMN(proTx.proTxHash, newState); @@ -1513,7 +1513,8 @@ static std::optional GetValidatedPayload(const CTransaction& tx, gsl::not return std::nullopt; } const bool is_basic_scheme_active{DeploymentActiveAfter(pindexPrev, Params().GetConsensus(), Consensus::DEPLOYMENT_V19)}; - if (!ptx.IsTriviallyValid(is_basic_scheme_active, state)) { + const bool is_multi_payout_active{DeploymentActiveAfter(pindexPrev, Params().GetConsensus(), Consensus::DEPLOYMENT_DIP0026)}; + if (!ptx.IsTriviallyValid(is_basic_scheme_active, is_multi_payout_active, state)) { // pass the state returned by the function above return std::nullopt; } @@ -1706,10 +1707,10 @@ bool CheckProUpRegTx(const CTransaction& tx, gsl::not_null p const auto& ptx{*opt_ptx}; CTxDestination payoutDest; - if (!ExtractDestination(ptx.scriptPayout, payoutDest)) { - // should not happen as we checked script types before - return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-payee-dest"); - } + const auto& scriptIterator = std::find_if(ptx.payoutShares.begin(), ptx.payoutShares.end(), [&](const auto& payoutShare){ + return !ExtractDestination(payoutShare.scriptPayout, payoutDest); + }); + if(scriptIterator != ptx.payoutShares.end()) return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-payee-dest"); auto mnList = deterministicMNManager->GetListForBlock(pindexPrev); auto dmn = mnList.GetMN(ptx.proTxHash); diff --git a/src/evo/dmnstate.cpp b/src/evo/dmnstate.cpp index 54f4aa7f7950..56b48d3ae74e 100644 --- a/src/evo/dmnstate.cpp +++ b/src/evo/dmnstate.cpp @@ -18,7 +18,7 @@ std::string CDeterministicMNState::ToString() const CTxDestination dest; std::string payoutAddress = "unknown"; std::string operatorPayoutAddress = "none"; - if (ExtractDestination(scriptPayout, dest)) { + if (ExtractDestination(payoutShares[0].scriptPayout, dest)) { payoutAddress = EncodeDestination(dest); } if (ExtractDestination(scriptOperatorPayout, dest)) { @@ -52,10 +52,13 @@ UniValue CDeterministicMNState::ToJson(MnType nType) const obj.pushKV("platformHTTPPort", platformHTTPPort); } + UniValue payoutArray; + payoutArray.setArray(); CTxDestination dest; - if (ExtractDestination(scriptPayout, dest)) { - obj.pushKV("payoutAddress", EncodeDestination(dest)); + for (const auto& payoutShare : payoutShares) { + payoutArray.push_back(payoutShare.ToJson()); } + obj.pushKV("payouts", payoutArray); obj.pushKV("pubKeyOperator", pubKeyOperator.ToString()); if (ExtractDestination(scriptOperatorPayout, dest)) { obj.pushKV("operatorPayoutAddress", EncodeDestination(dest)); @@ -100,11 +103,13 @@ UniValue CDeterministicMNStateDiff::ToJson(MnType nType) const if (fields & Field_keyIDVoting) { obj.pushKV("votingAddress", EncodeDestination(PKHash(state.keyIDVoting))); } - if (fields & Field_scriptPayout) { - CTxDestination dest; - if (ExtractDestination(state.scriptPayout, dest)) { - obj.pushKV("payoutAddress", EncodeDestination(dest)); + if (fields & Field_payoutShares) { + UniValue payoutArray; + payoutArray.setArray(); + for (const auto& payoutShare : state.payoutShares) { + payoutArray.push_back(payoutShare.ToJson()); } + obj.pushKV("payouts", payoutArray); } if (fields & Field_scriptOperatorPayout) { CTxDestination dest; diff --git a/src/evo/dmnstate.h b/src/evo/dmnstate.h index 0a0f62316dae..7679eb8d1708 100644 --- a/src/evo/dmnstate.h +++ b/src/evo/dmnstate.h @@ -155,7 +155,8 @@ class CDeterministicMNState CBLSLazyPublicKey pubKeyOperator; CKeyID keyIDVoting; CService addr; - CScript scriptPayout; + // initialized as a vector of length one, in order to deserialize correctly old messages + std::vector payoutShares{PayoutShare(CScript())}; CScript scriptOperatorPayout; uint160 platformNodeID{}; @@ -170,7 +171,7 @@ class CDeterministicMNState pubKeyOperator(proTx.pubKeyOperator), keyIDVoting(proTx.keyIDVoting), addr(proTx.addr), - scriptPayout(proTx.scriptPayout), + payoutShares(proTx.payoutShares), platformNodeID(proTx.platformNodeID), platformP2PPort(proTx.platformP2PPort), platformHTTPPort(proTx.platformHTTPPort) @@ -189,7 +190,7 @@ class CDeterministicMNState pubKeyOperator(s.pubKeyOperator), keyIDVoting(s.keyIDVoting), addr(s.addr), - scriptPayout(s.scriptPayout), + payoutShares({PayoutShare(s.scriptPayout)}), scriptOperatorPayout(s.scriptOperatorPayout) {} explicit CDeterministicMNState(const CDeterministicMNState_mntype_format& s) : @@ -206,7 +207,7 @@ class CDeterministicMNState pubKeyOperator(s.pubKeyOperator), keyIDVoting(s.keyIDVoting), addr(s.addr), - scriptPayout(s.scriptPayout), + payoutShares({PayoutShare(s.scriptPayout)}), scriptOperatorPayout(s.scriptOperatorPayout), platformNodeID(s.platformNodeID), platformP2PPort(s.platformP2PPort), @@ -235,8 +236,13 @@ class CDeterministicMNState READWRITE(CBLSLazyPublicKeyVersionWrapper(const_cast(obj.pubKeyOperator), obj.nVersion == CProRegTx::LEGACY_BLS_VERSION)); READWRITE( obj.keyIDVoting, - obj.addr, - obj.scriptPayout, + obj.addr); + if (obj.nVersion < CProRegTx::MULTI_PAYOUT_VERSION) { + READWRITE(obj.payoutShares[0].scriptPayout); + }else{ + READWRITE(obj.payoutShares); + } + READWRITE( obj.scriptOperatorPayout, obj.platformNodeID, obj.platformP2PPort, @@ -302,7 +308,7 @@ class CDeterministicMNStateDiff Field_pubKeyOperator = 0x0200, Field_keyIDVoting = 0x0400, Field_addr = 0x0800, - Field_scriptPayout = 0x1000, + Field_payoutShares = 0x1000, Field_scriptOperatorPayout = 0x2000, Field_nConsecutivePayments = 0x4000, Field_platformNodeID = 0x8000, @@ -324,7 +330,7 @@ class CDeterministicMNStateDiff DMN_STATE_DIFF_LINE(pubKeyOperator) \ DMN_STATE_DIFF_LINE(keyIDVoting) \ DMN_STATE_DIFF_LINE(addr) \ - DMN_STATE_DIFF_LINE(scriptPayout) \ + DMN_STATE_DIFF_LINE(payoutShares) \ DMN_STATE_DIFF_LINE(scriptOperatorPayout) \ DMN_STATE_DIFF_LINE(nConsecutivePayments) \ DMN_STATE_DIFF_LINE(platformNodeID) \ diff --git a/src/evo/providertx.cpp b/src/evo/providertx.cpp index 89c1e7af4280..1d7b9a92c61f 100644 --- a/src/evo/providertx.cpp +++ b/src/evo/providertx.cpp @@ -12,9 +12,37 @@ #include #include -bool CProRegTx::IsTriviallyValid(bool is_basic_scheme_active, TxValidationState& state) const +template +static bool TriviallyVerifyProRegPayees(const ProRegTx& proRegTx, TxValidationState& state) { - if (nVersion == 0 || nVersion > GetVersion(is_basic_scheme_active)) { + const std::vector& payoutShares = proRegTx.payoutShares; + uint16_t totalPayoutReward{0}; + if (payoutShares.size() > 32) { + return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-payee-size"); + } + if (payoutShares.size() > 1 && proRegTx.nVersion < ProRegTx::MULTI_PAYOUT_VERSION) { + return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-payee-mismatch"); + } + for (const auto& payoutShare : payoutShares) { + CScript scriptPayout = payoutShare.scriptPayout; + if (!scriptPayout.IsPayToPublicKeyHash() && !scriptPayout.IsPayToScriptHash()) { + return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-payee"); + } + + totalPayoutReward += payoutShare.payoutShareReward; + if (payoutShare.payoutShareReward > 10000) { + return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-payee-reward"); + } + } + if (totalPayoutReward != 10000) { + return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-payee-reward-sum"); + } + return true; +} + +bool CProRegTx::IsTriviallyValid(bool is_basic_scheme_active, bool is_multi_payout_active, TxValidationState& state) const +{ + if (nVersion == 0 || nVersion > GetVersion(is_basic_scheme_active, is_multi_payout_active)) { return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-protx-version"); } if (nVersion != BASIC_BLS_VERSION && nType == MnType::Evo) { @@ -33,24 +61,21 @@ bool CProRegTx::IsTriviallyValid(bool is_basic_scheme_active, TxValidationState& if (pubKeyOperator.IsLegacy() != (nVersion == LEGACY_BLS_VERSION)) { return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-operator-pubkey"); } - if (!scriptPayout.IsPayToPublicKeyHash() && !scriptPayout.IsPayToScriptHash()) { - return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-payee"); - } - - CTxDestination payoutDest; - if (!ExtractDestination(scriptPayout, payoutDest)) { - // should not happen as we checked script types before - return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-payee-dest"); - } - // don't allow reuse of payout key for other keys (don't allow people to put the payee key onto an online server) - if (payoutDest == CTxDestination(PKHash(keyIDOwner)) || payoutDest == CTxDestination(PKHash(keyIDVoting))) { - return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-payee-reuse"); - } - if (nOperatorReward > 10000) { return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-operator-reward"); } - + if (!TriviallyVerifyProRegPayees(*this, state)) return false; + for (const auto& payoutShare : payoutShares) { + CTxDestination payoutDest; + if (!ExtractDestination(payoutShare.scriptPayout, payoutDest)) { + // should not happen as we checked script types before + return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-payee-dest"); + } + // don't allow reuse of payout key for other keys (don't allow people to put the payee key onto an online server) + if (payoutDest == CTxDestination(PKHash(keyIDOwner)) || payoutDest == CTxDestination(PKHash(keyIDVoting))) { + return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-payee-reuse"); + } + } return true; } @@ -62,13 +87,19 @@ std::string CProRegTx::MakeSignString() const CTxDestination destPayout; std::string strPayout; - if (ExtractDestination(scriptPayout, destPayout)) { - strPayout = EncodeDestination(destPayout); - } else { - strPayout = HexStr(scriptPayout); + for (const auto& payoutShare : payoutShares) { + CScript scriptPayout = payoutShare.scriptPayout; + if (ExtractDestination(scriptPayout, destPayout)) { + strPayout = EncodeDestination(destPayout); + } else { + strPayout = HexStr(scriptPayout); + } + if (nVersion < MULTI_PAYOUT_VERSION) { + s += strPayout + "|"; + } else { + s += (strPayout + "|" + strprintf("%d", payoutShare.payoutShareReward) + "|"); + } } - - s += strPayout + "|"; s += strprintf("%d", nOperatorReward) + "|"; s += EncodeDestination(PKHash(keyIDOwner)) + "|"; s += EncodeDestination(PKHash(keyIDVoting)) + "|"; @@ -83,7 +114,7 @@ std::string CProRegTx::ToString() const { CTxDestination dest; std::string payee = "unknown"; - if (ExtractDestination(scriptPayout, dest)) { + if (ExtractDestination(payoutShares[0].scriptPayout, dest)) { payee = EncodeDestination(dest); } @@ -91,7 +122,7 @@ std::string CProRegTx::ToString() const nVersion, ToUnderlying(nType), collateralOutpoint.ToStringShort(), addr.ToString(), (double)nOperatorReward / 100, EncodeDestination(PKHash(keyIDOwner)), pubKeyOperator.ToString(), EncodeDestination(PKHash(keyIDVoting)), payee, platformNodeID.ToString(), platformP2PPort, platformHTTPPort); } -bool CProUpServTx::IsTriviallyValid(bool is_basic_scheme_active, TxValidationState& state) const +bool CProUpServTx::IsTriviallyValid(bool is_basic_scheme_active, bool is_multi_payout_active, TxValidationState& state) const { if (nVersion == 0 || nVersion > GetVersion(is_basic_scheme_active)) { return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-protx-version"); @@ -115,9 +146,9 @@ std::string CProUpServTx::ToString() const nVersion, ToUnderlying(nType), proTxHash.ToString(), addr.ToString(), payee, platformNodeID.ToString(), platformP2PPort, platformHTTPPort); } -bool CProUpRegTx::IsTriviallyValid(bool is_basic_scheme_active, TxValidationState& state) const +bool CProUpRegTx::IsTriviallyValid(bool is_basic_scheme_active, bool is_multi_payout_active, TxValidationState& state) const { - if (nVersion == 0 || nVersion > GetVersion(is_basic_scheme_active)) { + if (nVersion == 0 || nVersion > GetVersion(is_basic_scheme_active, is_multi_payout_active)) { return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-protx-version"); } if (nMode != 0) { @@ -130,17 +161,14 @@ bool CProUpRegTx::IsTriviallyValid(bool is_basic_scheme_active, TxValidationStat if (pubKeyOperator.IsLegacy() != (nVersion == LEGACY_BLS_VERSION)) { return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-operator-pubkey"); } - if (!scriptPayout.IsPayToPublicKeyHash() && !scriptPayout.IsPayToScriptHash()) { - return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-payee"); - } - return true; + return TriviallyVerifyProRegPayees(*this, state); } std::string CProUpRegTx::ToString() const { CTxDestination dest; std::string payee = "unknown"; - if (ExtractDestination(scriptPayout, dest)) { + if (ExtractDestination(payoutShares[0].scriptPayout, dest)) { payee = EncodeDestination(dest); } @@ -148,7 +176,7 @@ std::string CProUpRegTx::ToString() const nVersion, proTxHash.ToString(), pubKeyOperator.ToString(), EncodeDestination(PKHash(keyIDVoting)), payee); } -bool CProUpRevTx::IsTriviallyValid(bool is_basic_scheme_active, TxValidationState& state) const +bool CProUpRevTx::IsTriviallyValid(bool is_basic_scheme_active, bool is_multi_payout_active, TxValidationState& state) const { if (nVersion == 0 || nVersion > GetVersion(is_basic_scheme_active)) { return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-protx-version"); diff --git a/src/evo/providertx.h b/src/evo/providertx.h index dab5c3330a15..e818a477913c 100644 --- a/src/evo/providertx.h +++ b/src/evo/providertx.h @@ -21,16 +21,53 @@ class CBlockIndex; class CCoinsViewCache; class TxValidationState; +class PayoutShare +{ +public: + CScript scriptPayout{}; + uint16_t payoutShareReward{0}; + PayoutShare() = default; + explicit PayoutShare(const CScript& scriptPayout, const uint16_t& payoutShareReward = 10000) : + scriptPayout(scriptPayout), payoutShareReward(payoutShareReward){}; + SERIALIZE_METHODS(PayoutShare, obj) + { + READWRITE(obj.scriptPayout, + obj.payoutShareReward); + } + bool operator==(const PayoutShare& payoutShare) const + { + return (this->scriptPayout == payoutShare.scriptPayout && this->payoutShareReward == payoutShare.payoutShareReward); + } + bool operator!=(const PayoutShare& payoutShare) const + { + return !(*this == payoutShare); + } + [[nodiscard]] UniValue ToJson() const + { + UniValue ret(UniValue::VOBJ); + CTxDestination dest; + ret.pushKV("payoutAddress", ExtractDestination(scriptPayout, dest) ? EncodeDestination(dest) : "UNKNOWN"); + ret.pushKV("payoutShareReward", payoutShareReward); + return ret; + } +}; + class CProRegTx { public: static constexpr auto SPECIALTX_TYPE = TRANSACTION_PROVIDER_REGISTER; static constexpr uint16_t LEGACY_BLS_VERSION = 1; static constexpr uint16_t BASIC_BLS_VERSION = 2; + static constexpr uint16_t MULTI_PAYOUT_VERSION = 3; - [[nodiscard]] static constexpr auto GetVersion(const bool is_basic_scheme_active) -> uint16_t + [[nodiscard]] static constexpr auto GetVersion(const bool is_basic_scheme_active, const bool is_multi_payout_active) -> uint16_t { - return is_basic_scheme_active ? BASIC_BLS_VERSION : LEGACY_BLS_VERSION; + if (is_multi_payout_active) { + // multi payout is activated after basic scheme + return MULTI_PAYOUT_VERSION; + } else { + return is_basic_scheme_active ? BASIC_BLS_VERSION : LEGACY_BLS_VERSION; + } } uint16_t nVersion{LEGACY_BLS_VERSION}; // message version @@ -45,7 +82,8 @@ class CProRegTx CBLSLazyPublicKey pubKeyOperator; CKeyID keyIDVoting; uint16_t nOperatorReward{0}; - CScript scriptPayout; + // initialized as a default vector of length one, in order to deserialize correctly old messages + std::vector payoutShares{PayoutShare(CScript())}; uint256 inputsHash; // replay protection std::vector vchSig; @@ -54,7 +92,7 @@ class CProRegTx READWRITE( obj.nVersion ); - if (obj.nVersion == 0 || obj.nVersion > BASIC_BLS_VERSION) { + if (obj.nVersion == 0 || obj.nVersion > MULTI_PAYOUT_VERSION) { // unknown version, bail out early return; } @@ -67,10 +105,13 @@ class CProRegTx obj.keyIDOwner, CBLSLazyPublicKeyVersionWrapper(const_cast(obj.pubKeyOperator), (obj.nVersion == LEGACY_BLS_VERSION)), obj.keyIDVoting, - obj.nOperatorReward, - obj.scriptPayout, - obj.inputsHash - ); + obj.nOperatorReward); + if (obj.nVersion < MULTI_PAYOUT_VERSION) { + READWRITE(obj.payoutShares[0].scriptPayout); + } else { + READWRITE(obj.payoutShares); + } + READWRITE(obj.inputsHash); if (obj.nType == MnType::Evo) { READWRITE( obj.platformNodeID, @@ -100,9 +141,12 @@ class CProRegTx obj.pushKV("ownerAddress", EncodeDestination(PKHash(keyIDOwner))); obj.pushKV("votingAddress", EncodeDestination(PKHash(keyIDVoting))); - if (CTxDestination dest; ExtractDestination(scriptPayout, dest)) { - obj.pushKV("payoutAddress", EncodeDestination(dest)); + UniValue payoutArray; + payoutArray.setArray(); + for (const auto& payoutShare : payoutShares) { + payoutArray.push_back(payoutShare.ToJson()); } + obj.pushKV("payouts", payoutArray); obj.pushKV("pubKeyOperator", pubKeyOperator.ToString()); obj.pushKV("operatorReward", (double)nOperatorReward / 100); if (nType == MnType::Evo) { @@ -114,7 +158,7 @@ class CProRegTx return obj; } - bool IsTriviallyValid(bool is_bls_legacy_scheme, TxValidationState& state) const; + bool IsTriviallyValid(bool is_bls_legacy_scheme, bool is_multi_payout_active, TxValidationState& state) const; }; class CProUpServTx @@ -194,7 +238,7 @@ class CProUpServTx return obj; } - bool IsTriviallyValid(bool is_bls_legacy_scheme, TxValidationState& state) const; + bool IsTriviallyValid(bool is_bls_legacy_scheme, bool is_multi_payout_active, TxValidationState& state) const; }; class CProUpRegTx @@ -203,10 +247,16 @@ class CProUpRegTx static constexpr auto SPECIALTX_TYPE = TRANSACTION_PROVIDER_UPDATE_REGISTRAR; static constexpr uint16_t LEGACY_BLS_VERSION = 1; static constexpr uint16_t BASIC_BLS_VERSION = 2; + static constexpr uint16_t MULTI_PAYOUT_VERSION = 3; - [[nodiscard]] static constexpr auto GetVersion(const bool is_basic_scheme_active) -> uint16_t + [[nodiscard]] static constexpr auto GetVersion(const bool is_basic_scheme_active, const bool is_multi_payout_active) -> uint16_t { - return is_basic_scheme_active ? BASIC_BLS_VERSION : LEGACY_BLS_VERSION; + if (is_multi_payout_active) { + // multi payout is activated after basic scheme + return MULTI_PAYOUT_VERSION; + } else { + return is_basic_scheme_active ? BASIC_BLS_VERSION : LEGACY_BLS_VERSION; + } } uint16_t nVersion{LEGACY_BLS_VERSION}; // message version @@ -214,7 +264,8 @@ class CProUpRegTx uint16_t nMode{0}; // only 0 supported for now CBLSLazyPublicKey pubKeyOperator; CKeyID keyIDVoting; - CScript scriptPayout; + // initialized as a default vector of length one, in order to deserialize correctly old messages + std::vector payoutShares{PayoutShare(CScript())}; uint256 inputsHash; // replay protection std::vector vchSig; @@ -223,7 +274,7 @@ class CProUpRegTx READWRITE( obj.nVersion ); - if (obj.nVersion == 0 || obj.nVersion > BASIC_BLS_VERSION) { + if (obj.nVersion == 0 || obj.nVersion > MULTI_PAYOUT_VERSION) { // unknown version, bail out early return; } @@ -231,10 +282,13 @@ class CProUpRegTx obj.proTxHash, obj.nMode, CBLSLazyPublicKeyVersionWrapper(const_cast(obj.pubKeyOperator), (obj.nVersion == LEGACY_BLS_VERSION)), - obj.keyIDVoting, - obj.scriptPayout, - obj.inputsHash - ); + obj.keyIDVoting); + if (obj.nVersion < MULTI_PAYOUT_VERSION) { + READWRITE(obj.payoutShares[0].scriptPayout); + } else { + READWRITE(obj.payoutShares); + } + READWRITE(obj.inputsHash); if (!(s.GetType() & SER_GETHASH)) { READWRITE( obj.vchSig @@ -251,15 +305,18 @@ class CProUpRegTx obj.pushKV("version", nVersion); obj.pushKV("proTxHash", proTxHash.ToString()); obj.pushKV("votingAddress", EncodeDestination(PKHash(keyIDVoting))); - if (CTxDestination dest; ExtractDestination(scriptPayout, dest)) { - obj.pushKV("payoutAddress", EncodeDestination(dest)); + UniValue payoutArray; + payoutArray.setArray(); + for (const auto& payoutShare : payoutShares) { + payoutArray.push_back(payoutShare.ToJson()); } + obj.pushKV("payouts", payoutArray); obj.pushKV("pubKeyOperator", pubKeyOperator.ToString()); obj.pushKV("inputsHash", inputsHash.ToString()); return obj; } - bool IsTriviallyValid(bool is_bls_legacy_scheme, TxValidationState& state) const; + bool IsTriviallyValid(bool is_bls_legacy_scheme, bool is_multi_payout_active, TxValidationState& state) const; }; class CProUpRevTx @@ -323,7 +380,7 @@ class CProUpRevTx return obj; } - bool IsTriviallyValid(bool is_bls_legacy_scheme, TxValidationState& state) const; + bool IsTriviallyValid(bool is_bls_legacy_scheme, bool is_multi_payout_active, TxValidationState& state) const; }; template diff --git a/src/evo/simplifiedmns.cpp b/src/evo/simplifiedmns.cpp index 9ced1bb0d251..1a08beb65790 100644 --- a/src/evo/simplifiedmns.cpp +++ b/src/evo/simplifiedmns.cpp @@ -36,7 +36,7 @@ CSimplifiedMNListEntry::CSimplifiedMNListEntry(const CDeterministicMN& dmn) : isValid(!dmn.pdmnState->IsBanned()), platformHTTPPort(dmn.pdmnState->platformHTTPPort), platformNodeID(dmn.pdmnState->platformNodeID), - scriptPayout(dmn.pdmnState->scriptPayout), + payoutShares(dmn.pdmnState->payoutShares), scriptOperatorPayout(dmn.pdmnState->scriptOperatorPayout), nVersion(dmn.pdmnState->nVersion == CProRegTx::LEGACY_BLS_VERSION ? LEGACY_BLS_VERSION : BASIC_BLS_VERSION), nType(dmn.nType) @@ -55,8 +55,8 @@ std::string CSimplifiedMNListEntry::ToString() const CTxDestination dest; std::string payoutAddress = "unknown"; std::string operatorPayoutAddress = "none"; - if (ExtractDestination(scriptPayout, dest)) { - payoutAddress = EncodeDestination(dest); + if (ExtractDestination(payoutShares[0].scriptPayout, dest)) { + payoutAddress = EncodeDestination(dest); } if (ExtractDestination(scriptOperatorPayout, dest)) { operatorPayoutAddress = EncodeDestination(dest); @@ -85,9 +85,12 @@ UniValue CSimplifiedMNListEntry::ToJson(bool extended) const if (extended) { CTxDestination dest; - if (ExtractDestination(scriptPayout, dest)) { - obj.pushKV("payoutAddress", EncodeDestination(dest)); + UniValue payoutArray; + payoutArray.setArray(); + for (const auto& payoutShare : payoutShares) { + payoutArray.push_back(payoutShare.ToJson()); } + obj.pushKV("payouts", payoutArray); if (ExtractDestination(scriptOperatorPayout, dest)) { obj.pushKV("operatorPayoutAddress", EncodeDestination(dest)); } @@ -303,8 +306,8 @@ CSimplifiedMNListDiff BuildSimplifiedDiff(const CDeterministicMNList& from, cons CSimplifiedMNListEntry sme1(toPtr); CSimplifiedMNListEntry sme2(*fromPtr); if ((sme1 != sme2) || - (extended && (sme1.scriptPayout != sme2.scriptPayout || sme1.scriptOperatorPayout != sme2.scriptOperatorPayout))) { - diffRet.mnList.push_back(std::move(sme1)); + (extended && (sme1.payoutShares != sme2.payoutShares || sme1.scriptOperatorPayout != sme2.scriptOperatorPayout))) { + diffRet.mnList.push_back(std::move(sme1)); } } }); diff --git a/src/evo/simplifiedmns.h b/src/evo/simplifiedmns.h index 4abde7c39d7b..97543c8f1115 100644 --- a/src/evo/simplifiedmns.h +++ b/src/evo/simplifiedmns.h @@ -36,7 +36,7 @@ class CSimplifiedMNListEntry bool isValid{false}; uint16_t platformHTTPPort{0}; uint160 platformNodeID{}; - CScript scriptPayout; // mem-only + std::vector payoutShares; // mem-only CScript scriptOperatorPayout; // mem-only uint16_t nVersion{LEGACY_BLS_VERSION}; MnType nType{MnType::Regular}; diff --git a/src/masternode/payments.cpp b/src/masternode/payments.cpp index 2bf94d213d04..ff4e0dbe9edb 100644 --- a/src/masternode/payments.cpp +++ b/src/masternode/payments.cpp @@ -60,7 +60,12 @@ } if (masternodeReward > 0) { - voutMasternodePaymentsRet.emplace_back(masternodeReward, dmnPayee->pdmnState->scriptPayout); + for (const auto& payoutShare : dmnPayee->pdmnState->payoutShares) { + CAmount payeeReward = (masternodeReward * payoutShare.payoutShareReward) / 10000; + if (payeeReward > 0) { + voutMasternodePaymentsRet.emplace_back(payeeReward, payoutShare.scriptPayout); + } + } } if (operatorReward > 0) { voutMasternodePaymentsRet.emplace_back(operatorReward, dmnPayee->pdmnState->scriptOperatorPayout); diff --git a/src/qt/masternodelist.cpp b/src/qt/masternodelist.cpp index 36ca8ae1de48..5795ce3d43be 100644 --- a/src/qt/masternodelist.cpp +++ b/src/qt/masternodelist.cpp @@ -216,11 +216,16 @@ void MasternodeList::updateDIP3List() mnList.ForEachMN(false, [&](auto& dmn) { if (walletModel && ui->checkBoxMyMasternodesOnly->isChecked()) { + bool fMyPayee = false; + for (const auto& payoutShare : dmn.pdmnState->payoutShares) { + fMyPayee |= walletModel->wallet().isSpendable(payoutShare.scriptPayout); + if (fMyPayee) break; + } bool fMyMasternode = setOutpts.count(dmn.collateralOutpoint) || - walletModel->wallet().isSpendable(PKHash(dmn.pdmnState->keyIDOwner)) || - walletModel->wallet().isSpendable(PKHash(dmn.pdmnState->keyIDVoting)) || - walletModel->wallet().isSpendable(dmn.pdmnState->scriptPayout) || - walletModel->wallet().isSpendable(dmn.pdmnState->scriptOperatorPayout); + walletModel->wallet().isSpendable(PKHash(dmn.pdmnState->keyIDOwner)) || + walletModel->wallet().isSpendable(PKHash(dmn.pdmnState->keyIDVoting)) || + fMyPayee || + walletModel->wallet().isSpendable(dmn.pdmnState->scriptOperatorPayout); if (!fMyMasternode) return; } // populate list @@ -243,9 +248,14 @@ void MasternodeList::updateDIP3List() QTableWidgetItem* nextPaymentItem = new CMasternodeListWidgetItem(strNextPayment, nNextPayment); CTxDestination payeeDest; - QString payeeStr = tr("UNKNOWN"); - if (ExtractDestination(dmn.pdmnState->scriptPayout, payeeDest)) { - payeeStr = QString::fromStdString(EncodeDestination(payeeDest)); + QString payeeStr; + for (const auto& payeeShare : dmn.pdmnState->payoutShares) { + if (!payeeStr.isEmpty()) payeeStr += ", "; + if (ExtractDestination(payeeShare.scriptPayout, payeeDest)) { + payeeStr += QString::fromStdString(EncodeDestination(payeeDest)); + } else { + payeeStr += tr("UNKNOWN"); + } } QTableWidgetItem* payeeItem = new QTableWidgetItem(payeeStr); From fc643f6d6ec4978b77f4fad8503208ec4ddbdc89 Mon Sep 17 00:00:00 2001 From: ale Date: Sun, 7 Jan 2024 11:28:00 -0500 Subject: [PATCH 02/15] feat: add DIP0026 to regtest chain params --- src/chainparams.cpp | 9 +++++++++ src/consensus/params.h | 6 +++--- src/deploymentinfo.cpp | 8 ++++++-- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/chainparams.cpp b/src/chainparams.cpp index ef2adfa23acb..b8011e165ee8 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -829,6 +829,15 @@ class CRegTestParams : public CChainParams { consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nFalloffCoeff = 5; // this corresponds to 10 periods consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].useEHF = true; + consensus.vDeployments[Consensus::DEPLOYMENT_DIP0026].bit = 11; + consensus.vDeployments[Consensus::DEPLOYMENT_DIP0026].nStartTime = 0; + consensus.vDeployments[Consensus::DEPLOYMENT_DIP0026].nTimeout = Consensus::BIP9Deployment::NO_TIMEOUT; + consensus.vDeployments[Consensus::DEPLOYMENT_DIP0026].nWindowSize = 12; + consensus.vDeployments[Consensus::DEPLOYMENT_DIP0026].nThresholdStart = 9; // 80% of 12 + consensus.vDeployments[Consensus::DEPLOYMENT_DIP0026].nThresholdMin = 7; // 60% of 12 + consensus.vDeployments[Consensus::DEPLOYMENT_DIP0026].nFalloffCoeff = 5; // this corresponds to 10 periods + consensus.vDeployments[Consensus::DEPLOYMENT_DIP0026].useEHF = true; + // The best chain should have at least this much work. consensus.nMinimumChainWork = uint256S("0x00"); diff --git a/src/consensus/params.h b/src/consensus/params.h index 74c148ef5be0..4949ba85c80d 100644 --- a/src/consensus/params.h +++ b/src/consensus/params.h @@ -31,15 +31,15 @@ enum BuriedDeployment : int16_t }; constexpr bool ValidDeployment(BuriedDeployment dep) { return DEPLOYMENT_HEIGHTINCB <= dep && dep <= DEPLOYMENT_V19; } -enum DeploymentPos : uint16_t -{ +enum DeploymentPos : uint16_t { DEPLOYMENT_TESTDUMMY, DEPLOYMENT_V20, // Deployment of EHF, LLMQ Randomness Beacon DEPLOYMENT_MN_RR, // Deployment of Masternode Reward Location Reallocation + DEPLOYMENT_DIP0026, // Deployment of Multi Payees in a single ProRegTx // NOTE: Also add new deployments to VersionBitsDeploymentInfo in deploymentinfo.cpp MAX_VERSION_BITS_DEPLOYMENTS }; -constexpr bool ValidDeployment(DeploymentPos dep) { return DEPLOYMENT_TESTDUMMY <= dep && dep <= DEPLOYMENT_MN_RR; } +constexpr bool ValidDeployment(DeploymentPos dep) { return DEPLOYMENT_TESTDUMMY <= dep && dep <= DEPLOYMENT_DIP0026; } /** * Struct for each individual consensus rule change using BIP9. diff --git a/src/deploymentinfo.cpp b/src/deploymentinfo.cpp index 17da5d3d36fa..2bd7aaf5c8d1 100644 --- a/src/deploymentinfo.cpp +++ b/src/deploymentinfo.cpp @@ -8,8 +8,8 @@ const struct VBDeploymentInfo VersionBitsDeploymentInfo[Consensus::MAX_VERSION_BITS_DEPLOYMENTS] = { { - /*.name =*/ "testdummy", - /*.gbt_force =*/ true, + /*.name =*/"testdummy", + /*.gbt_force =*/true, }, { /*.name =*/"v20", @@ -19,6 +19,10 @@ const struct VBDeploymentInfo VersionBitsDeploymentInfo[Consensus::MAX_VERSION_B /*.name =*/"mn_rr", /*.gbt_force =*/true, }, + { + /*.name =*/"multi_mn_payee", + /*.gbt_force =*/true, + }, }; std::string DeploymentName(Consensus::BuriedDeployment dep) From 094c791a9d73c722d97c31626adbbc5e149dedda Mon Sep 17 00:00:00 2001 From: ale Date: Sun, 7 Jan 2024 11:30:44 -0500 Subject: [PATCH 03/15] test: DIP0026 unit tests --- src/test/block_reward_reallocation_tests.cpp | 4 +- src/test/data/trivially_invalid.json | 48 ++++++ src/test/data/trivially_valid.json | 12 ++ src/test/evo_deterministicmns_tests.cpp | 168 ++++++++++++++++--- src/test/evo_trivialvalidation.cpp | 20 ++- 5 files changed, 218 insertions(+), 34 deletions(-) diff --git a/src/test/block_reward_reallocation_tests.cpp b/src/test/block_reward_reallocation_tests.cpp index 3563a2d1aae1..d3cc5dfc22de 100644 --- a/src/test/block_reward_reallocation_tests.cpp +++ b/src/test/block_reward_reallocation_tests.cpp @@ -116,13 +116,13 @@ static CMutableTransaction CreateProRegTx(const CTxMemPool& mempool, SimpleUTXOM operatorKeyRet.MakeNewKey(); CProRegTx proTx; - proTx.nVersion = CProRegTx::GetVersion(!bls::bls_legacy_scheme); + proTx.nVersion = CProRegTx::GetVersion(!bls::bls_legacy_scheme, false); proTx.collateralOutpoint.n = 0; proTx.addr = LookupNumeric("1.1.1.1", port); proTx.keyIDOwner = ownerKeyRet.GetPubKey().GetID(); proTx.pubKeyOperator.Set(operatorKeyRet.GetPublicKey(), bls::bls_legacy_scheme.load()); proTx.keyIDVoting = ownerKeyRet.GetPubKey().GetID(); - proTx.scriptPayout = scriptPayout; + proTx.payoutShares = {PayoutShare(scriptPayout)}; CMutableTransaction tx; tx.nVersion = 3; diff --git a/src/test/data/trivially_invalid.json b/src/test/data/trivially_invalid.json index cfc789cbee37..bfd17160c816 100644 --- a/src/test/data/trivially_invalid.json +++ b/src/test/data/trivially_invalid.json @@ -95,5 +95,53 @@ "03000400010000000000000000000000000000000000000000000000000000000000000000ffffffff016affffffff0100f2052a01000000016a00000000a401005f6d5e103c0f35f091201d9b7f26bc8a81ec870b2b1167021b08fc32dbd192aeffff81a667e71154308cc67481aa7da40f3cbfa48dca9075832e5c5977ab56c193c51286f9b11119a67e8c85eb57843f29655a97d41012c030204d09c6032006b8e49efd6c449580f8e49f553a22dba78d3306bcf18015a4eced33c19e43d1932522655e1f70e0f4409e7cf836a1f8ca609952c4b459f75e93f6aeb2a094e5485617", "bad-protx-reason", "Transaction with reason set to uint16_t numerical limit" +], +[ + "e69f882d72dbd1d8cc9c039591cb668d3f39ae7bf683656dcb0d3ccbfe11a587", + "proregtx", + "multi_payee", + "030001000300c4d497c6929e04bf6bec4415e38ef8c6a57f83a6a2e538aa348fcdc701d650000000004746304302202ab359bad46974f83ea01ec0182fc61cce280bfd5af78c35a200547e01f274a6021f1505c16c90dde1b85d9b0897df0491ea245792f300a4126fdd1bf4aee51bed01ffffffff01168d635fbfd7df3a380a30dda5a4cf2d160ad47cc9614ac9f4e827f8876e26000000004847304402200bffa3bb172bf05e905e8f30a0a540e64bb117c395562c2604c885eb74ea7b3f0220474b50e93e2f6210abef70c954c845a412996f4609d829d0ef0b6db9ddde060701ffffffff012da8dc79c177e8deffc3e95308cda74baac13c5bcfbe4010af709dbe0c2bb1000000004847304402205472788b0d8023e262558c0f0e41486beb2846825faf137d046cd2fd6b51a4e702201b6f27aa9bb6f17f3c6f83effc0e6e9157730260e2fd68429ae2760bb337ee4f01ffffffff0200e87648170000001976a9146b0a56ae0e4f1cedf674162ffc8f38129cae6d0d88acb4beb3a7080000001976a9146b0a56ae0e4f1cedf674162ffc8f38129cae6d0d88ac00000000f003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff010101010001113df182d838fd6db3ca3c45fb547438a7c7b42c91c35f8e3b900da2173e8707643e7878cdf9b3ef35c101a89d9de18465061cd821de8fa1c02faf8eb4fbc71a9ef06316113df182d838fd6db3ca3c45fb547438a7c7b42c0000021976a9146b0a56ae0e4f1cedf674162ffc8f38129cae6d0d88ac401f1976a9146b0a56ae0e4f1cedf674162ffc8f38129cae6d0d88ac581bbd6317e6a65a8b7e6e691f26b3e2313259a541ba0a4421876debf12e42fbc82500", + "bad-protx-payee-reward-sum", + "Transaction with multiple payees, with the sum of their share greater than 10000" +], +[ + "0e91ee8c90caed41f589891904b2f57009694e8d415cd6d51f3dd5caf39a6f0e", + "proregtx", + "multi_payee", + "03000100030144ef217f355407c4714325d1dcbdd18f53fc5e1195d20f964345a4053f5051000000004847304402200e20975458378b8d4a9b96648a6d563f108ac80ef22a33280594720f91a91eb902203e688ccdd730f2e9bd20f30dbd5ee6b6633d5d9c97de503b9786531f00e7e2f001ffffffff01810049ccace812eadf0f327178a91e926f76394c7fafdcb39cd1ed2f48e3d100000000484730440220124a0295a31f8c6bb30d4884cc2289f12287236f0d43ad4965dec52c8cd1397702202afb910833e50c237d714244b8aef337aee4cfe1cc2ccc7af22d19d2a2c63e4c01ffffffff01c35affc55fa2f45961cc5a533d22bab9c7491a84cb5613c3ebf7e64fa2d88a000000004847304402204c1d376a808aa87ca35fbc6f86abc4b7636a3dbdbddafe8c8957c3c0ae6dccad022010d9deb0e6e3a547c9a0393475710b6dd2ec63ed8f48de1efe427a76752b707d01ffffffff0200e87648170000001976a9140e1d8c054f2634a1f6f45a57d06256651a6c6c3588acf6a058b7050000001976a9140e1d8c054f2634a1f6f45a57d06256651a6c6c3588ac00000000f003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff010101010001856a73dfe25e61022cc0490b5999cda0dae5bad6a62d4c0c8e8b4a6298ea41852a8a3ceee045730ed81f751e11552dec030a7ec7f17fe9a6db8394bfb3f9e0e46b9285e7856a73dfe25e61022cc0490b5999cda0dae5bad60000021976a9140e1d8c054f2634a1f6f45a57d06256651a6c6c3588ac01001976a9140e1d8c054f2634a1f6f45a57d06256651a6c6c3588ac1127a85d5bf29ea33c15c3c8fc625891486f95fb0ff6f44094d7a6ea07b6ff1efad800", + "bad-protx-payee-reward", + "Transaction with multiple payees, one of them has a share greater than 10000" +], +[ + "83d32597506dd377c18c3f717f263330de2fae1c774f69073be24c1d4f9a26c7", + "proregtx", + "multi_payee", + "030001000300c4d497c6929e04bf6bec4415e38ef8c6a57f83a6a2e538aa348fcdc701d650000000004847304402204445cbf336b943eeec2989cbdfb09917b896090b2f8773f93efe2ee6c2da702e02205a9f817c860a902df26eb8552f2d1672e665c2a61dc31d8b929425b05129892f01ffffffff01168d635fbfd7df3a380a30dda5a4cf2d160ad47cc9614ac9f4e827f8876e2600000000484730440220107a3462f90fdb707aff3371faa7311b11aff14fa4720d38a7b345c92d909f2602204613f90433feff555c173699b9fca2c9b54418d91ee08c2154314ff3a0b25e1a01ffffffff012da8dc79c177e8deffc3e95308cda74baac13c5bcfbe4010af709dbe0c2bb1000000004847304402202a66357c5098f84870fd8de7e11f3572c61ad4ea7848fcd77235dd487c5639d902205d0cd9cf7a1a3c97a7c8cc23a1fae25c9c8282416f8b6564762d0ad5cb32ab5801ffffffff0200e87648170000001976a91446d26fdb5673608912dfd99caea4bf5586b6948c88acb4beb3a7080000001976a91446d26fdb5673608912dfd99caea4bf5586b6948c88ac00000000fd300603000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff01010101000153c7b4c09b21b3ddd196e6b3ef534d48e4561a18b93ec5d945d455711834a39728d1dda42eaf8a9f838cabcdb0e3c6bae3fd6e93879f6bc0cd77561505cd97b66459314553c7b4c09b21b3ddd196e6b3ef534d48e4561a180000321976a91446d26fdb5673608912dfd99caea4bf5586b6948c88ac14001976a91446d26fdb5673608912dfd99caea4bf5586b6948c88ac14001976a91446d26fdb5673608912dfd99caea4bf5586b6948c88ac14001976a91446d26fdb5673608912dfd99caea4bf5586b6948c88ac14001976a91446d26fdb5673608912dfd99caea4bf5586b6948c88ac14001976a91446d26fdb5673608912dfd99caea4bf5586b6948c88ac14001976a91446d26fdb5673608912dfd99caea4bf5586b6948c88ac14001976a91446d26fdb5673608912dfd99caea4bf5586b6948c88ac14001976a91446d26fdb5673608912dfd99caea4bf5586b6948c88ac14001976a91446d26fdb5673608912dfd99caea4bf5586b6948c88ac14001976a91446d26fdb5673608912dfd99caea4bf5586b6948c88ac14001976a91446d26fdb5673608912dfd99caea4bf5586b6948c88ac14001976a91446d26fdb5673608912dfd99caea4bf5586b6948c88ac14001976a91446d26fdb5673608912dfd99caea4bf5586b6948c88ac14001976a91446d26fdb5673608912dfd99caea4bf5586b6948c88ac14001976a91446d26fdb5673608912dfd99caea4bf5586b6948c88ac14001976a91446d26fdb5673608912dfd99caea4bf5586b6948c88ac14001976a91446d26fdb5673608912dfd99caea4bf5586b6948c88ac14001976a91446d26fdb5673608912dfd99caea4bf5586b6948c88ac14001976a91446d26fdb5673608912dfd99caea4bf5586b6948c88ac14001976a91446d26fdb5673608912dfd99caea4bf5586b6948c88ac14001976a91446d26fdb5673608912dfd99caea4bf5586b6948c88ac14001976a91446d26fdb5673608912dfd99caea4bf5586b6948c88ac14001976a91446d26fdb5673608912dfd99caea4bf5586b6948c88ac14001976a91446d26fdb5673608912dfd99caea4bf5586b6948c88ac14001976a91446d26fdb5673608912dfd99caea4bf5586b6948c88ac14001976a91446d26fdb5673608912dfd99caea4bf5586b6948c88ac14001976a91446d26fdb5673608912dfd99caea4bf5586b6948c88ac14001976a91446d26fdb5673608912dfd99caea4bf5586b6948c88ac14001976a91446d26fdb5673608912dfd99caea4bf5586b6948c88ac14001976a91446d26fdb5673608912dfd99caea4bf5586b6948c88ac14001976a91446d26fdb5673608912dfd99caea4bf5586b6948c88ac14001976a91446d26fdb5673608912dfd99caea4bf5586b6948c88ac14001976a91446d26fdb5673608912dfd99caea4bf5586b6948c88ac14001976a91446d26fdb5673608912dfd99caea4bf5586b6948c88ac14001976a91446d26fdb5673608912dfd99caea4bf5586b6948c88ac14001976a91446d26fdb5673608912dfd99caea4bf5586b6948c88ac14001976a91446d26fdb5673608912dfd99caea4bf5586b6948c88ac14001976a91446d26fdb5673608912dfd99caea4bf5586b6948c88ac14001976a91446d26fdb5673608912dfd99caea4bf5586b6948c88ac14001976a91446d26fdb5673608912dfd99caea4bf5586b6948c88ac14001976a91446d26fdb5673608912dfd99caea4bf5586b6948c88ac14001976a91446d26fdb5673608912dfd99caea4bf5586b6948c88ac14001976a91446d26fdb5673608912dfd99caea4bf5586b6948c88ac14001976a91446d26fdb5673608912dfd99caea4bf5586b6948c88ac14001976a91446d26fdb5673608912dfd99caea4bf5586b6948c88ac14001976a91446d26fdb5673608912dfd99caea4bf5586b6948c88ac14001976a91446d26fdb5673608912dfd99caea4bf5586b6948c88ac14001976a91446d26fdb5673608912dfd99caea4bf5586b6948c88ac14001976a91446d26fdb5673608912dfd99caea4bf5586b6948c88ac1400bd6317e6a65a8b7e6e691f26b3e2313259a541ba0a4421876debf12e42fbc82500", + "bad-protx-payee-size", + "Transaction with more than 32 payees" +], +[ + "5601adaee479b129440900f57347e43dcfb64e78f2e580caf1ccb0212c7f4c09", + "proupregtx", + "multi_payee", + "03000300010144ef217f355407c4714325d1dcbdd18f53fc5e1195d20f964345a4053f5051000000004847304402200ceeb8a7a5201591ff24ce10a8e7e9f3af7eacca8defa4ec6791a85d4da0250202200a2fefe775d721c9ffadcbb79a55d40b4b68a5d65d80e95760b4c06212e732c801ffffffff0200e1f505000000001976a914751e76e8199196d454941c45d1b3a323f1433bd688ac0093459e0b0000001976a914751e76e8199196d454941c45d1b3a323f1433bd688ac00000000fd1f0103006be26b5a3f2af674f397bfd3c06a7f1c6c6d968b41432853d9f0b7f258e80358000081fce9a6bbef23b63e00f1f078da796a03a8e2c3b7a75f46312997e44f229d07ca47ec7b6c35a6a11ac6f7f4466c5541ac3359f818eae2c5e22d85e792256bfc4dff64ad031976a91454c858dd531fee622f55203bd4534a506bb7e19c88ace8031976a914f508c02ed7a0000e22d06528393403815cade37b88ac88131976a914f508c02ed7a0000e22d06528393403815cade37b88ac88131192c19da39880b1f83379fffd0829cce9be8e9b39ee163927a59a60742a997e4120843488e4f46d13bdb6e4d08e22caafb2f5c3dcc931495b7361fa4debdea041e86ef385b3f82beb19ef0eaa1975f97a2ba82cfcaa4f583222936b581cd2ecbedc", + "bad-protx-payee-reward-sum", + "Transaction with multiple payees, with the sum of their share greater than 10000" +], +[ + "48f3f77d73e254c997d0386d350ab06239100fb896dd0b6e81830ae92b8bf399", + "proupregtx", + "multi_payee", + "03000300010144ef217f355407c4714325d1dcbdd18f53fc5e1195d20f964345a4053f5051000000004847304402205dcb493f268ec94d351a81fa5e675f16c04d1a9e7d7f0b2f0cb7cc725f2530f202200c61a31ce89633d4d39b7b052588c2945ceea761ad8d35e0a6cc9b2d5ec795b901ffffffff0200e1f505000000001976a914751e76e8199196d454941c45d1b3a323f1433bd688ac0093459e0b0000001976a914751e76e8199196d454941c45d1b3a323f1433bd688ac00000000fd1f0103006955bfd47b2e71f06d9d68158bb8b1d27ca0bb9abf4288eeaf34f5c509baf81c0000b229e2b3ed971164ce7c79e694b2ca7a382b3b7cf26d4d170f192480a9717a2e64059eb7b07dffeeb3d849cbc2238f18d9383b47620a5b1c24bd2be98a476859764b12a7031976a9147cfff833a0fc9133d25bb4d79d81bfa8cb9b9b1c88ace8031976a9145f826c7cfd5cc54ebccecdd2cf67ed4546a280ab88ac88131976a9145f826c7cfd5cc54ebccecdd2cf67ed4546a280ab88ac409c1192c19da39880b1f83379fffd0829cce9be8e9b39ee163927a59a60742a997e411fb4683b29bf2e894aa32b15c280ab6a19d35c93fe9ace64fe1686529c266f01977da69eb7fb3e1f5c31326c29cc8cc2415b00c919ecef0811ea04e0d96d100bfb", + "bad-protx-payee-reward", + "Transaction with multiple payees, one of them has a share greater than 10000" +], +[ + "e857d978a2d38d927838e6d66cc1093e6c6ebc2934eb43e12ada037a58c7a9ca", + "proupregtx", + "multi_payee", + "03000300010144ef217f355407c4714325d1dcbdd18f53fc5e1195d20f964345a4053f5051000000004847304402202e023027c2c8c3afd2b223c407c74abe0bdbe40f8e7b4ad9bd183c54111784df02207c31f3a6e49951c8ccc170227749340b1af79f2200700fda257095f4c4ba71d801ffffffff0200e1f505000000001976a914751e76e8199196d454941c45d1b3a323f1433bd688ac0093459e0b0000001976a914751e76e8199196d454941c45d1b3a323f1433bd688ac00000000fd43060300a410eb7f825b59d315dd61b6f15446cc9387e5c27112f3234b2fc9036b5d718d0000a549f5e81590532112a85aa48f81efbc70063e7f36851a91554f1d28b1740946b11657620c88af9dc6054cd628d714c02f4e7d4afa59f867ece56dbf44551967ca6ea860321976a914b0f5ca7d48dce1c8ff07688f7a2071b28284e74a88ac14001976a914b0f5ca7d48dce1c8ff07688f7a2071b28284e74a88ac14001976a914b0f5ca7d48dce1c8ff07688f7a2071b28284e74a88ac14001976a914b0f5ca7d48dce1c8ff07688f7a2071b28284e74a88ac14001976a914b0f5ca7d48dce1c8ff07688f7a2071b28284e74a88ac14001976a914b0f5ca7d48dce1c8ff07688f7a2071b28284e74a88ac14001976a914b0f5ca7d48dce1c8ff07688f7a2071b28284e74a88ac14001976a914b0f5ca7d48dce1c8ff07688f7a2071b28284e74a88ac14001976a914b0f5ca7d48dce1c8ff07688f7a2071b28284e74a88ac14001976a914b0f5ca7d48dce1c8ff07688f7a2071b28284e74a88ac14001976a914b0f5ca7d48dce1c8ff07688f7a2071b28284e74a88ac14001976a914b0f5ca7d48dce1c8ff07688f7a2071b28284e74a88ac14001976a914b0f5ca7d48dce1c8ff07688f7a2071b28284e74a88ac14001976a914b0f5ca7d48dce1c8ff07688f7a2071b28284e74a88ac14001976a914b0f5ca7d48dce1c8ff07688f7a2071b28284e74a88ac14001976a914b0f5ca7d48dce1c8ff07688f7a2071b28284e74a88ac14001976a914b0f5ca7d48dce1c8ff07688f7a2071b28284e74a88ac14001976a914b0f5ca7d48dce1c8ff07688f7a2071b28284e74a88ac14001976a914b0f5ca7d48dce1c8ff07688f7a2071b28284e74a88ac14001976a914b0f5ca7d48dce1c8ff07688f7a2071b28284e74a88ac14001976a914b0f5ca7d48dce1c8ff07688f7a2071b28284e74a88ac14001976a914b0f5ca7d48dce1c8ff07688f7a2071b28284e74a88ac14001976a914b0f5ca7d48dce1c8ff07688f7a2071b28284e74a88ac14001976a914b0f5ca7d48dce1c8ff07688f7a2071b28284e74a88ac14001976a914b0f5ca7d48dce1c8ff07688f7a2071b28284e74a88ac14001976a914b0f5ca7d48dce1c8ff07688f7a2071b28284e74a88ac14001976a914b0f5ca7d48dce1c8ff07688f7a2071b28284e74a88ac14001976a914b0f5ca7d48dce1c8ff07688f7a2071b28284e74a88ac14001976a914b0f5ca7d48dce1c8ff07688f7a2071b28284e74a88ac14001976a914b0f5ca7d48dce1c8ff07688f7a2071b28284e74a88ac14001976a914b0f5ca7d48dce1c8ff07688f7a2071b28284e74a88ac14001976a914b0f5ca7d48dce1c8ff07688f7a2071b28284e74a88ac14001976a914b0f5ca7d48dce1c8ff07688f7a2071b28284e74a88ac14001976a914b0f5ca7d48dce1c8ff07688f7a2071b28284e74a88ac14001976a914b0f5ca7d48dce1c8ff07688f7a2071b28284e74a88ac14001976a914b0f5ca7d48dce1c8ff07688f7a2071b28284e74a88ac14001976a914b0f5ca7d48dce1c8ff07688f7a2071b28284e74a88ac14001976a914b0f5ca7d48dce1c8ff07688f7a2071b28284e74a88ac14001976a914b0f5ca7d48dce1c8ff07688f7a2071b28284e74a88ac14001976a914b0f5ca7d48dce1c8ff07688f7a2071b28284e74a88ac14001976a914b0f5ca7d48dce1c8ff07688f7a2071b28284e74a88ac14001976a914b0f5ca7d48dce1c8ff07688f7a2071b28284e74a88ac14001976a914b0f5ca7d48dce1c8ff07688f7a2071b28284e74a88ac14001976a914b0f5ca7d48dce1c8ff07688f7a2071b28284e74a88ac14001976a914b0f5ca7d48dce1c8ff07688f7a2071b28284e74a88ac14001976a914b0f5ca7d48dce1c8ff07688f7a2071b28284e74a88ac14001976a914b0f5ca7d48dce1c8ff07688f7a2071b28284e74a88ac14001976a914b0f5ca7d48dce1c8ff07688f7a2071b28284e74a88ac14001976a914b0f5ca7d48dce1c8ff07688f7a2071b28284e74a88ac14001976a914b0f5ca7d48dce1c8ff07688f7a2071b28284e74a88ac14001192c19da39880b1f83379fffd0829cce9be8e9b39ee163927a59a60742a997e411f71c269a0f41cb9b09a27a9a6b8faa43156668a7c3c5e5370660cd797ab3e5cb409e0f49975d0f4be9752f52317963bb0daecbadec5877d35abd18238d9a07d16", + "bad-protx-payee-size", + "Transaction with more than 32 payees" ] ] diff --git a/src/test/data/trivially_valid.json b/src/test/data/trivially_valid.json index c148a8f8de12..85715227122b 100644 --- a/src/test/data/trivially_valid.json +++ b/src/test/data/trivially_valid.json @@ -149,5 +149,17 @@ "proupservtx", "legacy", "0300020001fb8d43439173f49572100530bab58ae1bfc6f5a1d0f0f7d6f014a3e5649fabe2000000006a47304402206e87e2221e6a469edd6868b4a665528913ce2783c0405572d13a524241682b97022020710be7c84d1d518e8bbcc1433b6bfc583db144d595fe709553b4bab1859593012102a5e9d9f3b6cb02b64c5d39135c445f24466f469c8bc70bbec3bc44316384adc2feffffff0121850100000000001976a9141751bcc51a5d213e2198fcd269293437346d3e0188ac00000000b50100e0e45d58d6e540d174a63e772f1954d2a2b7b529e211fab355c67c4434b26bfe00000000000000000000ffff55d1f204270f00b306665ec86710f0799b71ecf6f6f60b7683dd7a771f4b94396fe79aaf44db8106c3ef32d21edd405c92e749536431bcc993918d301c01949a31c5df2237f07d9ce69aa49f1feda08da627f75c98bb450d83dca7f032835397b7cada992b59ec1b8f20e9935ef31184d8341c264cf7e9184e730a48c7183e28527ad0bca36756" +], +[ + "23723a58e92d356c2ca3a30602400ce6ed02a36a4afaaa94b19430efb6c18b41", + "proregtx", + "multi_payee", + "030001000300c4d497c6929e04bf6bec4415e38ef8c6a57f83a6a2e538aa348fcdc701d6500000000048473044022005ede1b7c1e2a01db7e6350186978babbed341205637c94ddda8541f401c37e002205524e207bd2ca0994f721c2c4dac44ab7f83c2cc70ed378abe290461e4c254fb01ffffffff01168d635fbfd7df3a380a30dda5a4cf2d160ad47cc9614ac9f4e827f8876e260000000048473044022024eb4a7a793d828661dd36ec1193051e594f6efcb6c25940a732fefdbfc4ec6f0220623aa65798843176544a7e939f4aa4e442e9682c8235e36304a93547475e605b01ffffffff012da8dc79c177e8deffc3e95308cda74baac13c5bcfbe4010af709dbe0c2bb1000000004847304402204c0e2b440fd38c11c65abe2f288d8ca438f41416abc8a99d1ac4756454feb99f02203d58b20bf03845f273588fa1054ad33daa20816188a73461362c8c2a1c64a19d01ffffffff0200e87648170000001976a914bcfa23253207c68d73018770cd4214dd2f53cf1588acb4beb3a7080000001976a914bcfa23253207c68d73018770cd4214dd2f53cf1588ac00000000f003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff0101010100014902ab25d01fbbd6aae1059b658b7ef3d758622e95f204316ea338d07cbcb0b20fb3cfac5bec95baca841a1f88f9888ebdd6a19225bb4603e4b579c0982a495168e001b14902ab25d01fbbd6aae1059b658b7ef3d758622e0000021976a914bcfa23253207c68d73018770cd4214dd2f53cf1588acb80b1976a914bcfa23253207c68d73018770cd4214dd2f53cf1588ac581bbd6317e6a65a8b7e6e691f26b3e2313259a541ba0a4421876debf12e42fbc82500" +], +[ + "0f60106817e039bed77c499c1dc0cb2fedcbf030b555a1b4e146b6dff9079d07", + "proupregtx", + "multi_payee", + "03000300010144ef217f355407c4714325d1dcbdd18f53fc5e1195d20f964345a4053f505100000000484730440220794728b4abcf6d947c775ba5de2bf785b3812a812a491fcef526e5cb1aae23ce0220355380b24dbb2c58bb12641902da9e75e453d70590ac0628b50c91361fc329b701ffffffff0200e1f505000000001976a914751e76e8199196d454941c45d1b3a323f1433bd688ac0093459e0b0000001976a914751e76e8199196d454941c45d1b3a323f1433bd688ac00000000fd1f0103002bdfef4f30f0b1a4a43f4946b169c44685eb39dc4680ef7f58703d6b1f6bc7460000b6d819c0fd9b734b1d120a0b208b5523e5f42e533411a822a432c2ebbc4fc36bcb6a24ed55c450e6c65aefcface93344faed8ff385a07162d2076bd69e54085c266e540e031976a9143831e001a01a67e983e461ec7a3b11470480a25088ace8031976a9142da958b4243076c2bd0cc84cc0a2e40fe256769c88ac88131976a9142da958b4243076c2bd0cc84cc0a2e40fe256769c88aca00f1192c19da39880b1f83379fffd0829cce9be8e9b39ee163927a59a60742a997e411f5869bca48c5024bf85f6975d9f6105b4407df368bc378024bdd8a81d5b52772473807dc8e2869e6006779bceec0d3f147b0243d3f2eac9185e03b28334bffe7a" ] ] diff --git a/src/test/evo_deterministicmns_tests.cpp b/src/test/evo_deterministicmns_tests.cpp index fb7adcaff8b7..085277965996 100644 --- a/src/test/evo_deterministicmns_tests.cpp +++ b/src/test/evo_deterministicmns_tests.cpp @@ -20,13 +20,13 @@ #include #include +#include #include #include #include using SimpleUTXOMap = std::map>; - static SimpleUTXOMap BuildSimpleUtxoMap(const std::vector& txs) { SimpleUTXOMap utxos; @@ -94,24 +94,25 @@ static void SignTransaction(const CTxMemPool& mempool, CMutableTransaction& tx, } } -static CMutableTransaction CreateProRegTx(const CTxMemPool& mempool, SimpleUTXOMap& utxos, int port, const CScript& scriptPayout, const CKey& coinbaseKey, CKey& ownerKeyRet, CBLSSecretKey& operatorKeyRet) +static CMutableTransaction CreateProRegTx(const CTxMemPool& mempool, SimpleUTXOMap& utxos, int port, const std::vector& payoutShares, const CKey& coinbaseKey, CKey& ownerKeyRet, CBLSSecretKey& operatorKeyRet, bool is_multi_payout_active = false) { ownerKeyRet.MakeNewKey(true); operatorKeyRet.MakeNewKey(); CProRegTx proTx; - proTx.nVersion = CProRegTx::GetVersion(!bls::bls_legacy_scheme); + proTx.nVersion = CProRegTx::GetVersion(!bls::bls_legacy_scheme, is_multi_payout_active); proTx.collateralOutpoint.n = 0; proTx.addr = LookupNumeric("1.1.1.1", port); proTx.keyIDOwner = ownerKeyRet.GetPubKey().GetID(); proTx.pubKeyOperator.Set(operatorKeyRet.GetPublicKey(), bls::bls_legacy_scheme.load()); proTx.keyIDVoting = ownerKeyRet.GetPubKey().GetID(); - proTx.scriptPayout = scriptPayout; + proTx.payoutShares = payoutShares; CMutableTransaction tx; tx.nVersion = 3; tx.nType = TRANSACTION_PROVIDER_REGISTER; - FundTransaction(tx, utxos, scriptPayout, dmn_types::Regular.collat_amount, coinbaseKey); + // Just fund the tx with the first script of the array + FundTransaction(tx, utxos, payoutShares[0].scriptPayout, dmn_types::Regular.collat_amount, coinbaseKey); proTx.inputsHash = CalcTxInputsHash(CTransaction(tx)); SetTxPayload(tx, proTx); SignTransaction(mempool, tx, coinbaseKey); @@ -139,14 +140,14 @@ static CMutableTransaction CreateProUpServTx(const CTxMemPool& mempool, SimpleUT return tx; } -static CMutableTransaction CreateProUpRegTx(const CTxMemPool& mempool, SimpleUTXOMap& utxos, const uint256& proTxHash, const CKey& mnKey, const CBLSPublicKey& pubKeyOperator, const CKeyID& keyIDVoting, const CScript& scriptPayout, const CKey& coinbaseKey) +static CMutableTransaction CreateProUpRegTx(const CTxMemPool& mempool, SimpleUTXOMap& utxos, const uint256& proTxHash, const CKey& mnKey, const CBLSPublicKey& pubKeyOperator, const CKeyID& keyIDVoting, const std::vector& payoutShares, const CKey& coinbaseKey, bool is_multi_payout_active = false) { CProUpRegTx proTx; - proTx.nVersion = CProUpRegTx::GetVersion(!bls::bls_legacy_scheme); + proTx.nVersion = CProUpRegTx::GetVersion(!bls::bls_legacy_scheme, is_multi_payout_active); proTx.proTxHash = proTxHash; proTx.pubKeyOperator.Set(pubKeyOperator, bls::bls_legacy_scheme.load()); proTx.keyIDVoting = keyIDVoting; - proTx.scriptPayout = scriptPayout; + proTx.payoutShares = payoutShares; CMutableTransaction tx; tx.nVersion = 3; @@ -186,7 +187,7 @@ static CMutableTransaction MalleateProTxPayout(const CMutableTransaction& tx) CKey key; key.MakeNewKey(false); - proTx.scriptPayout = GetScriptForDestination(PKHash(key.GetPubKey())); + proTx.payoutShares = {PayoutShare(GetScriptForDestination(PKHash(key.GetPubKey())))}; CMutableTransaction tx2 = tx; SetTxPayload(tx2, proTx); @@ -208,7 +209,8 @@ static CDeterministicMNCPtr FindPayoutDmn(const CBlock& block) for (const auto& txout : block.vtx[0]->vout) { CDeterministicMNCPtr found; dmnList.ForEachMNShared(true, [&](const CDeterministicMNCPtr& dmn) { - if (found == nullptr && txout.scriptPubKey == dmn->pdmnState->scriptPayout) { + // Checking only the first payee is enough for those tests + if (found == nullptr && txout.scriptPubKey == dmn->pdmnState->payoutShares[0].scriptPayout) { found = dmn; } }); @@ -241,7 +243,7 @@ void FuncDIP3Activation(TestChainSetup& setup) CKey ownerKey; CBLSSecretKey operatorKey; CTxDestination payoutDest = DecodeDestination("yRq1Ky1AfFmf597rnotj7QRxsDUKePVWNF"); - auto tx = CreateProRegTx(*(setup.m_node.mempool), utxos, 1, GetScriptForDestination(payoutDest), setup.coinbaseKey, ownerKey, operatorKey); + auto tx = CreateProRegTx(*(setup.m_node.mempool), utxos, 1, {PayoutShare(GetScriptForDestination(payoutDest))}, setup.coinbaseKey, ownerKey, operatorKey); std::vector txns = {tx}; int nHeight = ::ChainActive().Height(); @@ -276,7 +278,7 @@ void FuncV19Activation(TestChainSetup& setup) CKey collateral_key; collateral_key.MakeNewKey(false); auto collateralScript = GetScriptForDestination(PKHash(collateral_key.GetPubKey())); - auto tx_reg = CreateProRegTx(*(setup.m_node.mempool), utxos, 1, collateralScript, setup.coinbaseKey, owner_key, operator_key); + auto tx_reg = CreateProRegTx(*(setup.m_node.mempool), utxos, 1, {PayoutShare(collateralScript)}, setup.coinbaseKey, owner_key, operator_key); auto tx_reg_hash = tx_reg.GetHash(); int nHeight = ::ChainActive().Height(); @@ -297,7 +299,7 @@ void FuncV19Activation(TestChainSetup& setup) // update CBLSSecretKey operator_key_new; operator_key_new.MakeNewKey(); - auto tx_upreg = CreateProUpRegTx(*(setup.m_node.mempool), utxos, tx_reg_hash, owner_key, operator_key_new.GetPublicKey(), owner_key.GetPubKey().GetID(), collateralScript, setup.coinbaseKey); + auto tx_upreg = CreateProUpRegTx(*(setup.m_node.mempool), utxos, tx_reg_hash, owner_key, operator_key_new.GetPublicKey(), owner_key.GetPubKey().GetID(), {PayoutShare(collateralScript)}, setup.coinbaseKey); block = std::make_shared(setup.CreateBlock({tx_upreg}, setup.coinbaseKey)); BOOST_ASSERT(Assert(setup.m_node.chainman)->ProcessNewBlock(Params(), block, true, nullptr)); @@ -395,6 +397,121 @@ void FuncV19Activation(TestChainSetup& setup) BOOST_ASSERT(dummmy_list == tip_list); }; +void FuncDIP0026MultiPayout(TestChainSetup& setup) +{ + BOOST_ASSERT(DeploymentActiveAfter(::ChainActive().Tip(), Params().GetConsensus(), Consensus::DEPLOYMENT_V19)); + BOOST_ASSERT(!DeploymentActiveAfter(::ChainActive().Tip(), Params().GetConsensus(), Consensus::DEPLOYMENT_DIP0026)); + { + LOCK(cs_main); + setup.m_node.mnhf_manager->AddSignal(::ChainActive().Tip(), 11); + } + auto utxos = BuildSimpleUtxoMap(setup.m_coinbase_txns); + int nHeight = ::ChainActive().Height(); + + CKey owner_key; + CBLSSecretKey operator_key; + CKey collateral_key; + CKey payee_key; + CKey payee_key2; + uint16_t collateral_amount = 3000; + uint16_t payee_amount = 7000; + uint16_t payee2_amount = 0; + collateral_key.MakeNewKey(false); + payee_key.MakeNewKey(false); + payee_key2.MakeNewKey(false); + auto collateralScript = GetScriptForDestination(PKHash(collateral_key.GetPubKey())); + auto payeeScript = GetScriptForDestination(PKHash(collateral_key.GetPubKey())); + auto payee2Script = GetScriptForDestination(PKHash(payee_key2.GetPubKey())); + TxValidationState tx_state; + CMutableTransaction proreg_tx; + + // Test 1): A ProRegTx with multiple payees is not valid before activation of DIP0026 + { + LOCK(cs_main); + auto tx = CreateProRegTx(*(setup.m_node.mempool), utxos, 1, {PayoutShare(collateralScript, collateral_amount), PayoutShare(payeeScript, payee_amount)}, setup.coinbaseKey, owner_key, operator_key, true); + BOOST_ASSERT(!CheckProRegTx(CTransaction(tx), ::ChainActive().Tip(), tx_state, ::ChainstateActive().CoinsTip(), true)); + BOOST_CHECK_EQUAL(tx_state.GetRejectReason(), "bad-protx-version"); + } + // Mine some blocks in such a way that DIP0026 will be activated + for (size_t i = 0; i < 50; i++) { + setup.CreateAndProcessBlock({}, setup.coinbaseKey); + deterministicMNManager->UpdatedBlockTip(::ChainActive().Tip()); + nHeight++; + } + BOOST_CHECK_EQUAL(::ChainActive().Height(), nHeight); + BOOST_ASSERT(DeploymentActiveAfter(::ChainActive().Tip(), Params().GetConsensus(), Consensus::DEPLOYMENT_DIP0026)); + // Test 2): Create a valid masternode with two payees + { + proreg_tx = CreateProRegTx(*(setup.m_node.mempool), utxos, 1, {PayoutShare(collateralScript, collateral_amount), PayoutShare(payeeScript, payee_amount)}, setup.coinbaseKey, owner_key, operator_key, true); + setup.CreateAndProcessBlock({proreg_tx}, setup.coinbaseKey); + deterministicMNManager->UpdatedBlockTip(::ChainActive().Tip()); + BOOST_CHECK_EQUAL(::ChainActive().Height(), nHeight + 1); + BOOST_ASSERT(deterministicMNManager->GetListAtChainTip().HasMN(proreg_tx.GetHash())); + nHeight++; + } + // Test 3) Verify that both payees are paid as expected + { + auto dmnExpectedPayee = deterministicMNManager->GetListAtChainTip().GetMNPayee(::ChainActive().Tip()); + CBlock block = setup.CreateAndProcessBlock({}, setup.coinbaseKey); + deterministicMNManager->UpdatedBlockTip(::ChainActive().Tip()); + BOOST_ASSERT(!block.vtx.empty()); + BOOST_ASSERT(block.vtx[0]->vout.size() == 3); + + auto dmnPayout = FindPayoutDmn(block); + BOOST_ASSERT(dmnPayout != nullptr); + BOOST_CHECK_EQUAL(dmnPayout->proTxHash.ToString(), dmnExpectedPayee->proTxHash.ToString()); + + // Correct payees with the correct paid ratio + auto& coinbaseVouts = block.vtx[0]->vout; + BOOST_ASSERT(coinbaseVouts[1].scriptPubKey == collateralScript); + BOOST_ASSERT(coinbaseVouts[2].scriptPubKey == payeeScript); + CAmount totalPayed = (coinbaseVouts[1].nValue + coinbaseVouts[2].nValue); + BOOST_ASSERT(abs(totalPayed * collateral_amount - coinbaseVouts[1].nValue * 10000) < 10000 * 2); + BOOST_ASSERT(abs(totalPayed * payee_amount - coinbaseVouts[2].nValue * 10000) < 10000 * 2); + nHeight++; + } + CKey votingKey; + votingKey.MakeNewKey(false); + payee2_amount = 1000; + payee_amount = 4000; + collateral_amount = 5000; + // Test 4) Finally create a valid ProUpRegTx with 3 payees + { + auto newPayoutShares = {PayoutShare(payee2Script, payee2_amount), + PayoutShare(collateralScript, collateral_amount), + PayoutShare(payeeScript, payee_amount)}; + auto tx = CreateProUpRegTx(*(setup.m_node.mempool), utxos, proreg_tx.GetHash(), owner_key, operator_key.GetPublicKey(), votingKey.GetPubKey().GetID(), newPayoutShares, setup.coinbaseKey, true); + setup.CreateAndProcessBlock({tx}, setup.coinbaseKey); + deterministicMNManager->UpdatedBlockTip(::ChainActive().Tip()); + BOOST_CHECK_EQUAL(::ChainActive().Height(), nHeight + 1); + BOOST_ASSERT(deterministicMNManager->GetListAtChainTip().HasMN(proreg_tx.GetHash())); + nHeight++; + } + // Test 5) Check Again correct MN payments + { + auto dmnExpectedPayee = deterministicMNManager->GetListAtChainTip().GetMNPayee(::ChainActive().Tip()); + CBlock block = setup.CreateAndProcessBlock({}, setup.coinbaseKey); + deterministicMNManager->UpdatedBlockTip(::ChainActive().Tip()); + BOOST_ASSERT(!block.vtx.empty()); + auto& coinbaseVouts = block.vtx[0]->vout; + + BOOST_ASSERT(coinbaseVouts.size() == 4); + + auto dmnPayout = FindPayoutDmn(block); + BOOST_ASSERT(dmnPayout != nullptr); + BOOST_CHECK_EQUAL(dmnPayout->proTxHash.ToString(), dmnExpectedPayee->proTxHash.ToString()); + + // Correct payees with the correct paid ratio + BOOST_ASSERT(coinbaseVouts[1].scriptPubKey == payee2Script); + BOOST_ASSERT(coinbaseVouts[2].scriptPubKey == collateralScript); + BOOST_ASSERT(coinbaseVouts[3].scriptPubKey == payeeScript); + CAmount totalPayed = (coinbaseVouts[1].nValue + coinbaseVouts[2].nValue + coinbaseVouts[3].nValue); + BOOST_ASSERT(abs(totalPayed * payee2_amount - coinbaseVouts[1].nValue * 10000) < 10000 * 3); + BOOST_ASSERT(abs(totalPayed * collateral_amount - coinbaseVouts[2].nValue * 10000) < 10000 * 3); + BOOST_ASSERT(abs(totalPayed * payee_amount - coinbaseVouts[3].nValue * 10000) < 10000 * 3); + BOOST_CHECK_EQUAL(::ChainActive().Height(), nHeight + 1); + } +} void FuncDIP3Protx(TestChainSetup& setup) { CKey sporkKey; @@ -415,7 +532,7 @@ void FuncDIP3Protx(TestChainSetup& setup) for (size_t i = 0; i < 6; i++) { CKey ownerKey; CBLSSecretKey operatorKey; - auto tx = CreateProRegTx(*(setup.m_node.mempool), utxos, port++, GenerateRandomAddress(), setup.coinbaseKey, ownerKey, operatorKey); + auto tx = CreateProRegTx(*(setup.m_node.mempool), utxos, port++, {PayoutShare(GenerateRandomAddress())}, setup.coinbaseKey, ownerKey, operatorKey); dmnHashes.emplace_back(tx.GetHash()); ownerKeys.emplace(tx.GetHash(), ownerKey); operatorKeys.emplace(tx.GetHash(), operatorKey); @@ -472,7 +589,7 @@ void FuncDIP3Protx(TestChainSetup& setup) for (size_t j = 0; j < 3; j++) { CKey ownerKey; CBLSSecretKey operatorKey; - auto tx = CreateProRegTx(*(setup.m_node.mempool), utxos, port++, GenerateRandomAddress(), setup.coinbaseKey, ownerKey, operatorKey); + auto tx = CreateProRegTx(*(setup.m_node.mempool), utxos, port++, {PayoutShare(GenerateRandomAddress())}, setup.coinbaseKey, ownerKey, operatorKey); dmnHashes.emplace_back(tx.GetHash()); ownerKeys.emplace(tx.GetHash(), ownerKey); operatorKeys.emplace(tx.GetHash(), operatorKey); @@ -529,7 +646,7 @@ void FuncDIP3Protx(TestChainSetup& setup) CBLSSecretKey newOperatorKey; newOperatorKey.MakeNewKey(); dmn = deterministicMNManager->GetListAtChainTip().GetMN(dmnHashes[0]); - tx = CreateProUpRegTx(*(setup.m_node.mempool), utxos, dmnHashes[0], ownerKeys[dmnHashes[0]], newOperatorKey.GetPublicKey(), ownerKeys[dmnHashes[0]].GetPubKey().GetID(), dmn->pdmnState->scriptPayout, setup.coinbaseKey); + tx = CreateProUpRegTx(*(setup.m_node.mempool), utxos, dmnHashes[0], ownerKeys[dmnHashes[0]], newOperatorKey.GetPublicKey(), ownerKeys[dmnHashes[0]].GetPubKey().GetID(), dmn->pdmnState->payoutShares, setup.coinbaseKey); // check malleability protection again, but this time by also relying on the signature inside the ProUpRegTx auto tx2 = MalleateProTxPayout(tx); TxValidationState dummy_state; @@ -609,13 +726,12 @@ void FuncTestMempoolReorg(TestChainSetup& setup) BOOST_CHECK_EQUAL(block->GetHash(), ::ChainActive().Tip()->GetBlockHash()); CProRegTx payload; - payload.nVersion = CProRegTx::GetVersion(!bls::bls_legacy_scheme); + payload.nVersion = CProRegTx::GetVersion(!bls::bls_legacy_scheme, false); payload.addr = LookupNumeric("1.1.1.1", 1); payload.keyIDOwner = ownerKey.GetPubKey().GetID(); payload.pubKeyOperator.Set(operatorKey.GetPublicKey(), bls::bls_legacy_scheme.load()); payload.keyIDVoting = ownerKey.GetPubKey().GetID(); - payload.scriptPayout = scriptPayout; - + payload.payoutShares = {PayoutShare(scriptPayout)}; for (size_t i = 0; i < tx_collateral.vout.size(); ++i) { if (tx_collateral.vout[i].nValue == dmn_types::Regular.collat_amount) { payload.collateralOutpoint = COutPoint(tx_collateral.GetHash(), i); @@ -663,7 +779,7 @@ void FuncTestMempoolDualProregtx(TestChainSetup& setup) // Create a MN CKey ownerKey1; CBLSSecretKey operatorKey1; - auto tx_reg1 = CreateProRegTx(*(setup.m_node.mempool), utxos, 1, GenerateRandomAddress(), setup.coinbaseKey, ownerKey1, operatorKey1); + auto tx_reg1 = CreateProRegTx(*(setup.m_node.mempool), utxos, 1, {PayoutShare(GenerateRandomAddress())}, setup.coinbaseKey, ownerKey1, operatorKey1); // Create a MN with an external collateral that references tx_reg1 CKey ownerKey; @@ -683,7 +799,7 @@ void FuncTestMempoolDualProregtx(TestChainSetup& setup) payload.keyIDOwner = ownerKey.GetPubKey().GetID(); payload.pubKeyOperator.Set(operatorKey.GetPublicKey(), bls::bls_legacy_scheme.load()); payload.keyIDVoting = ownerKey.GetPubKey().GetID(); - payload.scriptPayout = scriptPayout; + payload.payoutShares = {PayoutShare(scriptPayout)}; for (size_t i = 0; i < tx_reg1.vout.size(); ++i) { if (tx_reg1.vout[i].nValue == dmn_types::Regular.collat_amount) { @@ -740,12 +856,12 @@ void FuncVerifyDB(TestChainSetup& setup) BOOST_CHECK_EQUAL(block->GetHash(), ::ChainActive().Tip()->GetBlockHash()); CProRegTx payload; - payload.nVersion = CProRegTx::GetVersion(!bls::bls_legacy_scheme); + payload.nVersion = CProRegTx::GetVersion(!bls::bls_legacy_scheme, false); payload.addr = LookupNumeric("1.1.1.1", 1); payload.keyIDOwner = ownerKey.GetPubKey().GetID(); payload.pubKeyOperator.Set(operatorKey.GetPublicKey(), bls::bls_legacy_scheme.load()); payload.keyIDVoting = ownerKey.GetPubKey().GetID(); - payload.scriptPayout = scriptPayout; + payload.payoutShares = {PayoutShare(scriptPayout)}; for (size_t i = 0; i < tx_collateral.vout.size(); ++i) { if (tx_collateral.vout[i].nValue == dmn_types::Regular.collat_amount) { @@ -805,6 +921,12 @@ BOOST_AUTO_TEST_CASE(v19_activation_legacy) FuncV19Activation(setup); } +BOOST_AUTO_TEST_CASE(multi_payout_activation) +{ + TestChainV19Setup setup; + FuncDIP0026MultiPayout(setup); +} + BOOST_AUTO_TEST_CASE(dip3_protx_legacy) { TestChainDIP3Setup setup; diff --git a/src/test/evo_trivialvalidation.cpp b/src/test/evo_trivialvalidation.cpp index 1adaa99654df..b63ee04a2df1 100644 --- a/src/test/evo_trivialvalidation.cpp +++ b/src/test/evo_trivialvalidation.cpp @@ -17,8 +17,8 @@ extern UniValue read_json(const std::string& jsondata); BOOST_FIXTURE_TEST_SUITE(evo_trivialvalidation, BasicTestingSetup) -template -void TestTxHelper(const CMutableTransaction& tx, bool is_basic_bls, bool expected_failure, const std::string& expected_error) +template +void TestTxHelper(const CMutableTransaction& tx, bool is_basic_bls, bool is_multi_payout_active, bool expected_failure, const std::string& expected_error) { T payload; @@ -29,7 +29,7 @@ void TestTxHelper(const CMutableTransaction& tx, bool is_basic_bls, bool expecte if (payload_to_fail) return; TxValidationState dummy_state; - BOOST_CHECK_EQUAL(payload.IsTriviallyValid(is_basic_bls, dummy_state), !expected_failure); + BOOST_CHECK_EQUAL(payload.IsTriviallyValid(is_basic_bls, is_multi_payout_active, dummy_state), !expected_failure); if (expected_failure) { BOOST_CHECK_EQUAL(dummy_state.GetRejectReason(), expected_error); } @@ -50,8 +50,10 @@ void trivialvalidation_runner(const std::string& json) txHash = uint256S(test[0].get_str()); BOOST_TEST_MESSAGE("tx: " << test[0].get_str()); txType = test[1].get_str(); - BOOST_CHECK(test[2].get_str() == "basic" || test[2].get_str() == "legacy"); - bool is_basic_bls = test[2].get_str() == "basic"; + BOOST_CHECK(test[2].get_str() == "basic" || test[2].get_str() == "legacy" || test[2].get_str() == "multi_payee"); + // multi payee txs use basic bls only + bool is_basic_bls = test[2].get_str() == "basic" || test[2].get_str() == "multi_payee"; + bool is_multi_payout_active = test[2].get_str() == "multi_payee"; // Raw transaction CDataStream stream(ParseHex(test[3].get_str()), SER_NETWORK, PROTOCOL_VERSION); stream >> tx; @@ -68,25 +70,25 @@ void trivialvalidation_runner(const std::string& json) case TRANSACTION_PROVIDER_REGISTER: { BOOST_CHECK_EQUAL(txType, "proregtx"); - TestTxHelper(tx, is_basic_bls, expected, expected_err); + TestTxHelper(tx, is_basic_bls, is_multi_payout_active, expected, expected_err); break; } case TRANSACTION_PROVIDER_UPDATE_SERVICE: { BOOST_CHECK_EQUAL(txType, "proupservtx"); - TestTxHelper(tx, is_basic_bls, expected, expected_err); + TestTxHelper(tx, is_basic_bls, is_multi_payout_active, expected, expected_err); break; } case TRANSACTION_PROVIDER_UPDATE_REGISTRAR: { BOOST_CHECK_EQUAL(txType, "proupregtx"); - TestTxHelper(tx, is_basic_bls, expected, expected_err); + TestTxHelper(tx, is_basic_bls, is_multi_payout_active, expected, expected_err); break; } case TRANSACTION_PROVIDER_UPDATE_REVOKE: { BOOST_CHECK_EQUAL(txType, "prouprevtx"); - TestTxHelper(tx, is_basic_bls, expected, expected_err); + TestTxHelper(tx, is_basic_bls, is_multi_payout_active, expected, expected_err); break; } default: From de32c9d851ad4902e55226ddfb213351669083f5 Mon Sep 17 00:00:00 2001 From: ale Date: Thu, 4 Jan 2024 10:09:24 -0500 Subject: [PATCH 04/15] feat(rpc): Update RPC --- src/rpc/blockchain.cpp | 1 + src/rpc/evo.cpp | 96 ++++++++++++++++++++++++++++-------------- src/rpc/masternode.cpp | 48 ++++++++++++--------- 3 files changed, 92 insertions(+), 53 deletions(-) diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index 9a93b207b01f..1f5a9f3cc10d 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -1762,6 +1762,7 @@ UniValue getblockchaininfo(const JSONRPCRequest& request) SoftForkDescPushBack(tip, softforks, consensusParams, Consensus::DEPLOYMENT_V19); SoftForkDescPushBack(tip, ehfSignals, softforks, consensusParams, Consensus::DEPLOYMENT_V20); SoftForkDescPushBack(tip, ehfSignals, softforks, consensusParams, Consensus::DEPLOYMENT_MN_RR); + SoftForkDescPushBack(tip, ehfSignals, softforks, consensusParams, Consensus::DEPLOYMENT_DIP0026); SoftForkDescPushBack(tip, ehfSignals, softforks, consensusParams, Consensus::DEPLOYMENT_TESTDUMMY); obj.pushKV("softforks", softforks); diff --git a/src/rpc/evo.cpp b/src/rpc/evo.cpp index 87c59e5c0256..a743526c428d 100644 --- a/src/rpc/evo.cpp +++ b/src/rpc/evo.cpp @@ -546,6 +546,32 @@ static void protx_register_evo_help(const JSONRPCRequest& request) }.Check(request); } +static std::vector protx_multi_payee_handler(const UniValue& unparsedPayoutShares, bool use_legacy, const ChainstateManager& chainman) +{ + const auto& payoutShares = unparsedPayoutShares.get_array(); + const bool isDIP26Active { DeploymentActiveAfter(WITH_LOCK(cs_main, return chainman.ActiveChain().Tip();), Params().GetConsensus(), Consensus::DEPLOYMENT_DIP0026) }; + const bool is_multi_payout = payoutShares.size() > 1; + // Multi payout is allowed only for basic bls scheme and only when dip26 is active + if (is_multi_payout && !isDIP26Active) { + throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("DIP26 is not active yet")); + } + if (is_multi_payout && use_legacy) { + throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Multi payout requires basic bls scheme")); + } + CTxDestination payoutDest; + std::vector ret{payoutShares.size()}; + for (size_t i = 0; i < payoutShares.size(); i++) { + const auto& payoutScript = payoutShares[i].get_array()[0].get_str(); + const auto& payoutShareReward = payoutShares[i].get_array()[1].get_int(); + payoutDest = DecodeDestination(payoutScript); + if (!IsValidDestination(payoutDest)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("invalid payout address: %s", payoutScript)); + } + ret[i] = PayoutShare(GetScriptForDestination(payoutDest), payoutShareReward); + } + return ret; +} + static void protx_register_prepare_evo_help(const JSONRPCRequest& request) { RPCHelpMan{ @@ -611,7 +637,7 @@ static UniValue protx_register_common_wrapper(const JSONRPCRequest& request, EnsureWalletIsUnlocked(wallet.get()); } - const bool isV19active{DeploymentActiveAfter(WITH_LOCK(cs_main, return chainman.ActiveChain().Tip();), Params().GetConsensus(), Consensus::DEPLOYMENT_V19)}; + const bool isV19active { DeploymentActiveAfter(WITH_LOCK(cs_main, return chainman.ActiveChain().Tip();), Params().GetConsensus(), Consensus::DEPLOYMENT_V19) }; if (isEvoRequested && !isV19active) { throw JSONRPCError(RPC_INVALID_REQUEST, "EvoNodes aren't allowed yet"); } @@ -662,8 +688,6 @@ static UniValue protx_register_common_wrapper(const JSONRPCRequest& request, ptx.keyIDOwner = ParsePubKeyIDFromAddress(request.params[paramIdx + 1].get_str(), "owner address"); ptx.pubKeyOperator.Set(ParseBLSPubKey(request.params[paramIdx + 2].get_str(), "operator BLS address", use_legacy), use_legacy); - ptx.nVersion = use_legacy ? CProRegTx::LEGACY_BLS_VERSION : CProRegTx::BASIC_BLS_VERSION; - CHECK_NONFATAL(ptx.pubKeyOperator.IsLegacy() == (ptx.nVersion == CProRegTx::LEGACY_BLS_VERSION)); CKeyID keyIDVoting = ptx.keyIDOwner; @@ -680,10 +704,12 @@ static UniValue protx_register_common_wrapper(const JSONRPCRequest& request, } ptx.nOperatorReward = operatorReward; - CTxDestination payoutDest = DecodeDestination(request.params[paramIdx + 5].get_str()); - if (!IsValidDestination(payoutDest)) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("invalid payout address: %s", request.params[paramIdx + 5].get_str())); + ptx.payoutShares = protx_multi_payee_handler(request.params[paramIdx + 5], use_legacy, chainman); + if (ptx.payoutShares.empty()) { + throw JSONRPCError(RPC_INVALID_REQUEST, "missing payees"); } + ptx.nVersion = CProRegTx::GetVersion(!use_legacy, ptx.payoutShares.size() > 1); + CHECK_NONFATAL(ptx.pubKeyOperator.IsLegacy() == (ptx.nVersion == CProRegTx::LEGACY_BLS_VERSION)); if (isEvoRequested) { if (!IsHex(request.params[paramIdx + 6].get_str())) { @@ -707,14 +733,14 @@ static UniValue protx_register_common_wrapper(const JSONRPCRequest& request, } ptx.keyIDVoting = keyIDVoting; - ptx.scriptPayout = GetScriptForDestination(payoutDest); - if (!isFundRegister) { // make sure fee calculation works ptx.vchSig.resize(65); } - CTxDestination fundDest = payoutDest; + CTxDestination fundDest; + // TODO: decide who should fund the tx + ExtractDestination(ptx.payoutShares[0].scriptPayout, fundDest); if (!request.params[paramIdx + 6].isNull()) { fundDest = DecodeDestination(request.params[paramIdx + 6].get_str()); if (!IsValidDestination(fundDest)) @@ -997,8 +1023,9 @@ static UniValue protx_update_service_common_wrapper(const JSONRPCRequest& reques // use operator reward address as default source for fees ExtractDestination(ptx.scriptOperatorPayout, feeSource); } else { - // use payout address as default source for fees - ExtractDestination(dmn->pdmnState->scriptPayout, feeSource); + // use first payout address as default source for fees + // TODO: decide who should fund the tx + ExtractDestination(dmn->pdmnState->payoutShares[0].scriptPayout, feeSource); } } @@ -1054,7 +1081,7 @@ static UniValue protx_update_registrar_wrapper(const JSONRPCRequest& request, co throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("masternode %s not found", ptx.proTxHash.ToString())); } ptx.keyIDVoting = dmn->pdmnState->keyIDVoting; - ptx.scriptPayout = dmn->pdmnState->scriptPayout; + ptx.payoutShares = dmn->pdmnState->payoutShares; const bool isV19Active{DeploymentActiveAfter(WITH_LOCK(cs_main, return chainman.ActiveChain().Tip();), Params().GetConsensus(), Consensus::DEPLOYMENT_V19)}; const bool use_legacy = isV19Active ? specific_legacy_bls_scheme : true; @@ -1067,23 +1094,16 @@ static UniValue protx_update_registrar_wrapper(const JSONRPCRequest& request, co ptx.pubKeyOperator = dmn->pdmnState->pubKeyOperator; } - ptx.nVersion = use_legacy ? CProUpRegTx::LEGACY_BLS_VERSION : CProUpRegTx::BASIC_BLS_VERSION; - CHECK_NONFATAL(ptx.pubKeyOperator.IsLegacy() == (ptx.nVersion == CProUpRegTx::LEGACY_BLS_VERSION)); - if (request.params[2].get_str() != "") { ptx.keyIDVoting = ParsePubKeyIDFromAddress(request.params[2].get_str(), "voting address"); } - CTxDestination payoutDest; - ExtractDestination(ptx.scriptPayout, payoutDest); - if (request.params[3].get_str() != "") { - payoutDest = DecodeDestination(request.params[3].get_str()); - if (!IsValidDestination(payoutDest)) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("invalid payout address: %s", request.params[3].get_str())); - } - ptx.scriptPayout = GetScriptForDestination(payoutDest); + std::vector newPayoutShares = protx_multi_payee_handler(request.params[3], use_legacy, chainman); + if (!newPayoutShares.empty()) { + ptx.payoutShares = std::move(newPayoutShares); } - + ptx.nVersion = CProUpRegTx::GetVersion(!use_legacy, ptx.payoutShares.size() > 1); + CHECK_NONFATAL(ptx.pubKeyOperator.IsLegacy() == (ptx.nVersion == CProUpRegTx::LEGACY_BLS_VERSION)); LegacyScriptPubKeyMan* spk_man = wallet->GetLegacyScriptPubKeyMan(); if (!spk_man) { throw JSONRPCError(RPC_WALLET_ERROR, "This type of wallet does not support this command"); @@ -1098,9 +1118,11 @@ static UniValue protx_update_registrar_wrapper(const JSONRPCRequest& request, co tx.nVersion = 3; tx.nType = TRANSACTION_PROVIDER_UPDATE_REGISTRAR; - // make sure we get anough fees added + // make sure we get enough fees added ptx.vchSig.resize(65); - + CTxDestination payoutDest; + // TODO: decide who should fund the tx + ExtractDestination(ptx.payoutShares[0].scriptPayout, payoutDest); CTxDestination feeSourceDest = payoutDest; if (!request.params[4].isNull()) { feeSourceDest = DecodeDestination(request.params[4].get_str()); @@ -1192,14 +1214,16 @@ static UniValue protx_revoke(const JSONRPCRequest& request, const ChainstateMana throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, std::string("Invalid Dash address: ") + request.params[3].get_str()); FundSpecialTx(wallet.get(), tx, ptx, feeSourceDest); } else if (dmn->pdmnState->scriptOperatorPayout != CScript()) { - // Using funds from previousely specified operator payout address + // Using funds from previously specified operator payout address CTxDestination txDest; ExtractDestination(dmn->pdmnState->scriptOperatorPayout, txDest); FundSpecialTx(wallet.get(), tx, ptx, txDest); - } else if (dmn->pdmnState->scriptPayout != CScript()) { - // Using funds from previousely specified masternode payout address + } else if (dmn->pdmnState->payoutShares[0].scriptPayout != CScript()) { + // Using funds from previously specified masternode payout address CTxDestination txDest; - ExtractDestination(dmn->pdmnState->scriptPayout, txDest); + // Again, as fee source we can just use the first address in the payee list + // TODO: decide who should fund the tx + ExtractDestination(dmn->pdmnState->payoutShares[0].scriptPayout, txDest); FundSpecialTx(wallet.get(), tx, ptx, txDest); } else { throw JSONRPCError(RPC_INTERNAL_ERROR, "No payout or fee source addresses found, can't revoke"); @@ -1290,13 +1314,17 @@ static UniValue BuildDMNListEntry(CWallet* pwallet, const CDeterministicMN& dmn, ownsCollateral = CheckWalletOwnsScript(pwallet, collateralTx->vout[dmn.collateralOutpoint.n].scriptPubKey); } + const auto& scriptIterator = std::find_if(dmn.pdmnState->payoutShares.begin(), dmn.pdmnState->payoutShares.end(), [&](const auto& payoutShare){ + return CheckWalletOwnsScript(pwallet, payoutShare.scriptPayout); + }); + if (pwallet) { UniValue walletObj(UniValue::VOBJ); walletObj.pushKV("hasOwnerKey", hasOwnerKey); walletObj.pushKV("hasOperatorKey", false); walletObj.pushKV("hasVotingKey", hasVotingKey); walletObj.pushKV("ownsCollateral", ownsCollateral); - walletObj.pushKV("ownsPayeeScript", CheckWalletOwnsScript(pwallet, dmn.pdmnState->scriptPayout)); + walletObj.pushKV("ownsPayeeScript", scriptIterator != dmn.pdmnState->payoutShares.end()); walletObj.pushKV("ownsOperatorRewardScript", CheckWalletOwnsScript(pwallet, dmn.pdmnState->scriptOperatorPayout)); o.pushKV("wallet", walletObj); } @@ -1359,10 +1387,14 @@ static UniValue protx_list(const JSONRPCRequest& request, const ChainstateManage CDeterministicMNList mnList = deterministicMNManager->GetListForBlock(chainman.ActiveChain()[height]); mnList.ForEachMN(false, [&](const auto& dmn) { + const auto& scriptIterator = std::find_if(dmn.pdmnState->payoutShares.begin(), dmn.pdmnState->payoutShares.end(), [&](const auto& payoutShare){ + return CheckWalletOwnsScript(wallet.get(), payoutShare.scriptPayout); + }); + if (setOutpts.count(dmn.collateralOutpoint) || CheckWalletOwnsKey(wallet.get(), dmn.pdmnState->keyIDOwner) || CheckWalletOwnsKey(wallet.get(), dmn.pdmnState->keyIDVoting) || - CheckWalletOwnsScript(wallet.get(), dmn.pdmnState->scriptPayout) || + (scriptIterator != dmn.pdmnState->payoutShares.end()) || CheckWalletOwnsScript(wallet.get(), dmn.pdmnState->scriptOperatorPayout)) { ret.push_back(BuildDMNListEntry(wallet.get(), dmn, detailed)); } diff --git a/src/rpc/masternode.cpp b/src/rpc/masternode.cpp index e3403dc90a14..553a48a7af73 100644 --- a/src/rpc/masternode.cpp +++ b/src/rpc/masternode.cpp @@ -5,20 +5,21 @@ #include #include #include -#include -#include -#include #include +#include #include #include #include #include +#include +#include #include #include #include +#include #include +#include // for enumerate #include -#include #include #include #include @@ -141,10 +142,6 @@ static UniValue GetNextMasternodeForPayment(int heightShift) if (payees.empty()) return "unknown"; auto payee = payees.back(); - CScript payeeScript = payee->pdmnState->scriptPayout; - - CTxDestination payeeDest; - ExtractDestination(payeeScript, payeeDest); UniValue obj(UniValue::VOBJ); @@ -152,7 +149,12 @@ static UniValue GetNextMasternodeForPayment(int heightShift) obj.pushKV("IP:port", payee->pdmnState->addr.ToString()); obj.pushKV("proTxHash", payee->proTxHash.ToString()); obj.pushKV("outpoint", payee->collateralOutpoint.ToStringShort()); - obj.pushKV("payee", IsValidDestination(payeeDest) ? EncodeDestination(payeeDest) : "UNKNOWN"); + + UniValue payoutArray(UniValue::VARR); + for (const auto& payoutShare : payee->pdmnState->payoutShares) { + payoutArray.push_back(payoutShare.ToJson()); + } + obj.pushKV("payees", payoutArray); return obj; } @@ -277,15 +279,25 @@ static UniValue masternode_status(const JSONRPCRequest& request) return mnObj; } +static std::string GetStringFromMNPayoutShares(const std::vector& payoutShares) +{ + std::string strPayments = "UNKNOWN"; + CTxDestination dest; + for (const auto [i, payoutShare] : enumerate(payoutShares)) { + if (!ExtractDestination(payoutShare.scriptPayout, dest)) { + CHECK_NONFATAL(false); + } + strPayments = (i == 0) ? EncodeDestination(dest) : (strPayments + ", " + EncodeDestination(dest)); + } + return strPayments; +} static std::string GetRequiredPaymentsString(int nBlockHeight, const CDeterministicMNCPtr &payee) { - std::string strPayments = "Unknown"; + std::string strPayments = "UNKNOWN"; if (payee) { CTxDestination dest; - if (!ExtractDestination(payee->pdmnState->scriptPayout, dest)) { - CHECK_NONFATAL(false); - } - strPayments = EncodeDestination(dest); + strPayments = GetStringFromMNPayoutShares(payee->pdmnState->payoutShares); + if (payee->nOperatorReward != 0 && payee->pdmnState->scriptOperatorPayout != CScript()) { if (!ExtractDestination(payee->pdmnState->scriptOperatorPayout, dest)) { CHECK_NONFATAL(false); @@ -634,13 +646,7 @@ static UniValue masternodelist(const JSONRPCRequest& request, ChainstateManager& } } - CScript payeeScript = dmn.pdmnState->scriptPayout; - CTxDestination payeeDest; - std::string payeeStr = "UNKNOWN"; - if (ExtractDestination(payeeScript, payeeDest)) { - payeeStr = EncodeDestination(payeeDest); - } - + std::string payeeStr = GetStringFromMNPayoutShares(dmn.pdmnState->payoutShares); if (strMode == "addr") { std::string strAddress = dmn.pdmnState->addr.ToString(false); if (strFilter !="" && strAddress.find(strFilter) == std::string::npos && From c6cb351bc3120c1b8c9fb43a69c153cbf709f2b6 Mon Sep 17 00:00:00 2001 From: ale Date: Thu, 4 Jan 2024 10:10:42 -0500 Subject: [PATCH 05/15] test: Update existing functional test --- test/functional/feature_dip3_deterministicmns.py | 14 ++++++++------ test/functional/feature_llmq_evo.py | 2 +- test/functional/rpc_blockchain.py | 10 ++++++++++ test/functional/rpc_masternode.py | 8 +++++--- test/functional/test_framework/test_framework.py | 8 ++++---- 5 files changed, 28 insertions(+), 14 deletions(-) diff --git a/test/functional/feature_dip3_deterministicmns.py b/test/functional/feature_dip3_deterministicmns.py index b4d0b90156d3..19a8f50e914d 100755 --- a/test/functional/feature_dip3_deterministicmns.py +++ b/test/functional/feature_dip3_deterministicmns.py @@ -204,14 +204,15 @@ def run_test(self): assert old_voting_address != new_voting_address # also check if funds from payout address are used when no fee source address is specified node.sendtoaddress(mn.rewards_address, 0.001) - node.protx('update_registrar', mn.protx_hash, "", new_voting_address, "") + node.protx('update_registrar', mn.protx_hash, "", new_voting_address, []) node.generate(1) self.sync_all() new_dmnState = mn.node.masternode("status")["dmnState"] new_voting_address_from_rpc = new_dmnState["votingAddress"] assert new_voting_address_from_rpc == new_voting_address # make sure payoutAddress is the same as before - assert old_dmnState["payoutAddress"] == new_dmnState["payoutAddress"] + assert old_dmnState['payouts'][0]['payoutAddress'] == new_dmnState['payouts'][0]['payoutAddress'] + assert old_dmnState['payouts'][0]['payoutShareReward'] == new_dmnState['payouts'][0]['payoutShareReward'] def prepare_mn(self, node, idx, alias): mn = Masternode() @@ -248,7 +249,7 @@ def register_fund_mn(self, node, mn): mn.collateral_address = node.getnewaddress() mn.rewards_address = node.getnewaddress() - mn.protx_hash = node.protx('register_fund', mn.collateral_address, '127.0.0.1:%d' % mn.p2p_port, mn.ownerAddr, mn.operatorAddr, mn.votingAddr, mn.operator_reward, mn.rewards_address, mn.fundsAddr) + mn.protx_hash = node.protx('register_fund', mn.collateral_address, '127.0.0.1:%d' % mn.p2p_port, mn.ownerAddr, mn.operatorAddr, mn.votingAddr, mn.operator_reward, [[mn.rewards_address, 10000]], mn.fundsAddr) mn.collateral_txid = mn.protx_hash mn.collateral_vout = None @@ -264,7 +265,7 @@ def register_mn(self, node, mn): node.sendtoaddress(mn.fundsAddr, 0.001) mn.rewards_address = node.getnewaddress() - mn.protx_hash = node.protx('register', mn.collateral_txid, mn.collateral_vout, '127.0.0.1:%d' % mn.p2p_port, mn.ownerAddr, mn.operatorAddr, mn.votingAddr, mn.operator_reward, mn.rewards_address, mn.fundsAddr) + mn.protx_hash = node.protx('register', mn.collateral_txid, mn.collateral_vout, '127.0.0.1:%d' % mn.p2p_port, mn.ownerAddr, mn.operatorAddr, mn.votingAddr, mn.operator_reward, [[mn.rewards_address, 10000]], mn.fundsAddr) node.generate(1) def start_mn(self, mn): @@ -282,11 +283,12 @@ def spend_mn_collateral(self, mn, with_dummy_input_output=False): def update_mn_payee(self, mn, payee): self.nodes[0].sendtoaddress(mn.fundsAddr, 0.001) - self.nodes[0].protx('update_registrar', mn.protx_hash, '', '', payee, mn.fundsAddr) + self.nodes[0].protx('update_registrar', mn.protx_hash, '', '', [[payee, 10000]], mn.fundsAddr) self.nodes[0].generate(1) self.sync_all() info = self.nodes[0].protx('info', mn.protx_hash) - assert info['state']['payoutAddress'] == payee + assert info['state']['payouts'][0]['payoutAddress'] == payee + assert_equal(info['state']['payouts'][0]['payoutShareReward'], 10000) def test_protx_update_service(self, mn): self.nodes[0].sendtoaddress(mn.fundsAddr, 0.001) diff --git a/test/functional/feature_llmq_evo.py b/test/functional/feature_llmq_evo.py index ba0d53ad0fd4..6859041de2ec 100755 --- a/test/functional/feature_llmq_evo.py +++ b/test/functional/feature_llmq_evo.py @@ -235,7 +235,7 @@ def test_evo_is_rejected_before_v19(self): operatorReward = len(self.nodes) try: - self.nodes[0].protx('register_evo', collateral_txid, collateral_vout, ipAndPort, owner_address, bls['public'], voting_address, operatorReward, reward_address, funds_address, True) + self.nodes[0].protx('register_evo', collateral_txid, collateral_vout, ipAndPort, owner_address, bls['public'], voting_address, operatorReward, [[reward_address, 10000]], funds_address, True) # this should never succeed assert False except: diff --git a/test/functional/rpc_blockchain.py b/test/functional/rpc_blockchain.py index 19ac7544e4f8..bf7a785bfc52 100755 --- a/test/functional/rpc_blockchain.py +++ b/test/functional/rpc_blockchain.py @@ -162,6 +162,16 @@ def _test_getblockchaininfo(self): 'ehf': True, }, 'active': False}, + 'multi_mn_payee': { + 'type': 'bip9', + 'bip9': { + 'status': 'defined', + 'start_time': 0, + 'timeout': 9223372036854775807, + 'since': 0, + 'ehf': True, + }, + 'active': False}, 'testdummy': { 'type': 'bip9', 'bip9': { diff --git a/test/functional/rpc_masternode.py b/test/functional/rpc_masternode.py index 55fe3562711a..755078685845 100755 --- a/test/functional/rpc_masternode.py +++ b/test/functional/rpc_masternode.py @@ -74,13 +74,15 @@ def run_test(self): payments_masternode = self.nodes[0].masternode("payments")[0]["masternodes"][0] protx_info = self.nodes[0].protx("info", payments_masternode["proTxHash"]) if len(payments_masternode["payees"]) == 1: - assert_equal(protx_info["state"]["payoutAddress"], payments_masternode["payees"][0]["address"]) + assert_equal(protx_info["state"]["payouts"][0]["payoutAddress"], payments_masternode["payees"][0]["address"]) + assert_equal(protx_info["state"]["payouts"][0]["payoutShareReward"], 10000) checked_0_operator_reward = True else: assert_equal(len(payments_masternode["payees"]), 2) - option1 = protx_info["state"]["payoutAddress"] == payments_masternode["payees"][0]["address"] and \ + assert_equal(protx_info["state"]["payouts"][0]["payoutShareReward"], 10000) + option1 = protx_info["state"]["payouts"][0]["payoutAddress"] == payments_masternode["payees"][0]["address"] and \ protx_info["state"]["operatorPayoutAddress"] == payments_masternode["payees"][1]["address"] - option2 = protx_info["state"]["payoutAddress"] == payments_masternode["payees"][1]["address"] and \ + option2 = protx_info["state"]["payouts"][0]["payoutAddress"] == payments_masternode["payees"][1]["address"] and \ protx_info["state"]["operatorPayoutAddress"] == payments_masternode["payees"][0]["address"] assert option1 or option2 checked_non_0_operator_reward = True diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py index a0b26108d8ca..737248e40480 100755 --- a/test/functional/test_framework/test_framework.py +++ b/test/functional/test_framework/test_framework.py @@ -1225,9 +1225,9 @@ def dynamically_prepare_masternode(self, idx, node_p2p_port, evo=False, rnd=None protx_result = None if evo: - protx_result = self.nodes[0].protx("register_evo", collateral_txid, collateral_vout, ipAndPort, owner_address, bls['public'], voting_address, operatorReward, reward_address, platform_node_id, platform_p2p_port, platform_http_port, funds_address, True) + protx_result = self.nodes[0].protx("register_evo", collateral_txid, collateral_vout, ipAndPort, owner_address, bls['public'], voting_address, operatorReward, [[reward_address, 10000]], platform_node_id, platform_p2p_port, platform_http_port, funds_address, True) else: - protx_result = self.nodes[0].protx("register", collateral_txid, collateral_vout, ipAndPort, owner_address, bls['public'], voting_address, operatorReward, reward_address, funds_address, True) + protx_result = self.nodes[0].protx("register", collateral_txid, collateral_vout, ipAndPort, owner_address, bls['public'], voting_address, operatorReward, [[reward_address, 10000]], funds_address, True) self.wait_for_instantlock(protx_result, self.nodes[0]) tip = self.nodes[0].generate(1)[0] @@ -1310,10 +1310,10 @@ def prepare_masternode(self, idx): submit = (idx % 4) < 2 if register_fund: - protx_result = self.nodes[0].protx('register_fund', address, ipAndPort, ownerAddr, bls['public'], votingAddr, operatorReward, rewardsAddr, address, submit) + protx_result = self.nodes[0].protx('register_fund', address, ipAndPort, ownerAddr, bls['public'], votingAddr, operatorReward, [[rewardsAddr, 10000]], address, submit) else: self.nodes[0].generate(1) - protx_result = self.nodes[0].protx('register', txid, collateral_vout, ipAndPort, ownerAddr, bls['public'], votingAddr, operatorReward, rewardsAddr, address, submit) + protx_result = self.nodes[0].protx('register', txid, collateral_vout, ipAndPort, ownerAddr, bls['public'], votingAddr, operatorReward, [[rewardsAddr, 10000]], address, submit) if submit: proTxHash = protx_result From e24cb239f830108b8cc51f5626448789cd12ff56 Mon Sep 17 00:00:00 2001 From: ale Date: Sat, 6 Jan 2024 17:42:48 -0500 Subject: [PATCH 06/15] feat(consensus): Generalize regtest ehf In this way any deployment that is activated with DIP0023 will be deployed on regtest after activating spork 24 --- src/llmq/ehf_signals.cpp | 67 ++++++++++++++++++++++++---------------- 1 file changed, 41 insertions(+), 26 deletions(-) diff --git a/src/llmq/ehf_signals.cpp b/src/llmq/ehf_signals.cpp index 697b6a9d0753..f4824af9bc14 100644 --- a/src/llmq/ehf_signals.cpp +++ b/src/llmq/ehf_signals.cpp @@ -17,6 +17,7 @@ #include #include #include +#include // for enumerate #include namespace llmq { @@ -54,8 +55,15 @@ void CEHFSignalsHandler::UpdatedBlockTip(const CBlockIndex* const pindexNew) // TODO: v20 will never attempt to create EHF messages on main net; if this is needed it will be done by v20.1 or v21 nodes return; } - // TODO: should do this for all not-yet-signied bits - trySignEHFSignal(Params().GetConsensus().vDeployments[Consensus::DEPLOYMENT_MN_RR].bit, pindexNew); + + for (const auto [index, deployment] : enumerate(Params().GetConsensus().vDeployments)) { + // try only with not yet active deployments based on dip0023 + // deployments with an existing RecoveredSignature will be discarded internally + if (deployment.useEHF && !DeploymentActiveAfter(pindexNew, Params().GetConsensus(), + static_cast(index))) { + trySignEHFSignal(deployment.bit, pindexNew); + } + } } void CEHFSignalsHandler::trySignEHFSignal(int bit, const CBlockIndex* const pindex) @@ -111,31 +119,38 @@ void CEHFSignalsHandler::HandleNewRecoveredSig(const CRecoveredSig& recoveredSig } MNHFTxPayload mnhfPayload; - // TODO: should do this for all not-yet-signied bits - mnhfPayload.signal.versionBit = Params().GetConsensus().vDeployments[Consensus::DEPLOYMENT_MN_RR].bit; - - const uint256 expectedId = mnhfPayload.GetRequestId(); - LogPrint(BCLog::EHF, "CEHFSignalsHandler::HandleNewRecoveredSig expecting ID=%s received=%s\n", expectedId.ToString(), recoveredSig.getId().ToString()); - if (recoveredSig.getId() != mnhfPayload.GetRequestId()) { - // there's nothing interesting for CEHFSignalsHandler - LogPrint(BCLog::EHF, "CEHFSignalsHandler::HandleNewRecoveredSig id is known but it's not MN_RR, expected: %s\n", mnhfPayload.GetRequestId().ToString()); - return; - } - - mnhfPayload.signal.quorumHash = recoveredSig.getQuorumHash(); - mnhfPayload.signal.sig = recoveredSig.sig.Get(); - - CMutableTransaction tx = mnhfPayload.PrepareTx(); + // TODO: make sure to don't process the same recoveredSig twice + for (const auto& deployment : Params().GetConsensus().vDeployments) { + // skip deployments that do not use dip0023 + if (!deployment.useEHF) continue; + mnhfPayload.signal.versionBit = deployment.bit; + + const uint256 expectedId = mnhfPayload.GetRequestId(); + LogPrint(BCLog::EHF, "CEHFSignalsHandler::HandleNewRecoveredSig expecting ID=%s received=%s\n", + expectedId.ToString(), recoveredSig.getId().ToString()); + if (recoveredSig.getId() != mnhfPayload.GetRequestId()) { + // wrong deployment! Check the next one + continue; + } - { - CTransactionRef tx_to_sent = MakeTransactionRef(std::move(tx)); - LogPrintf("CEHFSignalsHandler::HandleNewRecoveredSig Special EHF TX is created hash=%s\n", tx_to_sent->GetHash().ToString()); - LOCK(cs_main); - TxValidationState state; - if (AcceptToMemoryPool(chainstate, mempool, state, tx_to_sent, /* bypass_limits=*/ false, /* nAbsurdFee=*/ 0)) { - connman.RelayTransaction(*tx_to_sent); - } else { - LogPrintf("CEHFSignalsHandler::HandleNewRecoveredSig -- AcceptToMemoryPool failed: %s\n", state.ToString()); + mnhfPayload.signal.quorumHash = recoveredSig.getQuorumHash(); + mnhfPayload.signal.sig = recoveredSig.sig.Get(); + + CMutableTransaction tx = mnhfPayload.PrepareTx(); + + { + CTransactionRef tx_to_sent = MakeTransactionRef(std::move(tx)); + LogPrintf("CEHFSignalsHandler::HandleNewRecoveredSig Special EHF TX is created hash=%s\n", + tx_to_sent->GetHash().ToString()); + LOCK(cs_main); + TxValidationState state; + if (AcceptToMemoryPool(chainstate, mempool, state, tx_to_sent, /* bypass_limits=*/false, /* nAbsurdFee=*/ + 0)) { + connman.RelayTransaction(*tx_to_sent); + } else { + LogPrintf("CEHFSignalsHandler::HandleNewRecoveredSig -- AcceptToMemoryPool failed: %s\n", + state.ToString()); + } } } } From cce757c13f84750a6b773bce59a132f96f51ef01 Mon Sep 17 00:00:00 2001 From: ale Date: Sat, 6 Jan 2024 17:43:59 -0500 Subject: [PATCH 07/15] test: add activate_dip_0026 And improve dynamically_prepare_masternode in such a way to support multiple payees --- .../test_framework/test_framework.py | 38 +++++++++++++------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py index 737248e40480..703fa945dd17 100755 --- a/test/functional/test_framework/test_framework.py +++ b/test/functional/test_framework/test_framework.py @@ -1124,19 +1124,26 @@ def activate_v19(self, expected_activation_height=None): def activate_v20(self, expected_activation_height=None): self.activate_by_name('v20', expected_activation_height) - def activate_mn_rr(self, expected_activation_height=None): + def activate_ehf_by_name(self, name, expected_activation_height=None): self.nodes[0].sporkupdate("SPORK_24_TEST_EHF", 0) self.wait_for_sporks_same() - mn_rr_height = 0 - while mn_rr_height == 0: + assert get_bip9_details(self.nodes[0], name)['ehf'] + ehf_height = 0 + while ehf_height == 0: time.sleep(1) try: - mn_rr_height = get_bip9_details(self.nodes[0], 'mn_rr')['ehf_height'] + ehf_height = get_bip9_details(self.nodes[0], name)['ehf_height'] except KeyError: pass self.nodes[0].generate(1) self.sync_all() - self.activate_by_name('mn_rr', expected_activation_height) + self.activate_by_name(name, expected_activation_height) + + def activate_mn_rr(self, expected_activation_height=None): + self.activate_ehf_by_name('mn_rr', expected_activation_height) + + def activate_dip0026(self, expected_activation_height=None): + self.activate_ehf_by_name('multi_mn_payee', expected_activation_height) def set_dash_llmq_test_params(self, llmq_size, llmq_threshold): self.llmq_size = llmq_size @@ -1153,7 +1160,7 @@ def create_simple_node(self): self.connect_nodes(i, idx) # TODO: to let creating Evo Nodes without instant-send available - def dynamically_add_masternode(self, evo=False, rnd=None, should_be_rejected=False): + def dynamically_add_masternode(self, evo=False, rnd=None, should_be_rejected=False, n_payees=1): mn_idx = len(self.nodes) node_p2p_port = p2p_port(mn_idx) @@ -1161,7 +1168,7 @@ def dynamically_add_masternode(self, evo=False, rnd=None, should_be_rejected=Fal protx_success = False try: - created_mn_info = self.dynamically_prepare_masternode(mn_idx, node_p2p_port, evo, rnd) + created_mn_info = self.dynamically_prepare_masternode(mn_idx, node_p2p_port, evo, rnd, n_payees) protx_success = True except: self.log.info("dynamically_prepare_masternode failed") @@ -1192,13 +1199,20 @@ def dynamically_add_masternode(self, evo=False, rnd=None, should_be_rejected=Fal self.log.info("Successfully started and synced proTx:"+str(created_mn_info.proTxHash)) return created_mn_info - def dynamically_prepare_masternode(self, idx, node_p2p_port, evo=False, rnd=None): + def dynamically_prepare_masternode(self, idx, node_p2p_port, evo=False, rnd=None, n_payees=1): bls = self.nodes[0].bls('generate') collateral_address = self.nodes[0].getnewaddress() funds_address = self.nodes[0].getnewaddress() owner_address = self.nodes[0].getnewaddress() voting_address = self.nodes[0].getnewaddress() - reward_address = self.nodes[0].getnewaddress() + payee_shares = [] + tot_generated_shares = 0 + # distribute reward shares randomly + for i in range(1, n_payees + 1): + reward_share = (10000 - tot_generated_shares) if i == n_payees else random.randint(0, 10000 - tot_generated_shares) + payee_shares.append([self.nodes[0].getnewaddress(), reward_share]) + tot_generated_shares += reward_share + assert (tot_generated_shares == 10000) platform_node_id = hash160(b'%d' % rnd).hex() if rnd is not None else hash160(b'%d' % node_p2p_port).hex() platform_p2p_port = '%d' % (node_p2p_port + 101) @@ -1225,16 +1239,16 @@ def dynamically_prepare_masternode(self, idx, node_p2p_port, evo=False, rnd=None protx_result = None if evo: - protx_result = self.nodes[0].protx("register_evo", collateral_txid, collateral_vout, ipAndPort, owner_address, bls['public'], voting_address, operatorReward, [[reward_address, 10000]], platform_node_id, platform_p2p_port, platform_http_port, funds_address, True) + protx_result = self.nodes[0].protx("register_evo", collateral_txid, collateral_vout, ipAndPort, owner_address, bls['public'], voting_address, operatorReward, payee_shares, platform_node_id, platform_p2p_port, platform_http_port, funds_address, True) else: - protx_result = self.nodes[0].protx("register", collateral_txid, collateral_vout, ipAndPort, owner_address, bls['public'], voting_address, operatorReward, [[reward_address, 10000]], funds_address, True) + protx_result = self.nodes[0].protx("register", collateral_txid, collateral_vout, ipAndPort, owner_address, bls['public'], voting_address, operatorReward, payee_shares, funds_address, True) self.wait_for_instantlock(protx_result, self.nodes[0]) tip = self.nodes[0].generate(1)[0] self.sync_all(self.nodes) assert_equal(self.nodes[0].getrawtransaction(protx_result, 1, tip)['confirmations'], 1) - mn_info = MasternodeInfo(protx_result, owner_address, voting_address, reward_address, operatorReward, bls['public'], bls['secret'], collateral_address, collateral_txid, collateral_vout, ipAndPort, evo) + mn_info = MasternodeInfo(protx_result, owner_address, voting_address, payee_shares, operatorReward, bls['public'], bls['secret'], collateral_address, collateral_txid, collateral_vout, ipAndPort, evo) self.mninfo.append(mn_info) mn_type_str = "EvoNode" if evo else "MN" From 2f3a1ab84ceaa13610a8853d4c02d76823ddf17c Mon Sep 17 00:00:00 2001 From: ale Date: Sun, 7 Jan 2024 11:16:19 -0500 Subject: [PATCH 08/15] test: add dip0026 functional tests --- test/functional/feature_dip0026.py | 92 ++++++++++++++++++++++++++++++ test/functional/test_runner.py | 1 + 2 files changed, 93 insertions(+) create mode 100755 test/functional/feature_dip0026.py diff --git a/test/functional/feature_dip0026.py b/test/functional/feature_dip0026.py new file mode 100755 index 000000000000..c25e1b09f08e --- /dev/null +++ b/test/functional/feature_dip0026.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python3 + +''' +feature_dip0026.py + +Functional test DIP0026 + +''' +from test_framework.test_framework import DashTestFramework +from test_framework.util import assert_equal + + +class DIP0026Test(DashTestFramework): + def set_test_params(self): + self.set_dash_test_params(6, 5, fast_dip3_enforcement=True, evo_count=1) + + def run_test(self): + multi_payee_mns = [] + for i in range(len(self.nodes)): + if i != 0: + self.connect_nodes(i, 0) + self.activate_dip8() + + self.nodes[0].sporkupdate("SPORK_17_QUORUM_DKG_ENABLED", 0) + self.wait_for_sporks_same() + + # activate v19, v20 + self.activate_v19(expected_activation_height=900) + self.log.info("Activated v19 at height:" + str(self.nodes[0].getblockcount())) + + self.activate_v20() + self.log.info("Activated v20 at height:" + str(self.nodes[0].getblockcount())) + + # make sure that there is a quorum that can handle instant send + self.mine_cycle_quorum(llmq_type_name='llmq_test_dip0024', llmq_type=103) + + self.log.info("Checking that multipayee masternodes are rejected before dip0026 activation") + self.dynamically_add_masternode(evo=False, rnd=7, n_payees=2, should_be_rejected=True) + + # activate dip0026 + self.activate_dip0026() + self.log.info("Activated dip0026 at height:" + str(self.nodes[0].getblockcount())) + self.log.info("Creating a 2-payees masternode") + multi_payee_mns.append(self.dynamically_add_masternode(evo=False, rnd=7, n_payees=2)) + + self.log.info("Checking that there cannot be more than 32 payees:") + self.dynamically_add_masternode(evo=False, rnd=8, n_payees=33, should_be_rejected=True) + + self.log.info("Creating an evo 3-payees masternode") + multi_payee_mns.append(self.dynamically_add_masternode(evo=False, rnd=8, n_payees=3)) + + self.log.info("Checking masternode payments:") + self.test_multipayee_payment(multi_payee_mns) + + def test_multipayee_payment(self, multi_payee_mns): + multi_payee_reward_counts = [0, 0] + # with two full cycles both multi payee masternodes have to be paid twice + for _ in range(len(self.mninfo)*2): + self.nodes[0].generate(1) + # make sure every node has the same chain tip + self.sync_all() + # find out who won with a RPC call + rpc_masternode_winner_info = self.nodes[0].masternode("payments")[0]["masternodes"][0] + # for each multi payee masternode that we created check if it has been paid in the last block + for i, multi_payee_mn in enumerate(multi_payee_mns): + if multi_payee_mn.proTxHash == rpc_masternode_winner_info["proTxHash"]: + multi_payee_reward_counts[i] += 1 + # if so verify that the right addresses/amount have been paid + # NB: Skip the first rpc entry since it's the coinbase tx + self.test_multipayee_payment_internal(rpc_masternode_winner_info["payees"][1:], multi_payee_mn) + + # each multi payee mn must have been paid twice + for count in multi_payee_reward_counts: + assert_equal(count, 2) + + def test_multipayee_payment_internal(self, rpc_masternode_winner_payees, multi_payee_mn): + tot_paid = 0 + # 1) Verify correctness of paid addresses + for i, payee_share in enumerate(multi_payee_mn.rewards_address): + payee_address = payee_share[0] + assert_equal(rpc_masternode_winner_payees[i]['address'], payee_address) + tot_paid += rpc_masternode_winner_payees[i]['amount'] + # 2) Verify correctness of rewards + for i, payee_share in enumerate(multi_payee_mn.rewards_address): + # this is not the exact formula with which rewards are calculated: + # due to integer division it differs up to (10k * number_of_payees) satoshi, + # but for the sake of the test it's fine + assert (abs(tot_paid * payee_share[1] - rpc_masternode_winner_payees[i]['amount'] * 10000) < 10000 * len(multi_payee_mn.rewards_address)) + + +if __name__ == '__main__': + DIP0026Test().main() diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index 73623c9d5051..269d8b41ceb2 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -109,6 +109,7 @@ 'wallet_listtransactions.py', 'feature_multikeysporks.py', 'feature_dip3_v19.py', + 'feature_dip0026.py', 'feature_llmq_signing.py', # NOTE: needs dash_hash to pass 'feature_llmq_signing.py --spork21', # NOTE: needs dash_hash to pass 'feature_llmq_chainlocks.py', # NOTE: needs dash_hash to pass From c55efd79c4f3dd0c6e4e121c28dc938ffc7b264b Mon Sep 17 00:00:00 2001 From: ale Date: Mon, 8 Jan 2024 16:43:31 -0500 Subject: [PATCH 09/15] feat: Serialize and deserialize correctly A helper class has been added which has the role to wrap and correctly serialize and deserialize a vector of payoutShare. --- src/evo/dmnstate.h | 26 ++++++++++++++----------- src/evo/providertx.h | 46 +++++++++++++++++++++++++++++--------------- 2 files changed, 45 insertions(+), 27 deletions(-) diff --git a/src/evo/dmnstate.h b/src/evo/dmnstate.h index 7679eb8d1708..7f4e974fb6a2 100644 --- a/src/evo/dmnstate.h +++ b/src/evo/dmnstate.h @@ -155,8 +155,7 @@ class CDeterministicMNState CBLSLazyPublicKey pubKeyOperator; CKeyID keyIDVoting; CService addr; - // initialized as a vector of length one, in order to deserialize correctly old messages - std::vector payoutShares{PayoutShare(CScript())}; + std::vector payoutShares; CScript scriptOperatorPayout; uint160 platformNodeID{}; @@ -236,13 +235,8 @@ class CDeterministicMNState READWRITE(CBLSLazyPublicKeyVersionWrapper(const_cast(obj.pubKeyOperator), obj.nVersion == CProRegTx::LEGACY_BLS_VERSION)); READWRITE( obj.keyIDVoting, - obj.addr); - if (obj.nVersion < CProRegTx::MULTI_PAYOUT_VERSION) { - READWRITE(obj.payoutShares[0].scriptPayout); - }else{ - READWRITE(obj.payoutShares); - } - READWRITE( + obj.addr, + PayoutSharesSerializerWrapper(const_cast&>(obj.payoutShares), (obj.nVersion < CProRegTx::MULTI_PAYOUT_VERSION)), obj.scriptOperatorPayout, obj.platformNodeID, obj.platformP2PPort, @@ -350,20 +344,27 @@ class CDeterministicMNStateDiff #define DMN_STATE_DIFF_LINE(f) if (a.f != b.f) { state.f = b.f; fields |= Field_##f; } DMN_STATE_DIFF_ALL_FIELDS #undef DMN_STATE_DIFF_LINE - if (fields & Field_pubKeyOperator) { state.nVersion = b.nVersion; fields |= Field_nVersion; } + if (fields & Field_pubKeyOperator || fields & Field_payoutShares) { + state.nVersion = b.nVersion; + fields |= Field_nVersion; + } } [[nodiscard]] UniValue ToJson(MnType nType) const; SERIALIZE_METHODS(CDeterministicMNStateDiff, obj) { - // NOTE: reading pubKeyOperator requires nVersion + // NOTE: reading pubKeyOperator and payoutShares requires nVersion bool read_pubkey{false}; + bool read_payoutShares{false}; READWRITE(VARINT(obj.fields)); #define DMN_STATE_DIFF_LINE(f) \ if (strcmp(#f, "pubKeyOperator") == 0 && (obj.fields & Field_pubKeyOperator)) {\ SER_READ(obj, read_pubkey = true); \ READWRITE(CBLSLazyPublicKeyVersionWrapper(const_cast(obj.state.pubKeyOperator), obj.state.nVersion == CProRegTx::LEGACY_BLS_VERSION)); \ + } else if (strcmp(#f, "payoutShares") == 0 && (obj.fields & Field_payoutShares)) {\ + SER_READ(obj, read_payoutShares = true); \ + READWRITE(PayoutSharesSerializerWrapper(const_cast&>(obj.state.payoutShares), (obj.state.nVersion < CProRegTx::MULTI_PAYOUT_VERSION))); \ } else if (obj.fields & Field_##f) READWRITE(obj.state.f); DMN_STATE_DIFF_ALL_FIELDS @@ -372,6 +373,9 @@ class CDeterministicMNStateDiff SER_READ(obj, obj.fields |= Field_nVersion); SER_READ(obj, obj.state.pubKeyOperator.SetLegacy(obj.state.nVersion == CProRegTx::LEGACY_BLS_VERSION)); } + if (read_payoutShares) { + SER_READ(obj, obj.fields |= Field_nVersion); + } } void ApplyToState(CDeterministicMNState& target) const diff --git a/src/evo/providertx.h b/src/evo/providertx.h index e818a477913c..4f94de4c800f 100644 --- a/src/evo/providertx.h +++ b/src/evo/providertx.h @@ -52,6 +52,29 @@ class PayoutShare } }; +class PayoutSharesSerializerWrapper +{ +private: + std::vector& payoutShares; + bool isSinglePayee; + +public: + PayoutSharesSerializerWrapper(std::vector& payoutShares, bool isSinglePayee) : + payoutShares(payoutShares), isSinglePayee(isSinglePayee) + { + } + + SERIALIZE_METHODS(PayoutSharesSerializerWrapper, obj) + { + if (!obj.isSinglePayee) { + READWRITE(obj.payoutShares); + } else { + SER_READ(obj, obj.payoutShares = std::vector{PayoutShare(CScript())}); + READWRITE(obj.payoutShares[0].scriptPayout); + } + } +}; + class CProRegTx { public: @@ -82,8 +105,7 @@ class CProRegTx CBLSLazyPublicKey pubKeyOperator; CKeyID keyIDVoting; uint16_t nOperatorReward{0}; - // initialized as a default vector of length one, in order to deserialize correctly old messages - std::vector payoutShares{PayoutShare(CScript())}; + std::vector payoutShares; uint256 inputsHash; // replay protection std::vector vchSig; @@ -105,13 +127,9 @@ class CProRegTx obj.keyIDOwner, CBLSLazyPublicKeyVersionWrapper(const_cast(obj.pubKeyOperator), (obj.nVersion == LEGACY_BLS_VERSION)), obj.keyIDVoting, - obj.nOperatorReward); - if (obj.nVersion < MULTI_PAYOUT_VERSION) { - READWRITE(obj.payoutShares[0].scriptPayout); - } else { - READWRITE(obj.payoutShares); - } - READWRITE(obj.inputsHash); + obj.nOperatorReward, + PayoutSharesSerializerWrapper(const_cast&>(obj.payoutShares), (obj.nVersion < MULTI_PAYOUT_VERSION)), + obj.inputsHash); if (obj.nType == MnType::Evo) { READWRITE( obj.platformNodeID, @@ -282,13 +300,9 @@ class CProUpRegTx obj.proTxHash, obj.nMode, CBLSLazyPublicKeyVersionWrapper(const_cast(obj.pubKeyOperator), (obj.nVersion == LEGACY_BLS_VERSION)), - obj.keyIDVoting); - if (obj.nVersion < MULTI_PAYOUT_VERSION) { - READWRITE(obj.payoutShares[0].scriptPayout); - } else { - READWRITE(obj.payoutShares); - } - READWRITE(obj.inputsHash); + obj.keyIDVoting, + PayoutSharesSerializerWrapper(const_cast&>(obj.payoutShares), (obj.nVersion < MULTI_PAYOUT_VERSION)), + obj.inputsHash); if (!(s.GetType() & SER_GETHASH)) { READWRITE( obj.vchSig From a93068e56cb05974f18b479820dadf10476fc209 Mon Sep 17 00:00:00 2001 From: ale Date: Mon, 8 Jan 2024 17:03:48 -0500 Subject: [PATCH 10/15] [squash]: do not initialize payoutShares --- src/evo/providertx.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/evo/providertx.h b/src/evo/providertx.h index 4f94de4c800f..43d234d0898c 100644 --- a/src/evo/providertx.h +++ b/src/evo/providertx.h @@ -282,8 +282,7 @@ class CProUpRegTx uint16_t nMode{0}; // only 0 supported for now CBLSLazyPublicKey pubKeyOperator; CKeyID keyIDVoting; - // initialized as a default vector of length one, in order to deserialize correctly old messages - std::vector payoutShares{PayoutShare(CScript())}; + std::vector payoutShares; uint256 inputsHash; // replay protection std::vector vchSig; From 4c60ebd35c0bdf74ae8ddef44f9bb7918565ab6e Mon Sep 17 00:00:00 2001 From: ale Date: Tue, 9 Jan 2024 09:41:02 -0500 Subject: [PATCH 11/15] [squash]: revert changes to lines I did not touch --- src/bloom.cpp | 12 ++++++------ src/consensus/params.h | 3 ++- src/deploymentinfo.cpp | 4 ++-- src/evo/providertx.h | 6 ++++-- src/evo/simplifiedmns.cpp | 2 +- src/qt/masternodelist.cpp | 8 ++++---- src/rpc/evo.cpp | 2 +- src/test/evo_deterministicmns_tests.cpp | 2 ++ src/test/evo_trivialvalidation.cpp | 2 +- 9 files changed, 23 insertions(+), 18 deletions(-) diff --git a/src/bloom.cpp b/src/bloom.cpp index c2678d2ed425..79149726341e 100644 --- a/src/bloom.cpp +++ b/src/bloom.cpp @@ -139,10 +139,10 @@ bool CBloomFilter::CheckSpecialTransactionMatchesAndUpdate(const CTransaction &t case(TRANSACTION_PROVIDER_REGISTER): { CProRegTx proTx; if (GetTxPayload(tx, proTx)) { - if (contains(proTx.collateralOutpoint) || - contains(proTx.keyIDOwner) || - contains(proTx.keyIDVoting) || - CheckPayeeSharesScripts(proTx.payoutShares)) { + if(contains(proTx.collateralOutpoint) || + contains(proTx.keyIDOwner) || + contains(proTx.keyIDVoting) || + CheckPayeeSharesScripts(proTx.payoutShares)) { if ((nFlags & BLOOM_UPDATE_MASK) == BLOOM_UPDATE_ALL) insert(tx.GetHash()); return true; @@ -169,8 +169,8 @@ bool CBloomFilter::CheckSpecialTransactionMatchesAndUpdate(const CTransaction &t if (GetTxPayload(tx, proTx)) { if(contains(proTx.proTxHash)) return true; - if (contains(proTx.keyIDVoting) || - CheckPayeeSharesScripts(proTx.payoutShares)) { + if(contains(proTx.keyIDVoting) || + CheckPayeeSharesScripts(proTx.payoutShares)) { if ((nFlags & BLOOM_UPDATE_MASK) == BLOOM_UPDATE_ALL) insert(proTx.proTxHash); return true; diff --git a/src/consensus/params.h b/src/consensus/params.h index 4949ba85c80d..7ae9a697a536 100644 --- a/src/consensus/params.h +++ b/src/consensus/params.h @@ -31,7 +31,8 @@ enum BuriedDeployment : int16_t }; constexpr bool ValidDeployment(BuriedDeployment dep) { return DEPLOYMENT_HEIGHTINCB <= dep && dep <= DEPLOYMENT_V19; } -enum DeploymentPos : uint16_t { +enum DeploymentPos : uint16_t +{ DEPLOYMENT_TESTDUMMY, DEPLOYMENT_V20, // Deployment of EHF, LLMQ Randomness Beacon DEPLOYMENT_MN_RR, // Deployment of Masternode Reward Location Reallocation diff --git a/src/deploymentinfo.cpp b/src/deploymentinfo.cpp index 2bd7aaf5c8d1..1a0963c90032 100644 --- a/src/deploymentinfo.cpp +++ b/src/deploymentinfo.cpp @@ -8,8 +8,8 @@ const struct VBDeploymentInfo VersionBitsDeploymentInfo[Consensus::MAX_VERSION_BITS_DEPLOYMENTS] = { { - /*.name =*/"testdummy", - /*.gbt_force =*/true, + /*.name =*/ "testdummy", + /*.gbt_force =*/ true, }, { /*.name =*/"v20", diff --git a/src/evo/providertx.h b/src/evo/providertx.h index 43d234d0898c..88c34665196a 100644 --- a/src/evo/providertx.h +++ b/src/evo/providertx.h @@ -129,7 +129,8 @@ class CProRegTx obj.keyIDVoting, obj.nOperatorReward, PayoutSharesSerializerWrapper(const_cast&>(obj.payoutShares), (obj.nVersion < MULTI_PAYOUT_VERSION)), - obj.inputsHash); + obj.inputsHash + ); if (obj.nType == MnType::Evo) { READWRITE( obj.platformNodeID, @@ -301,7 +302,8 @@ class CProUpRegTx CBLSLazyPublicKeyVersionWrapper(const_cast(obj.pubKeyOperator), (obj.nVersion == LEGACY_BLS_VERSION)), obj.keyIDVoting, PayoutSharesSerializerWrapper(const_cast&>(obj.payoutShares), (obj.nVersion < MULTI_PAYOUT_VERSION)), - obj.inputsHash); + obj.inputsHash + ); if (!(s.GetType() & SER_GETHASH)) { READWRITE( obj.vchSig diff --git a/src/evo/simplifiedmns.cpp b/src/evo/simplifiedmns.cpp index 1a08beb65790..236e91efab12 100644 --- a/src/evo/simplifiedmns.cpp +++ b/src/evo/simplifiedmns.cpp @@ -307,7 +307,7 @@ CSimplifiedMNListDiff BuildSimplifiedDiff(const CDeterministicMNList& from, cons CSimplifiedMNListEntry sme2(*fromPtr); if ((sme1 != sme2) || (extended && (sme1.payoutShares != sme2.payoutShares || sme1.scriptOperatorPayout != sme2.scriptOperatorPayout))) { - diffRet.mnList.push_back(std::move(sme1)); + diffRet.mnList.push_back(std::move(sme1)); } } }); diff --git a/src/qt/masternodelist.cpp b/src/qt/masternodelist.cpp index 5795ce3d43be..d9e40af80f35 100644 --- a/src/qt/masternodelist.cpp +++ b/src/qt/masternodelist.cpp @@ -222,10 +222,10 @@ void MasternodeList::updateDIP3List() if (fMyPayee) break; } bool fMyMasternode = setOutpts.count(dmn.collateralOutpoint) || - walletModel->wallet().isSpendable(PKHash(dmn.pdmnState->keyIDOwner)) || - walletModel->wallet().isSpendable(PKHash(dmn.pdmnState->keyIDVoting)) || - fMyPayee || - walletModel->wallet().isSpendable(dmn.pdmnState->scriptOperatorPayout); + walletModel->wallet().isSpendable(PKHash(dmn.pdmnState->keyIDOwner)) || + walletModel->wallet().isSpendable(PKHash(dmn.pdmnState->keyIDVoting)) || + fMyPayee || + walletModel->wallet().isSpendable(dmn.pdmnState->scriptOperatorPayout); if (!fMyMasternode) return; } // populate list diff --git a/src/rpc/evo.cpp b/src/rpc/evo.cpp index a743526c428d..fc95e9971857 100644 --- a/src/rpc/evo.cpp +++ b/src/rpc/evo.cpp @@ -637,7 +637,7 @@ static UniValue protx_register_common_wrapper(const JSONRPCRequest& request, EnsureWalletIsUnlocked(wallet.get()); } - const bool isV19active { DeploymentActiveAfter(WITH_LOCK(cs_main, return chainman.ActiveChain().Tip();), Params().GetConsensus(), Consensus::DEPLOYMENT_V19) }; + const bool isV19active{DeploymentActiveAfter(WITH_LOCK(cs_main, return chainman.ActiveChain().Tip();), Params().GetConsensus(), Consensus::DEPLOYMENT_V19)}; if (isEvoRequested && !isV19active) { throw JSONRPCError(RPC_INVALID_REQUEST, "EvoNodes aren't allowed yet"); } diff --git a/src/test/evo_deterministicmns_tests.cpp b/src/test/evo_deterministicmns_tests.cpp index 085277965996..056acc713468 100644 --- a/src/test/evo_deterministicmns_tests.cpp +++ b/src/test/evo_deterministicmns_tests.cpp @@ -27,6 +27,7 @@ #include using SimpleUTXOMap = std::map>; + static SimpleUTXOMap BuildSimpleUtxoMap(const std::vector& txs) { SimpleUTXOMap utxos; @@ -732,6 +733,7 @@ void FuncTestMempoolReorg(TestChainSetup& setup) payload.pubKeyOperator.Set(operatorKey.GetPublicKey(), bls::bls_legacy_scheme.load()); payload.keyIDVoting = ownerKey.GetPubKey().GetID(); payload.payoutShares = {PayoutShare(scriptPayout)}; + for (size_t i = 0; i < tx_collateral.vout.size(); ++i) { if (tx_collateral.vout[i].nValue == dmn_types::Regular.collat_amount) { payload.collateralOutpoint = COutPoint(tx_collateral.GetHash(), i); diff --git a/src/test/evo_trivialvalidation.cpp b/src/test/evo_trivialvalidation.cpp index b63ee04a2df1..0429c4891414 100644 --- a/src/test/evo_trivialvalidation.cpp +++ b/src/test/evo_trivialvalidation.cpp @@ -17,7 +17,7 @@ extern UniValue read_json(const std::string& jsondata); BOOST_FIXTURE_TEST_SUITE(evo_trivialvalidation, BasicTestingSetup) -template +template void TestTxHelper(const CMutableTransaction& tx, bool is_basic_bls, bool is_multi_payout_active, bool expected_failure, const std::string& expected_error) { T payload; From 3ba76187d9b6a2e821358e4d9d26e1f7b6ba292e Mon Sep 17 00:00:00 2001 From: Alessandro Rezzi Date: Wed, 10 Jan 2024 15:37:36 +0100 Subject: [PATCH 12/15] [squash]: use ranges in place of for loop --- src/bloom.cpp | 9 +-------- src/qt/masternodelist.cpp | 8 +++----- 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/src/bloom.cpp b/src/bloom.cpp index 79149726341e..a8c1bfdf434d 100644 --- a/src/bloom.cpp +++ b/src/bloom.cpp @@ -112,14 +112,7 @@ bool CBloomFilter::CheckScript(const CScript &script) const bool CBloomFilter::CheckPayeeSharesScripts(const std::vector& payoutShares) const { - bool scriptCheck = false; - for (const auto& payoutShare : payoutShares) { - if (CheckScript(payoutShare.scriptPayout)) { - scriptCheck = true; - break; - } - } - return scriptCheck; + return ranges::any_of(payoutShares, [&](const auto& payoutShare) { return CheckScript(payoutShare.scriptPayout); }); } // If the transaction is a special transaction that has a registration // transaction hash, test the registration transaction hash. diff --git a/src/qt/masternodelist.cpp b/src/qt/masternodelist.cpp index d9e40af80f35..073e4e10d3d1 100644 --- a/src/qt/masternodelist.cpp +++ b/src/qt/masternodelist.cpp @@ -216,11 +216,9 @@ void MasternodeList::updateDIP3List() mnList.ForEachMN(false, [&](auto& dmn) { if (walletModel && ui->checkBoxMyMasternodesOnly->isChecked()) { - bool fMyPayee = false; - for (const auto& payoutShare : dmn.pdmnState->payoutShares) { - fMyPayee |= walletModel->wallet().isSpendable(payoutShare.scriptPayout); - if (fMyPayee) break; - } + bool fMyPayee = ranges::any_of(dmn.pdmnState->payoutShares, [&](const auto& payoutShare) { + return walletModel->wallet().isSpendable(payoutShare.scriptPayout); + }); bool fMyMasternode = setOutpts.count(dmn.collateralOutpoint) || walletModel->wallet().isSpendable(PKHash(dmn.pdmnState->keyIDOwner)) || walletModel->wallet().isSpendable(PKHash(dmn.pdmnState->keyIDVoting)) || From 97be000d513e502a605a837324df791bf315fae8 Mon Sep 17 00:00:00 2001 From: Alessandro Rezzi Date: Wed, 10 Jan 2024 15:45:21 +0100 Subject: [PATCH 13/15] [squash]: apply review changes to isTriviallyValid and if formatting --- src/evo/deterministicmns.cpp | 4 +++- src/evo/providertx.cpp | 7 +++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/evo/deterministicmns.cpp b/src/evo/deterministicmns.cpp index bb2eb019e5e9..031da8d06e32 100644 --- a/src/evo/deterministicmns.cpp +++ b/src/evo/deterministicmns.cpp @@ -1710,7 +1710,9 @@ bool CheckProUpRegTx(const CTransaction& tx, gsl::not_null p const auto& scriptIterator = std::find_if(ptx.payoutShares.begin(), ptx.payoutShares.end(), [&](const auto& payoutShare){ return !ExtractDestination(payoutShare.scriptPayout, payoutDest); }); - if(scriptIterator != ptx.payoutShares.end()) return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-payee-dest"); + if (scriptIterator != ptx.payoutShares.end()) { + return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-payee-dest"); + } auto mnList = deterministicMNManager->GetListForBlock(pindexPrev); auto dmn = mnList.GetMN(ptx.proTxHash); diff --git a/src/evo/providertx.cpp b/src/evo/providertx.cpp index 1d7b9a92c61f..07a384e365cb 100644 --- a/src/evo/providertx.cpp +++ b/src/evo/providertx.cpp @@ -17,7 +17,7 @@ static bool TriviallyVerifyProRegPayees(const ProRegTx& proRegTx, TxValidationSt { const std::vector& payoutShares = proRegTx.payoutShares; uint16_t totalPayoutReward{0}; - if (payoutShares.size() > 32) { + if (payoutShares.size() > 32 || payoutShares.empty()) { return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-payee-size"); } if (payoutShares.size() > 1 && proRegTx.nVersion < ProRegTx::MULTI_PAYOUT_VERSION) { @@ -64,7 +64,10 @@ bool CProRegTx::IsTriviallyValid(bool is_basic_scheme_active, bool is_multi_payo if (nOperatorReward > 10000) { return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-operator-reward"); } - if (!TriviallyVerifyProRegPayees(*this, state)) return false; + if (!TriviallyVerifyProRegPayees(*this, state)) { + // pass the state returned by the function above + return false; + } for (const auto& payoutShare : payoutShares) { CTxDestination payoutDest; if (!ExtractDestination(payoutShare.scriptPayout, payoutDest)) { From 84d9fd62d5b80452c084e250673000a12cfb9292 Mon Sep 17 00:00:00 2001 From: Alessandro Rezzi Date: Wed, 10 Jan 2024 19:23:34 +0100 Subject: [PATCH 14/15] [squash]: include all payoutShares in ToString() logs --- src/evo/dmnstate.cpp | 11 ++++++----- src/evo/providertx.cpp | 24 ++++++++++++------------ src/evo/providertx.h | 6 ++++++ src/evo/simplifiedmns.cpp | 11 ++++++----- 4 files changed, 30 insertions(+), 22 deletions(-) diff --git a/src/evo/dmnstate.cpp b/src/evo/dmnstate.cpp index 56b48d3ae74e..c741cdc7fb9c 100644 --- a/src/evo/dmnstate.cpp +++ b/src/evo/dmnstate.cpp @@ -16,19 +16,20 @@ std::string CDeterministicMNState::ToString() const { CTxDestination dest; - std::string payoutAddress = "unknown"; + std::string payoutSharesStr; std::string operatorPayoutAddress = "none"; - if (ExtractDestination(payoutShares[0].scriptPayout, dest)) { - payoutAddress = EncodeDestination(dest); + for (const auto& payoutShare : payoutShares) { + if (!payoutSharesStr.empty()) payoutSharesStr += ", "; + payoutSharesStr += payoutShare.ToString(); } if (ExtractDestination(scriptOperatorPayout, dest)) { operatorPayoutAddress = EncodeDestination(dest); } return strprintf("CDeterministicMNState(nVersion=%d, nRegisteredHeight=%d, nLastPaidHeight=%d, nPoSePenalty=%d, nPoSeRevivedHeight=%d, nPoSeBanHeight=%d, nRevocationReason=%d, " - "ownerAddress=%s, pubKeyOperator=%s, votingAddress=%s, addr=%s, payoutAddress=%s, operatorPayoutAddress=%s)", + "ownerAddress=%s, pubKeyOperator=%s, votingAddress=%s, addr=%s, payoutShares=%s, operatorPayoutAddress=%s)", nVersion, nRegisteredHeight, nLastPaidHeight, nPoSePenalty, nPoSeRevivedHeight, nPoSeBanHeight, nRevocationReason, - EncodeDestination(PKHash(keyIDOwner)), pubKeyOperator.ToString(), EncodeDestination(PKHash(keyIDVoting)), addr.ToStringIPPort(false), payoutAddress, operatorPayoutAddress); + EncodeDestination(PKHash(keyIDOwner)), pubKeyOperator.ToString(), EncodeDestination(PKHash(keyIDVoting)), addr.ToStringIPPort(false), payoutSharesStr, operatorPayoutAddress); } UniValue CDeterministicMNState::ToJson(MnType nType) const diff --git a/src/evo/providertx.cpp b/src/evo/providertx.cpp index 07a384e365cb..000182fb99b0 100644 --- a/src/evo/providertx.cpp +++ b/src/evo/providertx.cpp @@ -115,14 +115,14 @@ std::string CProRegTx::MakeSignString() const std::string CProRegTx::ToString() const { - CTxDestination dest; - std::string payee = "unknown"; - if (ExtractDestination(payoutShares[0].scriptPayout, dest)) { - payee = EncodeDestination(dest); + std::string payoutSharesStr; + for (const auto& payoutShare : payoutShares) { + if (!payoutSharesStr.empty()) payoutSharesStr += ", "; + payoutSharesStr += payoutShare.ToString(); } - return strprintf("CProRegTx(nVersion=%d, nType=%d, collateralOutpoint=%s, addr=%s, nOperatorReward=%f, ownerAddress=%s, pubKeyOperator=%s, votingAddress=%s, scriptPayout=%s, platformNodeID=%s, platformP2PPort=%d, platformHTTPPort=%d)", - nVersion, ToUnderlying(nType), collateralOutpoint.ToStringShort(), addr.ToString(), (double)nOperatorReward / 100, EncodeDestination(PKHash(keyIDOwner)), pubKeyOperator.ToString(), EncodeDestination(PKHash(keyIDVoting)), payee, platformNodeID.ToString(), platformP2PPort, platformHTTPPort); + return strprintf("CProRegTx(nVersion=%d, nType=%d, collateralOutpoint=%s, addr=%s, nOperatorReward=%f, ownerAddress=%s, pubKeyOperator=%s, votingAddress=%s, payoutShares=%s, platformNodeID=%s, platformP2PPort=%d, platformHTTPPort=%d)", + nVersion, ToUnderlying(nType), collateralOutpoint.ToStringShort(), addr.ToString(), (double)nOperatorReward / 100, EncodeDestination(PKHash(keyIDOwner)), pubKeyOperator.ToString(), EncodeDestination(PKHash(keyIDVoting)), payoutSharesStr, platformNodeID.ToString(), platformP2PPort, platformHTTPPort); } bool CProUpServTx::IsTriviallyValid(bool is_basic_scheme_active, bool is_multi_payout_active, TxValidationState& state) const @@ -169,14 +169,14 @@ bool CProUpRegTx::IsTriviallyValid(bool is_basic_scheme_active, bool is_multi_pa std::string CProUpRegTx::ToString() const { - CTxDestination dest; - std::string payee = "unknown"; - if (ExtractDestination(payoutShares[0].scriptPayout, dest)) { - payee = EncodeDestination(dest); + std::string payoutSharesStr; + for (const auto& payoutShare : payoutShares) { + if (!payoutSharesStr.empty()) payoutSharesStr += ", "; + payoutSharesStr += payoutShare.ToString(); } - return strprintf("CProUpRegTx(nVersion=%d, proTxHash=%s, pubKeyOperator=%s, votingAddress=%s, payoutAddress=%s)", - nVersion, proTxHash.ToString(), pubKeyOperator.ToString(), EncodeDestination(PKHash(keyIDVoting)), payee); + return strprintf("CProUpRegTx(nVersion=%d, proTxHash=%s, pubKeyOperator=%s, votingAddress=%s, payoutShares=%s)", + nVersion, proTxHash.ToString(), pubKeyOperator.ToString(), EncodeDestination(PKHash(keyIDVoting)), payoutSharesStr); } bool CProUpRevTx::IsTriviallyValid(bool is_basic_scheme_active, bool is_multi_payout_active, TxValidationState& state) const diff --git a/src/evo/providertx.h b/src/evo/providertx.h index 88c34665196a..0386cca6096f 100644 --- a/src/evo/providertx.h +++ b/src/evo/providertx.h @@ -50,6 +50,12 @@ class PayoutShare ret.pushKV("payoutShareReward", payoutShareReward); return ret; } + std::string ToString() const + { + CTxDestination dest; + std::string payee = ExtractDestination(scriptPayout, dest) ? EncodeDestination(dest) : "unknown"; + return strprintf("(scriptPayout=%s, payoutShare=%d)", payee, payoutShareReward); + } }; class PayoutSharesSerializerWrapper diff --git a/src/evo/simplifiedmns.cpp b/src/evo/simplifiedmns.cpp index 236e91efab12..a515fccae5e7 100644 --- a/src/evo/simplifiedmns.cpp +++ b/src/evo/simplifiedmns.cpp @@ -53,17 +53,18 @@ uint256 CSimplifiedMNListEntry::CalcHash() const std::string CSimplifiedMNListEntry::ToString() const { CTxDestination dest; - std::string payoutAddress = "unknown"; + std::string payoutSharesStr; std::string operatorPayoutAddress = "none"; - if (ExtractDestination(payoutShares[0].scriptPayout, dest)) { - payoutAddress = EncodeDestination(dest); + for (const auto& payoutShare : payoutShares) { + if (!payoutSharesStr.empty()) payoutSharesStr += ", "; + payoutSharesStr += payoutShare.ToString(); } if (ExtractDestination(scriptOperatorPayout, dest)) { operatorPayoutAddress = EncodeDestination(dest); } - return strprintf("CSimplifiedMNListEntry(nVersion=%d, nType=%d, proRegTxHash=%s, confirmedHash=%s, service=%s, pubKeyOperator=%s, votingAddress=%s, isValid=%d, payoutAddress=%s, operatorPayoutAddress=%s, platformHTTPPort=%d, platformNodeID=%s)", - nVersion, ToUnderlying(nType), proRegTxHash.ToString(), confirmedHash.ToString(), service.ToString(false), pubKeyOperator.ToString(), EncodeDestination(PKHash(keyIDVoting)), isValid, payoutAddress, operatorPayoutAddress, platformHTTPPort, platformNodeID.ToString()); + return strprintf("CSimplifiedMNListEntry(nVersion=%d, nType=%d, proRegTxHash=%s, confirmedHash=%s, service=%s, pubKeyOperator=%s, votingAddress=%s, isValid=%d, payoutShares=%s, operatorPayoutAddress=%s, platformHTTPPort=%d, platformNodeID=%s)", + nVersion, ToUnderlying(nType), proRegTxHash.ToString(), confirmedHash.ToString(), service.ToString(false), pubKeyOperator.ToString(), EncodeDestination(PKHash(keyIDVoting)), isValid, payoutSharesStr, operatorPayoutAddress, platformHTTPPort, platformNodeID.ToString()); } UniValue CSimplifiedMNListEntry::ToJson(bool extended) const From 4b5c4aaac1dd8f0cea62871a4aac7045b732df95 Mon Sep 17 00:00:00 2001 From: Alessandro Rezzi Date: Wed, 10 Jan 2024 21:14:09 +0100 Subject: [PATCH 15/15] [squash]: require fee address if tx is multi payee --- src/rpc/evo.cpp | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/rpc/evo.cpp b/src/rpc/evo.cpp index fc95e9971857..44ace3f6f6b0 100644 --- a/src/rpc/evo.cpp +++ b/src/rpc/evo.cpp @@ -739,12 +739,14 @@ static UniValue protx_register_common_wrapper(const JSONRPCRequest& request, } CTxDestination fundDest; - // TODO: decide who should fund the tx ExtractDestination(ptx.payoutShares[0].scriptPayout, fundDest); if (!request.params[paramIdx + 6].isNull()) { fundDest = DecodeDestination(request.params[paramIdx + 6].get_str()); if (!IsValidDestination(fundDest)) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, std::string("Invalid Dash address: ") + request.params[paramIdx + 6].get_str()); + } else if (ptx.payoutShares.size() > 1) { + // For multi payees protx the user must specify the fee address + throw JSONRPCError(RPC_INVALID_REQUEST, "Please specify a fee source address"); } FundSpecialTx(wallet.get(), tx, ptx, fundDest); @@ -1023,8 +1025,11 @@ static UniValue protx_update_service_common_wrapper(const JSONRPCRequest& reques // use operator reward address as default source for fees ExtractDestination(ptx.scriptOperatorPayout, feeSource); } else { - // use first payout address as default source for fees - // TODO: decide who should fund the tx + if (dmn->pdmnState->payoutShares.size() > 1) { + // For multi payees protx the user must specify the fee source address + throw JSONRPCError(RPC_INVALID_REQUEST, "Please specify a fee source address"); + } + // For single payees protx try to fund with the only address specified ExtractDestination(dmn->pdmnState->payoutShares[0].scriptPayout, feeSource); } } @@ -1121,13 +1126,15 @@ static UniValue protx_update_registrar_wrapper(const JSONRPCRequest& request, co // make sure we get enough fees added ptx.vchSig.resize(65); CTxDestination payoutDest; - // TODO: decide who should fund the tx ExtractDestination(ptx.payoutShares[0].scriptPayout, payoutDest); CTxDestination feeSourceDest = payoutDest; if (!request.params[4].isNull()) { feeSourceDest = DecodeDestination(request.params[4].get_str()); if (!IsValidDestination(feeSourceDest)) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, std::string("Invalid Dash address: ") + request.params[4].get_str()); + } else if (ptx.payoutShares.size() > 1) { + // For multi payees protx the user must specify the fee source address + throw JSONRPCError(RPC_INVALID_REQUEST, "Please specify a fee source address"); } FundSpecialTx(wallet.get(), tx, ptx, feeSourceDest); @@ -1219,10 +1226,12 @@ static UniValue protx_revoke(const JSONRPCRequest& request, const ChainstateMana ExtractDestination(dmn->pdmnState->scriptOperatorPayout, txDest); FundSpecialTx(wallet.get(), tx, ptx, txDest); } else if (dmn->pdmnState->payoutShares[0].scriptPayout != CScript()) { + if (dmn->pdmnState->payoutShares.size() > 1) { + // For multi payees the user must specify the fee address + throw JSONRPCError(RPC_INVALID_REQUEST, "Please specify a fee source address"); + } // Using funds from previously specified masternode payout address CTxDestination txDest; - // Again, as fee source we can just use the first address in the payee list - // TODO: decide who should fund the tx ExtractDestination(dmn->pdmnState->payoutShares[0].scriptPayout, txDest); FundSpecialTx(wallet.get(), tx, ptx, txDest); } else {