diff --git a/src/bloom.cpp b/src/bloom.cpp index 54c9204a2ddd2..a8c1bfdf434d7 100644 --- a/src/bloom.cpp +++ b/src/bloom.cpp @@ -110,6 +110,10 @@ bool CBloomFilter::CheckScript(const CScript &script) const return false; } +bool CBloomFilter::CheckPayeeSharesScripts(const std::vector& payoutShares) const +{ + 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. // If the transaction is a special transaction with any public keys or any @@ -131,7 +135,7 @@ bool CBloomFilter::CheckSpecialTransactionMatchesAndUpdate(const CTransaction &t if(contains(proTx.collateralOutpoint) || contains(proTx.keyIDOwner) || contains(proTx.keyIDVoting) || - CheckScript(proTx.scriptPayout)) { + CheckPayeeSharesScripts(proTx.payoutShares)) { if ((nFlags & BLOOM_UPDATE_MASK) == BLOOM_UPDATE_ALL) insert(tx.GetHash()); return true; @@ -159,7 +163,7 @@ bool CBloomFilter::CheckSpecialTransactionMatchesAndUpdate(const CTransaction &t if(contains(proTx.proTxHash)) return true; if(contains(proTx.keyIDVoting) || - CheckScript(proTx.scriptPayout)) { + 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 29bfb61522e59..a925697505911 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/chainparams.cpp b/src/chainparams.cpp index ef2adfa23acbe..b8011e165ee8e 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 74c148ef5be05..7ae9a697a536e 100644 --- a/src/consensus/params.h +++ b/src/consensus/params.h @@ -36,10 +36,11 @@ 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 17da5d3d36fa5..1a0963c900328 100644 --- a/src/deploymentinfo.cpp +++ b/src/deploymentinfo.cpp @@ -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) diff --git a/src/evo/deterministicmns.cpp b/src/evo/deterministicmns.cpp index 73d72b2be6392..031da8d06e329 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,8 +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 + 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"); } diff --git a/src/evo/dmnstate.cpp b/src/evo/dmnstate.cpp index 54f4aa7f7950a..c741cdc7fb9cd 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(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 @@ -52,10 +53,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 +104,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 0a0f62316daef..7f4e974fb6a2d 100644 --- a/src/evo/dmnstate.h +++ b/src/evo/dmnstate.h @@ -155,7 +155,7 @@ class CDeterministicMNState CBLSLazyPublicKey pubKeyOperator; CKeyID keyIDVoting; CService addr; - CScript scriptPayout; + std::vector payoutShares; CScript scriptOperatorPayout; uint160 platformNodeID{}; @@ -170,7 +170,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 +189,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 +206,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), @@ -236,7 +236,7 @@ class CDeterministicMNState READWRITE( obj.keyIDVoting, obj.addr, - obj.scriptPayout, + PayoutSharesSerializerWrapper(const_cast&>(obj.payoutShares), (obj.nVersion < CProRegTx::MULTI_PAYOUT_VERSION)), obj.scriptOperatorPayout, obj.platformNodeID, obj.platformP2PPort, @@ -302,7 +302,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 +324,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) \ @@ -344,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 @@ -366,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.cpp b/src/evo/providertx.cpp index 89c1e7af42800..000182fb99b0c 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 || payoutShares.empty()) { + 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,24 @@ 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)) { + // pass the state returned by the function above + 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 +90,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)) + "|"; @@ -81,17 +115,17 @@ std::string CProRegTx::MakeSignString() const std::string CProRegTx::ToString() const { - CTxDestination dest; - std::string payee = "unknown"; - if (ExtractDestination(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, 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 +149,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,25 +164,22 @@ 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)) { - 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, 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 dab5c3330a15e..0386cca6096f6 100644 --- a/src/evo/providertx.h +++ b/src/evo/providertx.h @@ -21,16 +21,82 @@ 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; + } + 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 +{ +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: 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 +111,7 @@ class CProRegTx CBLSLazyPublicKey pubKeyOperator; CKeyID keyIDVoting; uint16_t nOperatorReward{0}; - CScript scriptPayout; + std::vector payoutShares; uint256 inputsHash; // replay protection std::vector vchSig; @@ -54,7 +120,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; } @@ -68,7 +134,7 @@ class CProRegTx CBLSLazyPublicKeyVersionWrapper(const_cast(obj.pubKeyOperator), (obj.nVersion == LEGACY_BLS_VERSION)), obj.keyIDVoting, obj.nOperatorReward, - obj.scriptPayout, + PayoutSharesSerializerWrapper(const_cast&>(obj.payoutShares), (obj.nVersion < MULTI_PAYOUT_VERSION)), obj.inputsHash ); if (obj.nType == MnType::Evo) { @@ -100,9 +166,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 +183,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 +263,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 +272,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 +289,7 @@ class CProUpRegTx uint16_t nMode{0}; // only 0 supported for now CBLSLazyPublicKey pubKeyOperator; CKeyID keyIDVoting; - CScript scriptPayout; + std::vector payoutShares; uint256 inputsHash; // replay protection std::vector vchSig; @@ -223,7 +298,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; } @@ -232,7 +307,7 @@ class CProUpRegTx obj.nMode, CBLSLazyPublicKeyVersionWrapper(const_cast(obj.pubKeyOperator), (obj.nVersion == LEGACY_BLS_VERSION)), obj.keyIDVoting, - obj.scriptPayout, + PayoutSharesSerializerWrapper(const_cast&>(obj.payoutShares), (obj.nVersion < MULTI_PAYOUT_VERSION)), obj.inputsHash ); if (!(s.GetType() & SER_GETHASH)) { @@ -251,15 +326,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 +401,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 9ced1bb0d251f..a515fccae5e7a 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) @@ -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(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 @@ -85,9 +86,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,7 +307,7 @@ CSimplifiedMNListDiff BuildSimplifiedDiff(const CDeterministicMNList& from, cons CSimplifiedMNListEntry sme1(toPtr); CSimplifiedMNListEntry sme2(*fromPtr); if ((sme1 != sme2) || - (extended && (sme1.scriptPayout != sme2.scriptPayout || sme1.scriptOperatorPayout != sme2.scriptOperatorPayout))) { + (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 4abde7c39d7bc..97543c8f11154 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/llmq/ehf_signals.cpp b/src/llmq/ehf_signals.cpp index 697b6a9d07535..f4824af9bc145 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()); + } } } } diff --git a/src/masternode/payments.cpp b/src/masternode/payments.cpp index 2bf94d213d04a..ff4e0dbe9edbf 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 36ca8ae1de486..073e4e10d3d17 100644 --- a/src/qt/masternodelist.cpp +++ b/src/qt/masternodelist.cpp @@ -216,10 +216,13 @@ void MasternodeList::updateDIP3List() mnList.ForEachMN(false, [&](auto& dmn) { if (walletModel && ui->checkBoxMyMasternodesOnly->isChecked()) { + 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)) || - walletModel->wallet().isSpendable(dmn.pdmnState->scriptPayout) || + fMyPayee || walletModel->wallet().isSpendable(dmn.pdmnState->scriptOperatorPayout); if (!fMyMasternode) return; } @@ -243,9 +246,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); diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index 9a93b207b01f0..1f5a9f3cc10d5 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 87c59e5c0256e..44ace3f6f6b0d 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{ @@ -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,18 +733,20 @@ 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; + 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); @@ -997,8 +1025,12 @@ 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); + 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); } } @@ -1054,7 +1086,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 +1099,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,14 +1123,18 @@ 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; + 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); @@ -1192,14 +1221,18 @@ 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()) { + 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; - ExtractDestination(dmn->pdmnState->scriptPayout, txDest); + 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 +1323,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 +1396,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 e3403dc90a14c..553a48a7af733 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 && diff --git a/src/test/block_reward_reallocation_tests.cpp b/src/test/block_reward_reallocation_tests.cpp index 3563a2d1aae15..d3cc5dfc22de9 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 cfc789cbee377..bfd17160c816a 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 c148a8f8de12c..85715227122b6 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 fb7adcaff8b7f..056acc7134688 100644 --- a/src/test/evo_deterministicmns_tests.cpp +++ b/src/test/evo_deterministicmns_tests.cpp @@ -20,6 +20,7 @@ #include #include +#include #include #include @@ -94,24 +95,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 +141,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 +188,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 +210,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 +244,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 +279,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 +300,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 +398,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 +533,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 +590,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 +647,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,12 +727,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) { @@ -663,7 +781,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 +801,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 +858,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 +923,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 1adaa99654dfb..0429c48914143 100644 --- a/src/test/evo_trivialvalidation.cpp +++ b/src/test/evo_trivialvalidation.cpp @@ -18,7 +18,7 @@ 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) +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: diff --git a/test/functional/feature_dip0026.py b/test/functional/feature_dip0026.py new file mode 100755 index 0000000000000..c25e1b09f08e1 --- /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/feature_dip3_deterministicmns.py b/test/functional/feature_dip3_deterministicmns.py index b4d0b90156d3b..19a8f50e914d7 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 ba0d53ad0fd4a..6859041de2ec1 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 19ac7544e4f85..bf7a785bfc527 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 55fe3562711aa..7550786858450 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 a0b26108d8ca0..703fa945dd17c 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, 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, 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" @@ -1310,10 +1324,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 diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index 73623c9d50511..269d8b41ceb2a 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