feat!: 4k collateral high performance masternode implementation (#5039)

## Issue being fixed or feature implemented


## What was done?
Implementation of 4k collateral HPMN.

## How Has This Been Tested?

## Breaking Changes

## Checklist:
- [x] I have performed a self-review of my own code
- [x] I have commented my code, particularly in hard-to-understand areas
- [x] I have added or updated relevant unit/integration/functional/e2e
tests
- [x] I have made corresponding changes to the documentation

**For repository code-owners and collaborators only**
- [x] I have assigned this pull request to a milestone

---------

Co-authored-by: thephez <thephez@users.noreply.github.com>
Co-authored-by: UdjinM6 <UdjinM6@users.noreply.github.com>
Co-authored-by: pasta <pasta@dashboost.org>
Co-authored-by: PastaPastaPasta <6443210+pastapastapasta@users.noreply.github.com>
Co-authored-by: UdjinM6 <1935069+Udjinm6@users.noreply.github.com>
Co-authored-by: Konstantin Akimov <545784+knst@users.noreply.github.com>
This commit is contained in:
Odysseas Gabrielides 2023-02-14 20:48:33 +02:00 committed by GitHub
parent c8a7e69015
commit aa8462b060
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 1357 additions and 177 deletions

18
doc/release-notes-5039.md Normal file
View File

@ -0,0 +1,18 @@
Added RPCs
--------
The following RPCs were added: `protx register_hpmn`, `protx register_fund_hpmn`, `protx register_prepare_hpmn` and `protx update_service_hpmn`.
These HPMN RPCs correspond to the standard masternode RPCs but have the following additional mandatory arguments: `platformNodeID`, `platformP2PPort` and `platformHTTPPort`.
- `platformNodeID`: Platform P2P node ID, derived from P2P public key.
- `platformP2PPort`: TCP port of Dash Platform peer-to-peer communication between nodes (network byte order).
- `platformHTTPPort`: TCP port of Platform HTTP/API interface (network byte order).
Notes:
- `platformNodeID` must be unique across the network.
- `platformP2PPort`, `platformHTTPPort` and the Core port must be distinct.
Updated RPCs
--------
The RPC's `gobject getcurrentvotes` reply is enriched by adding the vote weight at the end of each line. Possible values are 1 or 4. Example:
`"7cb20c883c6093b8489f795b3ec0aad0d9c2c2821610ae9ed938baaf42fec66d": "277e6345359071410ab691c21a3a16f8f46c9229c2f8ec8f028c9a95c0f1c0e7-1:1670019339:yes:funding:4"`

View File

@ -165,6 +165,7 @@ BITCOIN_CORE_H = \
cuckoocache.h \
ctpl_stl.h \
cxxtimer.hpp \
evo/dmn_types.h \
evo/cbtx.h \
evo/deterministicmns.h \
evo/dmnstate.h \

View File

@ -271,6 +271,8 @@ public:
pchMessageStart[2] = 0x6b;
pchMessageStart[3] = 0xbd;
nDefaultPort = 9999;
nDefaultPlatformP2PPort = 26656;
nDefaultPlatformHTTPPort = 443;
nPruneAfterHeight = 100000;
m_assumed_blockchain_size = 45;
m_assumed_chain_state_size = 1;
@ -502,6 +504,8 @@ public:
pchMessageStart[2] = 0xca;
pchMessageStart[3] = 0xff;
nDefaultPort = 19999;
nDefaultPlatformP2PPort = 22000;
nDefaultPlatformHTTPPort = 22001;
nPruneAfterHeight = 1000;
m_assumed_blockchain_size = 4;
m_assumed_chain_state_size = 1;
@ -710,6 +714,8 @@ public:
pchMessageStart[2] = 0xff;
pchMessageStart[3] = 0xce;
nDefaultPort = 19799;
nDefaultPlatformP2PPort = 22100;
nDefaultPlatformHTTPPort = 22101;
nPruneAfterHeight = 1000;
m_assumed_blockchain_size = 0;
m_assumed_chain_state_size = 0;
@ -960,6 +966,8 @@ public:
pchMessageStart[2] = 0xb7;
pchMessageStart[3] = 0xdc;
nDefaultPort = 19899;
nDefaultPlatformP2PPort = 22200;
nDefaultPlatformHTTPPort = 22201;
nPruneAfterHeight = 1000;
m_assumed_blockchain_size = 0;
m_assumed_chain_state_size = 0;
@ -1027,10 +1035,11 @@ public:
AddLLMQ(Consensus::LLMQType::LLMQ_TEST_INSTANTSEND);
AddLLMQ(Consensus::LLMQType::LLMQ_TEST_V17);
AddLLMQ(Consensus::LLMQType::LLMQ_TEST_DIP0024);
AddLLMQ(Consensus::LLMQType::LLMQ_TEST_PLATFORM);
consensus.llmqTypeChainLocks = Consensus::LLMQType::LLMQ_TEST;
consensus.llmqTypeInstantSend = Consensus::LLMQType::LLMQ_TEST_INSTANTSEND;
consensus.llmqTypeDIP0024InstantSend = Consensus::LLMQType::LLMQ_TEST_DIP0024;
consensus.llmqTypePlatform = Consensus::LLMQType::LLMQ_TEST;
consensus.llmqTypePlatform = Consensus::LLMQType::LLMQ_TEST_PLATFORM;
consensus.llmqTypeMnhf = Consensus::LLMQType::LLMQ_TEST;
UpdateLLMQTestParametersFromArgs(args, Consensus::LLMQType::LLMQ_TEST);

View File

@ -61,6 +61,8 @@ public:
const Consensus::Params& GetConsensus() const { return consensus; }
const CMessageHeader::MessageStartChars& MessageStart() const { return pchMessageStart; }
uint16_t GetDefaultPort() const { return nDefaultPort; }
uint16_t GetDefaultPlatformP2PPort() const { return nDefaultPlatformP2PPort; }
uint16_t GetDefaultPlatformHTTPPort() const { return nDefaultPlatformHTTPPort; }
const CBlock& GenesisBlock() const { return genesis; }
const CBlock& DevNetGenesisBlock() const { return devnetGenesis; }
@ -145,6 +147,8 @@ protected:
std::vector<std::string> vSporkAddresses;
int nMinSporkKeys;
bool fBIP9CheckMasternodesUpgraded;
uint16_t nDefaultPlatformP2PPort;
uint16_t nDefaultPlatformHTTPPort;
void AddLLMQ(Consensus::LLMQType llmqType);
};

View File

@ -3,12 +3,13 @@
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <evo/deterministicmns.h>
#include <evo/dmn_types.h>
#include <evo/dmnstate.h>
#include <evo/specialtx.h>
#include <evo/providertx.h>
#include <evo/simplifiedmns.h>
#include <evo/specialtx.h>
#include <llmq/commitment.h>
#include <llmq/utils.h>
#include <evo/providertx.h>
#include <base58.h>
#include <chainparams.h>
@ -24,8 +25,8 @@
#include <memory>
static const std::string DB_LIST_SNAPSHOT = "dmn_S";
static const std::string DB_LIST_DIFF = "dmn_D";
static const std::string DB_LIST_SNAPSHOT = "dmn_S2";
static const std::string DB_LIST_DIFF = "dmn_D2";
std::unique_ptr<CDeterministicMNManager> deterministicMNManager;
@ -49,6 +50,7 @@ void CDeterministicMN::ToJson(UniValue& obj) const
UniValue stateObj;
pdmnState->ToJson(stateObj);
obj.pushKV("type", std::string(GetMnType(nType).description));
obj.pushKV("proTxHash", proTxHash.ToString());
obj.pushKV("collateralHash", collateralOutpoint.hash.ToString());
obj.pushKV("collateralIndex", (int)collateralOutpoint.n);
@ -175,15 +177,35 @@ static bool CompareByLastPaid(const CDeterministicMN* _a, const CDeterministicMN
return CompareByLastPaid(*_a, *_b);
}
CDeterministicMNCPtr CDeterministicMNList::GetMNPayee() const
CDeterministicMNCPtr CDeterministicMNList::GetMNPayee(const CBlockIndex* pIndex) const
{
if (mnMap.size() == 0) {
return nullptr;
}
CDeterministicMNCPtr best;
bool isv19Active = llmq::utils::IsV19Active(pIndex);
// Starting from v19 and until v20 (Platform release), HPMN will be rewarded 4 blocks in a row
// TODO: Skip this code once v20 is active
CDeterministicMNCPtr best = nullptr;
if (isv19Active) {
ForEachMNShared(true, [&](const CDeterministicMNCPtr& dmn) {
if (dmn->pdmnState->nLastPaidHeight == nHeight) {
// We found the last MN Payee.
// If the last payee is a HPMN, we need to check its consecutive payments and pay him again if needed
if (dmn->nType == MnType::HighPerformance.index && dmn->pdmnState->nConsecutivePayments < MnType::HighPerformance.voting_weight) {
best = dmn;
}
}
});
if (best != nullptr) return best;
// Note: If the last payee was a regular MN or if the payee is a HPMN that was removed from the mnList then that's fine.
// We can proceed with classic MN payee selection
}
ForEachMNShared(true, [&](const CDeterministicMNCPtr& dmn) {
if (!best || CompareByLastPaid(dmn.get(), best.get())) {
if (best == nullptr || CompareByLastPaid(dmn.get(), best.get())) {
best = dmn;
}
});
@ -213,9 +235,9 @@ std::vector<CDeterministicMNCPtr> CDeterministicMNList::GetProjectedMNPayees(int
return result;
}
std::vector<CDeterministicMNCPtr> CDeterministicMNList::CalculateQuorum(size_t maxSize, const uint256& modifier) const
std::vector<CDeterministicMNCPtr> CDeterministicMNList::CalculateQuorum(size_t maxSize, const uint256& modifier, const bool onlyHighPerformanceMasternodes) const
{
auto scores = CalculateScores(modifier);
auto scores = CalculateScores(modifier, onlyHighPerformanceMasternodes);
// sort is descending order
std::sort(scores.rbegin(), scores.rend(), [](const std::pair<arith_uint256, CDeterministicMNCPtr>& a, const std::pair<arith_uint256, CDeterministicMNCPtr>& b) {
@ -235,7 +257,7 @@ std::vector<CDeterministicMNCPtr> CDeterministicMNList::CalculateQuorum(size_t m
return result;
}
std::vector<std::pair<arith_uint256, CDeterministicMNCPtr>> CDeterministicMNList::CalculateScores(const uint256& modifier) const
std::vector<std::pair<arith_uint256, CDeterministicMNCPtr>> CDeterministicMNList::CalculateScores(const uint256& modifier, const bool onlyHighPerformanceMasternodes) const
{
std::vector<std::pair<arith_uint256, CDeterministicMNCPtr>> scores;
scores.reserve(GetAllMNsCount());
@ -245,6 +267,10 @@ std::vector<std::pair<arith_uint256, CDeterministicMNCPtr>> CDeterministicMNList
// future quorums
return;
}
if (onlyHighPerformanceMasternodes) {
if (dmn->nType != MnType::HighPerformance.index)
return;
}
// calculate sha256(sha256(proTxHash, confirmedHash), modifier) per MN
// Please note that this is not a double-sha256 but a single-sha256
// The first part is already precalculated (confirmedHashWithProRegTxHash)
@ -351,10 +377,11 @@ CDeterministicMNListDiff CDeterministicMNList::BuildDiff(const CDeterministicMNL
CSimplifiedMNListDiff CDeterministicMNList::BuildSimplifiedDiff(const CDeterministicMNList& to, bool extended) const
{
bool v19active = llmq::utils::IsV19Active(::ChainActive().Tip());
CSimplifiedMNListDiff diffRet;
diffRet.baseBlockHash = blockHash;
diffRet.blockHash = to.blockHash;
diffRet.nVersion = llmq::utils::IsV19Active(::ChainActive().Tip()) ? CSimplifiedMNListDiff::BASIC_BLS_VERSION : CSimplifiedMNListDiff::LEGACY_BLS_VERSION;
diffRet.nVersion = v19active ? CSimplifiedMNListDiff::BASIC_BLS_VERSION : CSimplifiedMNListDiff::LEGACY_BLS_VERSION;
to.ForEachMN(false, [&](const auto& toPtr) {
auto fromPtr = GetMN(toPtr.proTxHash);
@ -444,6 +471,14 @@ void CDeterministicMNList::AddMN(const CDeterministicMNCPtr& dmn, bool fBumpTota
dmn->proTxHash.ToString(), dmn->pdmnState->pubKeyOperator.Get().ToString())));
}
if (dmn->nType == MnType::HighPerformance.index) {
if (!AddUniqueProperty(*dmn, dmn->pdmnState->platformNodeID)) {
mnUniquePropertyMap = mnUniquePropertyMapSaved;
throw(std::runtime_error(strprintf("%s: Can't add a masternode %s with a duplicate platformNodeID=%s", __func__,
dmn->proTxHash.ToString(), dmn->pdmnState->platformNodeID.ToString())));
}
}
mnMap = mnMap.set(dmn->proTxHash, dmn);
mnInternalIdMap = mnInternalIdMap.set(dmn->GetInternalId(), dmn->proTxHash);
if (fBumpTotalCount) {
@ -477,6 +512,13 @@ void CDeterministicMNList::UpdateMN(const CDeterministicMN& oldDmn, const std::s
throw(std::runtime_error(strprintf("%s: Can't update a masternode %s with a duplicate pubKeyOperator=%s", __func__,
oldDmn.proTxHash.ToString(), pdmnState->pubKeyOperator.Get().ToString())));
}
if (dmn->nType == MnType::HighPerformance.index) {
if (!UpdateUniqueProperty(*dmn, oldState->platformNodeID, dmn->pdmnState->platformNodeID)) {
mnUniquePropertyMap = mnUniquePropertyMapSaved;
throw(std::runtime_error(strprintf("%s: Can't update a masternode %s with a duplicate platformNodeID=%s", __func__,
dmn->proTxHash.ToString(), dmn->pdmnState->platformNodeID.ToString())));
}
}
mnMap = mnMap.set(oldDmn.proTxHash, dmn);
}
@ -530,6 +572,14 @@ void CDeterministicMNList::RemoveMN(const uint256& proTxHash)
proTxHash.ToString(), dmn->pdmnState->pubKeyOperator.Get().ToString())));
}
if (dmn->nType == MnType::HighPerformance.index) {
if (!DeleteUniqueProperty(*dmn, dmn->pdmnState->platformNodeID)) {
mnUniquePropertyMap = mnUniquePropertyMapSaved;
throw(std::runtime_error(strprintf("%s: Can't delete a masternode %s with a duplicate platformNodeID=%s", __func__,
dmn->proTxHash.ToString(), dmn->pdmnState->platformNodeID.ToString())));
}
}
mnMap = mnMap.erase(proTxHash);
mnInternalIdMap = mnInternalIdMap.erase(dmn->GetInternalId());
}
@ -658,7 +708,7 @@ bool CDeterministicMNManager::BuildNewListFromBlock(const CBlock& block, const C
newList.SetBlockHash(uint256()); // we can't know the final block hash, so better not return a (invalid) block hash
newList.SetHeight(nHeight);
auto payee = oldList.GetMNPayee();
auto payee = oldList.GetMNPayee(pindexPrev);
// we iterate the oldList here and update the newList
// this is only valid as long these have not diverged at this point, which is the case as long as we don't add
@ -695,7 +745,11 @@ bool CDeterministicMNManager::BuildNewListFromBlock(const CBlock& block, const C
return _state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-protx-payload");
}
auto dmn = std::make_shared<CDeterministicMN>(newList.GetTotalRegisteredCount());
if (proTx.nType == MnType::HighPerformance.index && !llmq::utils::IsV19Active(pindexPrev)) {
return _state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-protx-payload");
}
auto dmn = std::make_shared<CDeterministicMN>(newList.GetTotalRegisteredCount(), proTx.nType == MnType::HighPerformance.index);
dmn->proTxHash = tx.GetHash();
// collateralOutpoint is either pointing to an external collateral or to the ProRegTx itself
@ -706,7 +760,8 @@ bool CDeterministicMNManager::BuildNewListFromBlock(const CBlock& block, const C
}
Coin coin;
if (!proTx.collateralOutpoint.hash.IsNull() && (!view.GetCoin(dmn->collateralOutpoint, coin) || coin.IsSpent() || coin.out.nValue != 1000 * COIN)) {
CAmount expectedCollateral = GetMnType(proTx.nType).collat_amount;
if (!proTx.collateralOutpoint.hash.IsNull() && (!view.GetCoin(dmn->collateralOutpoint, coin) || coin.IsSpent() || coin.out.nValue != expectedCollateral)) {
// should actually never get to this point as CheckProRegTx should have handled this case.
// We do this additional check nevertheless to be 100% sure
return _state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-protx-collateral");
@ -753,6 +808,10 @@ bool CDeterministicMNManager::BuildNewListFromBlock(const CBlock& block, const C
return _state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-protx-payload");
}
if (proTx.nType == MnType::HighPerformance.index && !llmq::utils::IsV19Active(pindexPrev)) {
return _state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-protx-payload");
}
if (newList.HasUniqueProperty(proTx.addr) && newList.GetUniquePropertyMN(proTx.addr)->proTxHash != proTx.proTxHash) {
return _state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_DUPLICATE, "bad-protx-dup-addr");
}
@ -761,10 +820,21 @@ bool CDeterministicMNManager::BuildNewListFromBlock(const CBlock& block, const C
if (!dmn) {
return _state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-protx-hash");
}
if (proTx.nType == MnType::HighPerformance.index && dmn->nType != MnType::HighPerformance.index) {
return _state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-protx-type");
}
if (proTx.nType == MnType::Regular.index && dmn->nType != MnType::Regular.index) {
return _state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-protx-type");
}
auto newState = std::make_shared<CDeterministicMNState>(*dmn->pdmnState);
newState->addr = proTx.addr;
newState->scriptOperatorPayout = proTx.scriptOperatorPayout;
if (proTx.nType == MnType::HighPerformance.index) {
newState->platformNodeID = proTx.platformNodeID;
newState->platformP2PPort = proTx.platformP2PPort;
newState->platformHTTPPort = proTx.platformHTTPPort;
}
if (newState->IsBanned()) {
// only revive when all keys are set
if (newState->pubKeyOperator.Get().IsValid() && !newState->keyIDVoting.IsNull() && !newState->keyIDOwner.IsNull()) {
@ -869,11 +939,43 @@ bool CDeterministicMNManager::BuildNewListFromBlock(const CBlock& block, const C
// The payee for the current block was determined by the previous block's list, but it might have disappeared in the
// current block. We still pay that MN one last time, however.
if (payee && newList.HasMN(payee->proTxHash)) {
auto newState = std::make_shared<CDeterministicMNState>(*newList.GetMN(payee->proTxHash)->pdmnState);
auto dmn = newList.GetMN(payee->proTxHash);
auto newState = std::make_shared<CDeterministicMNState>(*dmn->pdmnState);
newState->nLastPaidHeight = nHeight;
// Starting from v19 and until v20, HPMN will be paid 4 blocks in a row
// No need to check if v19 is active, since HPMN ProRegTx are allowed only after v19 activation
// TODO: Skip this code once v20 is active
// Note: If the payee wasn't found in the current block that's fine
if (dmn->nType == MnType::HighPerformance.index) {
++newState->nConsecutivePayments;
if (debugLogs) {
LogPrintf("CDeterministicMNManager::%s -- MN %s is a HPMN, bumping nConsecutivePayments to %d\n",
__func__, dmn->proTxHash.ToString(), newState->nConsecutivePayments);
}
}
newList.UpdateMN(payee->proTxHash, newState);
if (debugLogs) {
dmn = newList.GetMN(payee->proTxHash);
LogPrintf("CDeterministicMNManager::%s -- MN %s, nConsecutivePayments=%d\n",
__func__, dmn->proTxHash.ToString(), dmn->pdmnState->nConsecutivePayments);
}
}
// reset nConsecutivePayments on non-paid HPMNs
auto newList2 = newList;
newList2.ForEachMN(false, [&](auto& dmn) {
if (payee != nullptr && dmn.proTxHash == payee->proTxHash) return;
if (dmn.nType != MnType::HighPerformance.index) return;
if (dmn.pdmnState->nConsecutivePayments == 0) return;
if (debugLogs) {
LogPrintf("CDeterministicMNManager::%s -- MN %s, reset nConsecutivePayments %d->0\n",
__func__, dmn.proTxHash.ToString(), dmn.pdmnState->nConsecutivePayments);
}
auto newState = std::make_shared<CDeterministicMNState>(*dmn.pdmnState);
newState->nConsecutivePayments = 0;
newList.UpdateMN(dmn.proTxHash, newState);
});
mnListRet = std::move(newList);
return true;
@ -1013,7 +1115,10 @@ bool CDeterministicMNManager::IsProTxWithCollateral(const CTransactionRef& tx, u
if (proTx.collateralOutpoint.n >= tx->vout.size() || proTx.collateralOutpoint.n != n) {
return false;
}
if (tx->vout[n].nValue != 1000 * COIN) {
const CAmount expectedCollateral = GetMnType(proTx.nType).collat_amount;
if (tx->vout[n].nValue != expectedCollateral) {
return false;
}
return true;
@ -1074,6 +1179,110 @@ void CDeterministicMNManager::CleanupCache(int nHeight)
}
}
void CDeterministicMNManager::MigrateDiff(CDBBatch& batch, const CBlockIndex* pindexNext, const CDeterministicMNList& curMNList, CDeterministicMNList& newMNList)
{
static const std::string DB_OLD_LIST_DIFF = "dmn_D";
CDataStream diff_data(SER_DISK, CLIENT_VERSION);
if (!m_evoDb.GetRawDB().ReadDataStream(std::make_pair(DB_OLD_LIST_DIFF, pindexNext->GetBlockHash()), diff_data)) {
newMNList = curMNList;
newMNList.SetBlockHash(pindexNext->GetBlockHash());
newMNList.SetHeight(pindexNext->nHeight);
return;
}
CDeterministicMNListDiff mndiff;
mndiff.Unserialize(diff_data, CDeterministicMN::CURRENT_MN_FORMAT);
// At this point, all masternodes included in the diff are set as regular masternodes by default.
// We can then write them in evoDB as the new serialisation format includes the type
batch.Write(std::make_pair(DB_LIST_DIFF, pindexNext->GetBlockHash()), mndiff);
// applies added/removed MNs
newMNList = curMNList.ApplyDiff(pindexNext, mndiff);
}
bool CDeterministicMNManager::MigrateDBIfNeeded()
{
static const std::string DB_OLD_LIST_SNAPSHOT = "dmn_S";
static const std::string DB_OLD_LIST_DIFF = "dmn_D";
static const std::string DB_OLD_BEST_BLOCK = "b_b2";
LOCK(cs_main);
LogPrintf("CDeterministicMNManager::%s -- upgrading DB to migrate MN type\n", __func__);
if (::ChainActive().Tip() == nullptr) {
// should have no records
LogPrintf("CDeterministicMNManager::%s -- Chain empty. evoDB:%d.\n", __func__, m_evoDb.IsEmpty());
return m_evoDb.IsEmpty();
}
if (m_evoDb.GetRawDB().Exists(EVODB_BEST_BLOCK)) {
LogPrintf("CDeterministicMNManager::%s -- migration already done. skipping.\n", __func__);
return true;
}
// Removing the old EVODB_BEST_BLOCK value early results in older version to crash immediately, even if the upgrade
// process is cancelled in-between. But if the new version sees that the old EVODB_BEST_BLOCK is already removed,
// then we must assume that the upgrade process was already running before but was interrupted.
if (::ChainActive().Height() > 1 && !m_evoDb.GetRawDB().Exists(DB_OLD_BEST_BLOCK)) {
LogPrintf("CDeterministicMNManager::%s -- previous migration attempt failed.\n", __func__);
return false;
}
m_evoDb.GetRawDB().Erase(DB_OLD_BEST_BLOCK);
if (::ChainActive().Height() < Params().GetConsensus().DIP0003Height) {
// not reached DIP3 height yet, so no upgrade needed
LogPrintf("CDeterministicMNManager::%s -- migration not needed. dip3 not reached\n", __func__);
auto dbTx = m_evoDb.BeginTransaction();
m_evoDb.WriteBestBlock(::ChainActive().Tip()->GetBlockHash());
dbTx->Commit();
return true;
}
CDBBatch batch(m_evoDb.GetRawDB());
CDeterministicMNList curMNList;
curMNList.SetHeight(Params().GetConsensus().DIP0003Height - 1);
curMNList.SetBlockHash(::ChainActive()[Params().GetConsensus().DIP0003Height - 1]->GetBlockHash());
for (const auto nHeight : irange::range(Params().GetConsensus().DIP0003Height, ::ChainActive().Height() + 1)) {
auto pindex = ::ChainActive()[nHeight];
CDeterministicMNList newMNList;
MigrateDiff(batch, pindex, curMNList, newMNList);
if ((nHeight % DISK_SNAPSHOT_PERIOD) == 0) {
batch.Write(std::make_pair(DB_LIST_SNAPSHOT, pindex->GetBlockHash()), newMNList);
m_evoDb.GetRawDB().WriteBatch(batch);
batch.Clear();
}
curMNList = newMNList;
}
m_evoDb.GetRawDB().WriteBatch(batch);
// Writing EVODB_BEST_BLOCK (which is b_b3 now) marks the DB as upgraded
auto dbTx = m_evoDb.BeginTransaction();
m_evoDb.WriteBestBlock(::ChainActive().Tip()->GetBlockHash());
dbTx->Commit();
LogPrintf("CDeterministicMNManager::%s -- done migrating\n", __func__);
m_evoDb.GetRawDB().Erase(DB_OLD_LIST_DIFF);
m_evoDb.GetRawDB().Erase(DB_OLD_LIST_SNAPSHOT);
LogPrintf("CDeterministicMNManager::%s -- done cleaning old data\n", __func__);
m_evoDb.GetRawDB().CompactFull();
LogPrintf("CDeterministicMNManager::%s -- done compacting database\n", __func__);
return true;
}
template <typename ProTx>
static bool CheckService(const ProTx& proTx, CValidationState& state)
@ -1101,6 +1310,44 @@ static bool CheckService(const ProTx& proTx, CValidationState& state)
return true;
}
template <typename ProTx>
static bool CheckPlatformFields(const ProTx& proTx, CValidationState& state)
{
if (proTx.platformNodeID.IsNull()) {
return state.Invalid(ValidationInvalidReason::TX_BAD_SPECIAL, false, REJECT_INVALID, "bad-protx-platform-nodeid");
}
static int mainnetPlatformP2PPort = CreateChainParams(CBaseChainParams::MAIN)->GetDefaultPlatformP2PPort();
if (Params().NetworkIDString() == CBaseChainParams::MAIN) {
if (proTx.platformP2PPort != mainnetPlatformP2PPort) {
return state.Invalid(ValidationInvalidReason::TX_BAD_SPECIAL, false, REJECT_INVALID, "bad-protx-platform-p2p-port");
}
}
static int mainnetPlatformHTTPPort = CreateChainParams(CBaseChainParams::MAIN)->GetDefaultPlatformHTTPPort();
if (Params().NetworkIDString() == CBaseChainParams::MAIN) {
if (proTx.platformHTTPPort != mainnetPlatformHTTPPort) {
return state.Invalid(ValidationInvalidReason::TX_BAD_SPECIAL, false, REJECT_INVALID, "bad-protx-platform-http-port");
}
}
static int mainnetDefaultP2PPort = CreateChainParams(CBaseChainParams::MAIN)->GetDefaultPort();
if (proTx.platformP2PPort == mainnetDefaultP2PPort) {
return state.Invalid(ValidationInvalidReason::TX_BAD_SPECIAL, false, REJECT_INVALID, "bad-protx-platform-p2p-port");
}
if (proTx.platformHTTPPort == mainnetDefaultP2PPort) {
return state.Invalid(ValidationInvalidReason::TX_BAD_SPECIAL, false, REJECT_INVALID, "bad-protx-platform-http-port");
}
if (proTx.platformP2PPort == proTx.platformHTTPPort ||
proTx.platformP2PPort == proTx.addr.GetPort() ||
proTx.platformHTTPPort == proTx.addr.GetPort()) {
return state.Invalid(ValidationInvalidReason::TX_BAD_SPECIAL, false, REJECT_INVALID, "bad-protx-platform-dup-ports");
}
return true;
}
template <typename ProTx>
static bool CheckHashSig(const ProTx& proTx, const PKHash& pkhash, CValidationState& state)
{
@ -1152,13 +1399,21 @@ bool CheckProRegTx(const CTransaction& tx, const CBlockIndex* pindexPrev, CValid
return false;
}
if (ptx.nType == MnType::HighPerformance.index) {
if (!CheckPlatformFields(ptx, state)) {
return false;
}
}
CTxDestination collateralTxDest;
const PKHash *keyForPayloadSig = nullptr;
COutPoint collateralOutpoint;
CAmount expectedCollateral = GetMnType(ptx.nType).collat_amount;
if (!ptx.collateralOutpoint.hash.IsNull()) {
Coin coin;
if (!view.GetCoin(ptx.collateralOutpoint, coin) || coin.IsSpent() || coin.out.nValue != 1000 * COIN) {
if (!view.GetCoin(ptx.collateralOutpoint, coin) || coin.IsSpent() || coin.out.nValue != expectedCollateral) {
return state.Invalid(ValidationInvalidReason::TX_BAD_SPECIAL, false, REJECT_INVALID, "bad-protx-collateral");
}
@ -1178,7 +1433,7 @@ bool CheckProRegTx(const CTransaction& tx, const CBlockIndex* pindexPrev, CValid
if (ptx.collateralOutpoint.n >= tx.vout.size()) {
return state.Invalid(ValidationInvalidReason::TX_BAD_SPECIAL, false, REJECT_INVALID, "bad-protx-collateral-index");
}
if (tx.vout[ptx.collateralOutpoint.n].nValue != 1000 * COIN) {
if (tx.vout[ptx.collateralOutpoint.n].nValue != expectedCollateral) {
return state.Invalid(ValidationInvalidReason::TX_BAD_SPECIAL, false, REJECT_INVALID, "bad-protx-collateral");
}
@ -1255,6 +1510,12 @@ bool CheckProUpServTx(const CTransaction& tx, const CBlockIndex* pindexPrev, CVa
return false;
}
if (ptx.nType == MnType::HighPerformance.index) {
if (!CheckPlatformFields(ptx, state)) {
return false;
}
}
if (pindexPrev) {
auto mnList = deterministicMNManager->GetListForBlock(pindexPrev);
auto mn = mnList.GetMN(ptx.proTxHash);

View File

@ -8,8 +8,9 @@
#include <evo/dmnstate.h>
#include <arith_uint256.h>
#include <crypto/common.h>
#include <consensus/params.h>
#include <crypto/common.h>
#include <evo/dmn_types.h>
#include <evo/evodb.h>
#include <evo/providertx.h>
#include <saltedhasher.h>
@ -40,18 +41,17 @@ private:
uint64_t internalId{std::numeric_limits<uint64_t>::max()};
public:
static constexpr uint16_t CURRENT_MN_FORMAT = 0;
static constexpr uint16_t MN_TYPE_FORMAT = 1;
CDeterministicMN() = delete; // no default constructor, must specify internalId
explicit CDeterministicMN(uint64_t _internalId) : internalId(_internalId)
explicit CDeterministicMN(uint64_t _internalId, bool highPerformanceMasternode = false) :
internalId(_internalId),
nType(highPerformanceMasternode ? MnType::HighPerformance.index : MnType::Regular.index)
{
// only non-initial values
assert(_internalId != std::numeric_limits<uint64_t>::max());
}
// TODO: can be removed in a future version
CDeterministicMN(CDeterministicMN mn, uint64_t _internalId) : CDeterministicMN(std::move(mn)) {
// only non-initial values
assert(_internalId != std::numeric_limits<uint64_t>::max());
internalId = _internalId;
}
template <typename Stream>
CDeterministicMN(deserialize_type, Stream& s)
@ -62,30 +62,47 @@ public:
uint256 proTxHash;
COutPoint collateralOutpoint;
uint16_t nOperatorReward{0};
uint16_t nType{MnType::Regular.index};
std::shared_ptr<const CDeterministicMNState> pdmnState;
template <typename Stream, typename Operation>
inline void SerializationOp(Stream& s, Operation ser_action, bool oldFormat)
inline void SerializationOp(Stream& s, Operation ser_action, const uint8_t format_version)
{
READWRITE(proTxHash);
if (!oldFormat) {
READWRITE(VARINT(internalId));
}
READWRITE(VARINT(internalId));
READWRITE(collateralOutpoint);
READWRITE(nOperatorReward);
READWRITE(pdmnState);
// We need to read CDeterministicMNState using the old format only when called with CURRENT_MN_FORMAT on Unserialize()
// Serialisation (writing) will be done always using new format
if (ser_action.ForRead() && format_version == CURRENT_MN_FORMAT) {
CDeterministicMNState_Oldformat old_state;
READWRITE(old_state);
pdmnState = std::make_shared<const CDeterministicMNState>(old_state);
} else {
READWRITE(pdmnState);
}
// We need to read/write nType if:
// format_version is set to MN_TYPE_FORMAT (For writing (serialisation) it is always the case) Needed for the MNLISTDIFF Migration in evoDB
// We can't know if we are serialising for the Disk or for the Network here (s.GetType() is not accessible)
// Therefore if s.GetVersion() == CLIENT_VERSION -> Then we know we are serialising for the Disk
// Otherwise, we can safely check with protocol versioning logic so we won't break old clients
if (format_version >= MN_TYPE_FORMAT && (s.GetVersion() == CLIENT_VERSION || s.GetVersion() >= DMN_TYPE_PROTO_VERSION)) {
READWRITE(nType);
} else {
nType = MnType::Regular.index;
}
}
template<typename Stream>
void Serialize(Stream& s) const
{
const_cast<CDeterministicMN*>(this)->SerializationOp(s, CSerActionSerialize(), false);
const_cast<CDeterministicMN*>(this)->SerializationOp(s, CSerActionSerialize(), MN_TYPE_FORMAT);
}
template<typename Stream>
void Unserialize(Stream& s, bool oldFormat = false)
template <typename Stream>
void Unserialize(Stream& s, const uint8_t format_version = MN_TYPE_FORMAT)
{
SerializationOp(s, CSerActionUnserialize(), oldFormat);
SerializationOp(s, CSerActionUnserialize(), format_version);
}
[[nodiscard]] uint64_t GetInternalId() const;
@ -208,6 +225,11 @@ public:
return ranges::count_if(mnMap, [](const auto& p){ return IsMNValid(*p.second); });
}
[[nodiscard]] size_t GetAllHPMNsCount() const
{
return ranges::count_if(mnMap, [](const auto& p) { return p.second->nType == MnType::HighPerformance.index; });
}
/**
* Execute a callback on all masternodes in the mnList. This will pass a reference
* of each masternode to the callback function. This should be preferred over ForEachMNShared.
@ -286,7 +308,7 @@ public:
[[nodiscard]] CDeterministicMNCPtr GetValidMNByCollateral(const COutPoint& collateralOutpoint) const;
[[nodiscard]] CDeterministicMNCPtr GetMNByService(const CService& service) const;
[[nodiscard]] CDeterministicMNCPtr GetMNByInternalId(uint64_t internalId) const;
[[nodiscard]] CDeterministicMNCPtr GetMNPayee() const;
[[nodiscard]] CDeterministicMNCPtr GetMNPayee(const CBlockIndex* pIndex) const;
/**
* Calculates the projected MN payees for the next *count* blocks. The result is not guaranteed to be correct
@ -302,8 +324,8 @@ public:
* @param modifier
* @return
*/
[[nodiscard]] std::vector<CDeterministicMNCPtr> CalculateQuorum(size_t maxSize, const uint256& modifier) const;
[[nodiscard]] std::vector<std::pair<arith_uint256, CDeterministicMNCPtr>> CalculateScores(const uint256& modifier) const;
[[nodiscard]] std::vector<CDeterministicMNCPtr> CalculateQuorum(size_t maxSize, const uint256& modifier, const bool onlyHighPerformanceMasternodes = false) const;
[[nodiscard]] std::vector<std::pair<arith_uint256, CDeterministicMNCPtr>> CalculateScores(const uint256& modifier, const bool onlyHighPerformanceMasternodes) const;
/**
* Calculates the maximum penalty which is allowed at the height of this MN list. It is dynamic and might change
@ -448,18 +470,29 @@ public:
}
}
template<typename Stream>
void Unserialize(Stream& s)
template <typename Stream>
void Unserialize(Stream& s, const uint8_t format_version = CDeterministicMN::MN_TYPE_FORMAT)
{
updatedMNs.clear();
removedMns.clear();
size_t tmp;
uint64_t tmp2;
s >> addedMNs;
tmp = ReadCompactSize(s);
for (size_t i = 0; i < tmp; i++) {
CDeterministicMN mn(0);
// Unserialise CDeterministicMN using CURRENT_MN_FORMAT and set it's type to the default value TYPE_REGULAR_MASTERNODE
// It will be later written with format MN_TYPE_FORMAT which includes the type field.
mn.Unserialize(s, format_version);
auto dmn = std::make_shared<CDeterministicMN>(mn);
addedMNs.push_back(dmn);
}
tmp = ReadCompactSize(s);
for (size_t i = 0; i < tmp; i++) {
CDeterministicMNStateDiff diff;
// CDeterministicMNState hold new fields {nConsecutivePayments, platformNodeID, platformP2PPort, platformHTTPPort} but no migration is needed here since:
// CDeterministicMNStateDiff is always serialised using a bitmask.
// Because the new field have a new bit guide value then we are good to continue
tmp2 = ReadVarInt<Stream, VarIntMode::DEFAULT, uint64_t>(s);
s >> diff;
updatedMNs.emplace(tmp2, std::move(diff));
@ -527,6 +560,8 @@ public:
bool IsDIP3Enforced(int nHeight = -1);
void MigrateDiff(CDBBatch& batch, const CBlockIndex* pindexNext, const CDeterministicMNList& curMNList, CDeterministicMNList& newMNList);
bool MigrateDBIfNeeded();
void DoMaintenance();

45
src/evo/dmn_types.h Normal file
View File

@ -0,0 +1,45 @@
// Copyright (c) 2023 The Dash Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#ifndef BITCOIN_EVO_DMN_TYPES_H
#define BITCOIN_EVO_DMN_TYPES_H
#include <amount.h>
#include <cassert>
#include <string_view>
class CDeterministicMNType
{
public:
uint8_t index;
int32_t voting_weight;
CAmount collat_amount;
std::string_view description;
};
namespace MnType {
constexpr auto Regular = CDeterministicMNType{
.index = 0,
.voting_weight = 1,
.collat_amount = 1000 * COIN,
.description = "Regular",
};
constexpr auto HighPerformance = CDeterministicMNType{
.index = 1,
.voting_weight = 4,
.collat_amount = 4000 * COIN,
.description = "HighPerformance",
};
} // namespace MnType
constexpr const auto& GetMnType(int index)
{
switch (index) {
case 0: return MnType::Regular;
case 1: return MnType::HighPerformance;
default: assert(false);
}
}
#endif // BITCOIN_EVO_DMN_TYPES_H

View File

@ -39,6 +39,7 @@ void CDeterministicMNState::ToJson(UniValue& obj) const
obj.pushKV("service", addr.ToStringIPPort(false));
obj.pushKV("registeredHeight", nRegisteredHeight);
obj.pushKV("lastPaidHeight", nLastPaidHeight);
obj.pushKV("consecutivePayments", nConsecutivePayments);
obj.pushKV("PoSePenalty", nPoSePenalty);
obj.pushKV("PoSeRevivedHeight", nPoSeRevivedHeight);
obj.pushKV("PoSeBanHeight", nPoSeBanHeight);

View File

@ -25,6 +25,53 @@ namespace llmq
class CFinalCommitment;
} // namespace llmq
// TODO: To remove this in the future
class CDeterministicMNState_Oldformat
{
private:
int nPoSeBanHeight{-1};
friend class CDeterministicMNStateDiff;
friend class CDeterministicMNState;
public:
int nRegisteredHeight{-1};
int nLastPaidHeight{0};
int nPoSePenalty{0};
int nPoSeRevivedHeight{-1};
uint16_t nRevocationReason{CProUpRevTx::REASON_NOT_SPECIFIED};
uint256 confirmedHash;
uint256 confirmedHashWithProRegTxHash;
CKeyID keyIDOwner;
CBLSLazyPublicKey pubKeyOperator;
CKeyID keyIDVoting;
CService addr;
CScript scriptPayout;
CScript scriptOperatorPayout;
public:
CDeterministicMNState_Oldformat() = default;
SERIALIZE_METHODS(CDeterministicMNState_Oldformat, obj)
{
READWRITE(
obj.nRegisteredHeight,
obj.nLastPaidHeight,
obj.nPoSePenalty,
obj.nPoSeRevivedHeight,
obj.nPoSeBanHeight,
obj.nRevocationReason,
obj.confirmedHash,
obj.confirmedHashWithProRegTxHash,
obj.keyIDOwner,
obj.pubKeyOperator,
obj.keyIDVoting,
obj.addr,
obj.scriptPayout,
obj.scriptOperatorPayout);
}
};
class CDeterministicMNState
{
private:
@ -35,6 +82,7 @@ private:
public:
int nRegisteredHeight{-1};
int nLastPaidHeight{0};
int nConsecutivePayments{0};
int nPoSePenalty{0};
int nPoSeRevivedHeight{-1};
uint16_t nRevocationReason{CProUpRevTx::REASON_NOT_SPECIFIED};
@ -52,16 +100,38 @@ public:
CScript scriptPayout;
CScript scriptOperatorPayout;
uint160 platformNodeID{};
uint16_t platformP2PPort{0};
uint16_t platformHTTPPort{0};
public:
CDeterministicMNState() = default;
explicit CDeterministicMNState(const CProRegTx& proTx) :
keyIDOwner(proTx.keyIDOwner),
keyIDVoting(proTx.keyIDVoting),
addr(proTx.addr),
scriptPayout(proTx.scriptPayout)
keyIDOwner(proTx.keyIDOwner),
keyIDVoting(proTx.keyIDVoting),
addr(proTx.addr),
scriptPayout(proTx.scriptPayout),
platformNodeID(proTx.platformNodeID),
platformP2PPort(proTx.platformP2PPort),
platformHTTPPort(proTx.platformHTTPPort)
{
pubKeyOperator.Set(proTx.pubKeyOperator);
}
explicit CDeterministicMNState(const CDeterministicMNState_Oldformat& s) :
nPoSeBanHeight(s.nPoSeBanHeight),
nRegisteredHeight(s.nRegisteredHeight),
nLastPaidHeight(s.nLastPaidHeight),
nPoSePenalty(s.nPoSePenalty),
nPoSeRevivedHeight(s.nPoSeRevivedHeight),
nRevocationReason(s.nRevocationReason),
confirmedHash(s.confirmedHash),
confirmedHashWithProRegTxHash(s.confirmedHashWithProRegTxHash),
keyIDOwner(s.keyIDOwner),
pubKeyOperator(s.pubKeyOperator),
keyIDVoting(s.keyIDVoting),
addr(s.addr),
scriptPayout(s.scriptPayout),
scriptOperatorPayout(s.scriptOperatorPayout) {}
template <typename Stream>
CDeterministicMNState(deserialize_type, Stream& s)
{
@ -71,21 +141,24 @@ public:
SERIALIZE_METHODS(CDeterministicMNState, obj)
{
READWRITE(
obj.nRegisteredHeight,
obj.nLastPaidHeight,
obj.nPoSePenalty,
obj.nPoSeRevivedHeight,
obj.nPoSeBanHeight,
obj.nRevocationReason,
obj.confirmedHash,
obj.confirmedHashWithProRegTxHash,
obj.keyIDOwner,
obj.pubKeyOperator,
obj.keyIDVoting,
obj.addr,
obj.scriptPayout,
obj.scriptOperatorPayout
);
obj.nRegisteredHeight,
obj.nLastPaidHeight,
obj.nConsecutivePayments,
obj.nPoSePenalty,
obj.nPoSeRevivedHeight,
obj.nPoSeBanHeight,
obj.nRevocationReason,
obj.confirmedHash,
obj.confirmedHashWithProRegTxHash,
obj.keyIDOwner,
obj.pubKeyOperator,
obj.keyIDVoting,
obj.addr,
obj.scriptPayout,
obj.scriptOperatorPayout,
obj.platformNodeID,
obj.platformP2PPort,
obj.platformHTTPPort);
}
void ResetOperatorFields()
@ -94,6 +167,7 @@ public:
addr = CService();
scriptOperatorPayout = CScript();
nRevocationReason = CProUpRevTx::REASON_NOT_SPECIFIED;
platformNodeID = uint160();
}
void BanIfNotBanned(int height)
{
@ -133,37 +207,45 @@ class CDeterministicMNStateDiff
{
public:
enum Field : uint32_t {
Field_nRegisteredHeight = 0x0001,
Field_nLastPaidHeight = 0x0002,
Field_nPoSePenalty = 0x0004,
Field_nPoSeRevivedHeight = 0x0008,
Field_nPoSeBanHeight = 0x0010,
Field_nRevocationReason = 0x0020,
Field_confirmedHash = 0x0040,
Field_confirmedHashWithProRegTxHash = 0x0080,
Field_keyIDOwner = 0x0100,
Field_pubKeyOperator = 0x0200,
Field_keyIDVoting = 0x0400,
Field_addr = 0x0800,
Field_scriptPayout = 0x1000,
Field_scriptOperatorPayout = 0x2000,
Field_nRegisteredHeight = 0x0001,
Field_nLastPaidHeight = 0x0002,
Field_nPoSePenalty = 0x0004,
Field_nPoSeRevivedHeight = 0x0008,
Field_nPoSeBanHeight = 0x0010,
Field_nRevocationReason = 0x0020,
Field_confirmedHash = 0x0040,
Field_confirmedHashWithProRegTxHash = 0x0080,
Field_keyIDOwner = 0x0100,
Field_pubKeyOperator = 0x0200,
Field_keyIDVoting = 0x0400,
Field_addr = 0x0800,
Field_scriptPayout = 0x1000,
Field_scriptOperatorPayout = 0x2000,
Field_nConsecutivePayments = 0x4000,
Field_platformNodeID = 0x8000,
Field_platformP2PPort = 0x10000,
Field_platformHTTPPort = 0x20000,
};
#define DMN_STATE_DIFF_ALL_FIELDS \
DMN_STATE_DIFF_LINE(nRegisteredHeight) \
DMN_STATE_DIFF_LINE(nLastPaidHeight) \
DMN_STATE_DIFF_LINE(nPoSePenalty) \
DMN_STATE_DIFF_LINE(nPoSeRevivedHeight) \
DMN_STATE_DIFF_LINE(nPoSeBanHeight) \
DMN_STATE_DIFF_LINE(nRevocationReason) \
DMN_STATE_DIFF_LINE(confirmedHash) \
#define DMN_STATE_DIFF_ALL_FIELDS \
DMN_STATE_DIFF_LINE(nRegisteredHeight) \
DMN_STATE_DIFF_LINE(nLastPaidHeight) \
DMN_STATE_DIFF_LINE(nPoSePenalty) \
DMN_STATE_DIFF_LINE(nPoSeRevivedHeight) \
DMN_STATE_DIFF_LINE(nPoSeBanHeight) \
DMN_STATE_DIFF_LINE(nRevocationReason) \
DMN_STATE_DIFF_LINE(confirmedHash) \
DMN_STATE_DIFF_LINE(confirmedHashWithProRegTxHash) \
DMN_STATE_DIFF_LINE(keyIDOwner) \
DMN_STATE_DIFF_LINE(pubKeyOperator) \
DMN_STATE_DIFF_LINE(keyIDVoting) \
DMN_STATE_DIFF_LINE(addr) \
DMN_STATE_DIFF_LINE(scriptPayout) \
DMN_STATE_DIFF_LINE(scriptOperatorPayout)
DMN_STATE_DIFF_LINE(keyIDOwner) \
DMN_STATE_DIFF_LINE(pubKeyOperator) \
DMN_STATE_DIFF_LINE(keyIDVoting) \
DMN_STATE_DIFF_LINE(addr) \
DMN_STATE_DIFF_LINE(scriptPayout) \
DMN_STATE_DIFF_LINE(scriptOperatorPayout) \
DMN_STATE_DIFF_LINE(nConsecutivePayments) \
DMN_STATE_DIFF_LINE(platformNodeID) \
DMN_STATE_DIFF_LINE(platformP2PPort) \
DMN_STATE_DIFF_LINE(platformHTTPPort)
public:
uint32_t fields{0};

View File

@ -11,7 +11,8 @@
// "b_b" was used in the initial version of deterministic MN storage
// "b_b2" was used after compact diffs were introduced
static const std::string EVODB_BEST_BLOCK = "b_b2";
// "b_b3" was used after masternode type introduction in evoDB
static const std::string EVODB_BEST_BLOCK = "b_b3";
class CEvoDB;

View File

@ -2,6 +2,7 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <evo/dmn_types.h>
#include <evo/providertx.h>
#include <chainparams.h>
@ -14,7 +15,7 @@ maybe_error CProRegTx::IsTriviallyValid(bool is_bls_legacy_scheme) const
if (nVersion == 0 || nVersion > GetVersion(is_bls_legacy_scheme)) {
return {ValidationInvalidReason::CONSENSUS, "bad-protx-version"};
}
if (nType != 0) {
if (nType != MnType::Regular.index && nType != MnType::HighPerformance.index) {
return {ValidationInvalidReason::CONSENSUS, "bad-protx-type"};
}
if (nMode != 0) {
@ -78,8 +79,8 @@ std::string CProRegTx::ToString() const
payee = EncodeDestination(dest);
}
return strprintf("CProRegTx(nVersion=%d, collateralOutpoint=%s, addr=%s, nOperatorReward=%f, ownerAddress=%s, pubKeyOperator=%s, votingAddress=%s, scriptPayout=%s)",
nVersion, collateralOutpoint.ToStringShort(), addr.ToString(), (double)nOperatorReward / 100, EncodeDestination(PKHash(keyIDOwner)), pubKeyOperator.ToString(nVersion == LEGACY_BLS_VERSION), EncodeDestination(PKHash(keyIDVoting)), payee);
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, nType, collateralOutpoint.ToStringShort(), addr.ToString(), (double)nOperatorReward / 100, EncodeDestination(PKHash(keyIDOwner)), pubKeyOperator.ToString(nVersion == LEGACY_BLS_VERSION), EncodeDestination(PKHash(keyIDVoting)), payee, platformNodeID.ToString(), platformP2PPort, platformHTTPPort);
}
maybe_error CProUpServTx::IsTriviallyValid(bool is_bls_legacy_scheme) const
@ -99,8 +100,8 @@ std::string CProUpServTx::ToString() const
payee = EncodeDestination(dest);
}
return strprintf("CProUpServTx(nVersion=%d, proTxHash=%s, addr=%s, operatorPayoutAddress=%s)",
nVersion, proTxHash.ToString(), addr.ToString(), payee);
return strprintf("CProUpServTx(nVersion=%d, nType=%d, proTxHash=%s, addr=%s, operatorPayoutAddress=%s, platformNodeID=%s, platformP2PPort=%d, platformHTTPPort=%d)",
nVersion, nType, proTxHash.ToString(), addr.ToString(), payee, platformNodeID.ToString(), platformP2PPort, platformHTTPPort);
}
maybe_error CProUpRegTx::IsTriviallyValid(bool is_bls_legacy_scheme) const

View File

@ -10,6 +10,7 @@
#include <primitives/transaction.h>
#include <consensus/validation.h>
#include <evo/dmn_types.h>
#include <key_io.h>
#include <netaddress.h>
#include <pubkey.h>
@ -33,10 +34,13 @@ public:
}
uint16_t nVersion{LEGACY_BLS_VERSION}; // message version
uint16_t nType{0}; // only 0 supported for now
uint16_t nType{MnType::Regular.index};
uint16_t nMode{0}; // only 0 supported for now
COutPoint collateralOutpoint{uint256(), (uint32_t)-1}; // if hash is null, we refer to a ProRegTx output
CService addr;
uint160 platformNodeID{};
uint16_t platformP2PPort{0};
uint16_t platformHTTPPort{0};
CKeyID keyIDOwner;
CBLSPublicKey pubKeyOperator;
CKeyID keyIDVoting;
@ -66,6 +70,12 @@ public:
obj.scriptPayout,
obj.inputsHash
);
if (obj.nVersion == BASIC_BLS_VERSION && obj.nType == MnType::HighPerformance.index) {
READWRITE(
obj.platformNodeID,
obj.platformP2PPort,
obj.platformHTTPPort);
}
if (!(s.GetType() & SER_GETHASH)) {
READWRITE(obj.vchSig);
}
@ -82,6 +92,7 @@ public:
obj.clear();
obj.setObject();
obj.pushKV("version", nVersion);
obj.pushKV("type", nType);
obj.pushKV("collateralHash", collateralOutpoint.hash.ToString());
obj.pushKV("collateralIndex", (int)collateralOutpoint.n);
obj.pushKV("service", addr.ToString(false));
@ -94,7 +105,11 @@ public:
}
obj.pushKV("pubKeyOperator", pubKeyOperator.ToString(nVersion == LEGACY_BLS_VERSION));
obj.pushKV("operatorReward", (double)nOperatorReward / 100);
if (nType == MnType::HighPerformance.index) {
obj.pushKV("platformNodeID", platformNodeID.ToString());
obj.pushKV("platformP2PPort", platformP2PPort);
obj.pushKV("platformHTTPPort", platformHTTPPort);
}
obj.pushKV("inputsHash", inputsHash.ToString());
}
@ -114,8 +129,12 @@ public:
}
uint16_t nVersion{LEGACY_BLS_VERSION}; // message version
uint16_t nType{MnType::Regular.index};
uint256 proTxHash;
CService addr;
uint160 platformNodeID{};
uint16_t platformP2PPort{0};
uint16_t platformHTTPPort{0};
CScript scriptOperatorPayout;
uint256 inputsHash; // replay protection
CBLSSignature sig;
@ -129,12 +148,22 @@ public:
// unknown version, bail out early
return;
}
if (obj.nVersion == BASIC_BLS_VERSION) {
READWRITE(
obj.nType);
}
READWRITE(
obj.proTxHash,
obj.addr,
obj.scriptOperatorPayout,
obj.inputsHash
);
if (obj.nVersion == BASIC_BLS_VERSION && obj.nType == MnType::HighPerformance.index) {
READWRITE(
obj.platformNodeID,
obj.platformP2PPort,
obj.platformHTTPPort);
}
if (!(s.GetType() & SER_GETHASH)) {
READWRITE(
CBLSSignatureVersionWrapper(const_cast<CBLSSignature&>(obj.sig), (obj.nVersion == LEGACY_BLS_VERSION), true)
@ -149,12 +178,18 @@ public:
obj.clear();
obj.setObject();
obj.pushKV("version", nVersion);
obj.pushKV("type", nType);
obj.pushKV("proTxHash", proTxHash.ToString());
obj.pushKV("service", addr.ToString(false));
CTxDestination dest;
if (ExtractDestination(scriptOperatorPayout, dest)) {
obj.pushKV("operatorPayoutAddress", EncodeDestination(dest));
}
if (nType == MnType::HighPerformance.index) {
obj.pushKV("platformNodeID", platformNodeID.ToString());
obj.pushKV("platformP2PPort", platformP2PPort);
obj.pushKV("platformHTTPPort", platformHTTPPort);
}
obj.pushKV("inputsHash", inputsHash.ToString());
}

View File

@ -30,7 +30,10 @@ CSimplifiedMNListEntry::CSimplifiedMNListEntry(const CDeterministicMN& dmn) :
keyIDVoting(dmn.pdmnState->keyIDVoting),
isValid(!dmn.pdmnState->IsBanned()),
scriptPayout(dmn.pdmnState->scriptPayout),
scriptOperatorPayout(dmn.pdmnState->scriptOperatorPayout)
scriptOperatorPayout(dmn.pdmnState->scriptOperatorPayout),
nType(dmn.nType),
platformHTTPPort(dmn.pdmnState->platformHTTPPort),
platformNodeID(dmn.pdmnState->platformNodeID)
{
}
@ -53,8 +56,8 @@ std::string CSimplifiedMNListEntry::ToString() const
operatorPayoutAddress = EncodeDestination(dest);
}
return strprintf("CSimplifiedMNListEntry(proRegTxHash=%s, confirmedHash=%s, service=%s, pubKeyOperator=%s, votingAddress=%s, isValid=%d, payoutAddress=%s, operatorPayoutAddress=%s)",
proRegTxHash.ToString(), confirmedHash.ToString(), service.ToString(false), pubKeyOperator.Get().ToString(), EncodeDestination(PKHash(keyIDVoting)), isValid, payoutAddress, operatorPayoutAddress);
return strprintf("CSimplifiedMNListEntry(nType=%d, proRegTxHash=%s, confirmedHash=%s, service=%s, pubKeyOperator=%s, votingAddress=%s, isValid=%d, payoutAddress=%s, operatorPayoutAddress=%s, platformHTTPPort=%d, platformNodeID=%s)",
nType, proRegTxHash.ToString(), confirmedHash.ToString(), service.ToString(false), pubKeyOperator.Get().ToString(), EncodeDestination(PKHash(keyIDVoting)), isValid, payoutAddress, operatorPayoutAddress, platformHTTPPort, platformNodeID.ToString());
}
void CSimplifiedMNListEntry::ToJson(UniValue& obj, bool extended) const
@ -68,6 +71,11 @@ void CSimplifiedMNListEntry::ToJson(UniValue& obj, bool extended) const
obj.pushKV("votingAddress", EncodeDestination(PKHash(keyIDVoting)));
obj.pushKV("isValid", isValid);
obj.pushKV("nVersion", nVersion);
obj.pushKV("nType", nType);
if (nType == MnType::HighPerformance.index) {
obj.pushKV("platformHTTPPort", platformHTTPPort);
obj.pushKV("platformNodeID", platformNodeID.ToString());
}
if (!extended) return;
@ -80,6 +88,7 @@ void CSimplifiedMNListEntry::ToJson(UniValue& obj, bool extended) const
}
}
// TODO: Invistigate if we can delete this constructor
CSimplifiedMNList::CSimplifiedMNList(const std::vector<CSimplifiedMNListEntry>& smlEntries)
{
mnList.resize(smlEntries.size());

View File

@ -6,6 +6,8 @@
#define BITCOIN_EVO_SIMPLIFIEDMNS_H
#include <bls/bls.h>
#include <evo/deterministicmns.h>
#include <evo/dmn_types.h>
#include <merkleblock.h>
#include <netaddress.h>
#include <pubkey.h>
@ -32,6 +34,9 @@ public:
CBLSLazyPublicKey pubKeyOperator;
CKeyID keyIDVoting;
bool isValid{false};
uint16_t nType{MnType::Regular.index};
uint16_t platformHTTPPort{0};
uint160 platformNodeID{};
CScript scriptPayout; // mem-only
CScript scriptOperatorPayout; // mem-only
uint16_t nVersion{LEGACY_BLS_VERSION}; // mem-only
@ -47,7 +52,10 @@ public:
pubKeyOperator == rhs.pubKeyOperator &&
keyIDVoting == rhs.keyIDVoting &&
isValid == rhs.isValid &&
nVersion == rhs.nVersion;
nVersion == rhs.nVersion &&
nType == rhs.nType &&
platformHTTPPort == rhs.platformHTTPPort &&
platformNodeID == rhs.platformNodeID;
}
bool operator!=(const CSimplifiedMNListEntry& rhs) const
@ -65,6 +73,13 @@ public:
obj.keyIDVoting,
obj.isValid
);
if (obj.nVersion == BASIC_BLS_VERSION) {
READWRITE(obj.nType);
if (obj.nType == MnType::HighPerformance.index) {
READWRITE(obj.platformHTTPPort);
READWRITE(obj.platformNodeID);
}
}
}
uint256 CalcHash() const;

View File

@ -619,6 +619,8 @@ bool CGovernanceObject::IsCollateralValid(std::string& strError, bool& fMissingC
int CGovernanceObject::CountMatchingVotes(vote_signal_enum_t eVoteSignalIn, vote_outcome_enum_t eVoteOutcomeIn) const
{
auto mnList = deterministicMNManager->GetListAtChainTip();
LOCK(cs);
int nCount = 0;
@ -626,7 +628,10 @@ int CGovernanceObject::CountMatchingVotes(vote_signal_enum_t eVoteSignalIn, vote
const vote_rec_t& recVote = votepair.second;
auto it2 = recVote.mapInstances.find(eVoteSignalIn);
if (it2 != recVote.mapInstances.end() && it2->second.eOutcome == eVoteOutcomeIn) {
++nCount;
// 4x times weight vote for HPMN owners.
// No need to check if v19 is active since no HPMN are allowed to register before v19s
auto dmn = mnList.GetMNByCollateral(votepair.first);
if (dmn != nullptr) nCount += GetMnType(dmn->nType).voting_weight;
}
}
return nCount;

View File

@ -2,6 +2,7 @@
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <evo/dmn_types.h>
#include <governance/vote.h>
#include <bls/bls.h>
@ -110,11 +111,15 @@ CGovernanceVote::CGovernanceVote(const COutPoint& outpointMasternodeIn, const ui
std::string CGovernanceVote::ToString() const
{
auto mnList = deterministicMNManager->GetListAtChainTip();
auto dmn = mnList.GetMNByCollateral(masternodeOutpoint);
int voteWeight = dmn != nullptr ? GetMnType(dmn->nType).voting_weight : 0;
std::ostringstream ostr;
ostr << masternodeOutpoint.ToStringShort() << ":"
<< nTime << ":"
<< CGovernanceVoting::ConvertOutcomeToString(GetOutcome()) << ":"
<< CGovernanceVoting::ConvertSignalToString(GetSignal());
<< CGovernanceVoting::ConvertSignalToString(GetSignal()) << ":"
<< voteWeight;
return ostr.str();
}

View File

@ -2161,6 +2161,11 @@ bool AppInitMain(const CoreContext& context, NodeContext& node, interfaces::Bloc
break; // out of the chainstate activation do-while
}
if (!deterministicMNManager->MigrateDBIfNeeded()) {
strLoadError = _("Error upgrading evo database");
break;
}
if (!llmq::quorumBlockProcessor->UpgradeDB()) {
strLoadError = _("Error upgrading evo database");
break;

View File

@ -30,8 +30,9 @@ enum class LLMQType : uint8_t {
LLMQ_TEST_V17 = 102, // 3 members, 2 (66%) threshold, one per hour. Params might differ when -llmqtestparams is used
// for testing only
LLMQ_TEST_DIP0024 = 103, // 4 members, 2 (66%) threshold, one per hour. Params might differ when -llmqtestparams is used
LLMQ_TEST_DIP0024 = 103, // 4 members, 2 (66%) threshold, one per hour. Params might differ when -llmqtestparams is used
LLMQ_TEST_INSTANTSEND = 104, // 3 members, 2 (66%) threshold, one per hour. Params might differ when -llmqtestinstantsendparams is used
LLMQ_TEST_PLATFORM = 106, // 4 members, 2 (66%) threshold, one per hour.
// for devnets only. rotated version (v2) for devnets
LLMQ_DEVNET_DIP0024 = 105 // 8 members, 4 (50%) threshold, one per hour. Params might differ when -llmqdevnetparams is used
@ -106,7 +107,7 @@ struct LLMQParams {
};
static constexpr std::array<LLMQParams, 11> available_llmqs = {
static constexpr std::array<LLMQParams, 12> available_llmqs = {
/**
* llmq_test
@ -208,6 +209,31 @@ static constexpr std::array<LLMQParams, 11> available_llmqs = {
.recoveryMembers = 3,
},
/**
* llmq_test_platform
* This quorum is only used for testing
*
*/
LLMQParams{
.type = LLMQType::LLMQ_TEST_PLATFORM,
.name = "llmq_test_platform",
.useRotation = false,
.size = 4,
.minSize = 3,
.threshold = 2,
.dkgInterval = 24, // DKG cycle
.dkgPhaseBlocks = 2,
.dkgMiningWindowStart = 10, // signingActiveQuorumCount + dkgPhaseBlocks * 5 = after finalization
.dkgMiningWindowEnd = 18,
.dkgBadVotesThreshold = 2,
.signingActiveQuorumCount = 2, // just a few ones to allow easier testing
.keepOldConnections = 4,
.recoveryMembers = 3,
},
/**
* llmq_devnet
* This quorum is only used for testing on devnets

View File

@ -131,7 +131,7 @@ std::vector<CDeterministicMNCPtr> ComputeQuorumMembers(Consensus::LLMQType llmqT
{
auto allMns = deterministicMNManager->GetListForBlock(pQuorumBaseBlockIndex);
auto modifier = ::SerializeHash(std::make_pair(llmqType, pQuorumBaseBlockIndex->GetBlockHash()));
return allMns.CalculateQuorum(GetLLMQParams(llmqType).size, modifier);
return allMns.CalculateQuorum(GetLLMQParams(llmqType).size, modifier, IsLLMQTypeHPMNOnly(llmqType));
}
std::vector<std::vector<CDeterministicMNCPtr>> ComputeQuorumMembersByQuarterRotation(Consensus::LLMQType llmqType, const CBlockIndex* pCycleQuorumBaseBlockIndex)
@ -673,6 +673,11 @@ bool IsInstantSendLLMQTypeShared()
return false;
}
bool IsLLMQTypeHPMNOnly(Consensus::LLMQType llmqType)
{
return Params().GetConsensus().llmqTypePlatform == llmqType;
}
uint256 DeterministicOutboundConnection(const uint256& proTxHash1, const uint256& proTxHash2)
{
// We need to deterministically select who is going to initiate the connection. The naive way would be to simply
@ -930,6 +935,7 @@ bool IsQuorumTypeEnabledInternal(Consensus::LLMQType llmqType, const CQuorumMana
break;
}
case Consensus::LLMQType::LLMQ_TEST:
case Consensus::LLMQType::LLMQ_TEST_PLATFORM:
case Consensus::LLMQType::LLMQ_400_60:
case Consensus::LLMQType::LLMQ_400_85:
break;

View File

@ -85,6 +85,7 @@ Consensus::LLMQType GetInstantSendLLMQType(bool deterministic);
bool IsDIP0024Active(const CBlockIndex* pindex);
bool IsV19Active(const CBlockIndex* pindex);
const CBlockIndex* V19ActivationIndex(const CBlockIndex* pindex);
static bool IsLLMQTypeHPMNOnly(Consensus::LLMQType llmqType);
/// Returns the state of `-llmq-data-recovery`
bool QuorumDataRecoveryEnabled();

View File

@ -277,7 +277,7 @@ bool CMasternodePayments::GetBlockTxOuts(int nBlockHeight, CAmount blockReward,
voutMasternodePaymentsRet.clear();
const CBlockIndex* pindex = WITH_LOCK(cs_main, return ::ChainActive()[nBlockHeight - 1]);
auto dmnPayee = deterministicMNManager->GetListForBlock(pindex).GetMNPayee();
auto dmnPayee = deterministicMNManager->GetListForBlock(pindex).GetMNPayee(pindex);
if (!dmnPayee) {
return false;
}

View File

@ -8,6 +8,7 @@
#include <consensus/validation.h>
#include <core_io.h>
#include <evo/deterministicmns.h>
#include <evo/dmn_types.h>
#include <evo/providertx.h>
#include <evo/simplifiedmns.h>
#include <evo/specialtx.h>
@ -23,8 +24,8 @@
#include <rpc/server.h>
#include <rpc/util.h>
#include <util/moneystr.h>
#include <util/validation.h>
#include <util/translation.h>
#include <util/validation.h>
#include <validation.h>
#ifdef ENABLE_WALLET
@ -138,6 +139,18 @@ static RPCArg GetRpcArg(const std::string& strParamName)
"It has to match the private key which is later used when voting on proposals.\n"
"If set to an empty string, the currently active voting key address is reused."}
},
{"platformNodeID",
{"platformNodeID", RPCArg::Type::STR, RPCArg::Optional::NO,
"Platform P2P node ID, derived from P2P public key."}
},
{"platformP2PPort",
{"platformP2PPort", RPCArg::Type::NUM, RPCArg::Optional::NO,
"TCP port of Dash Platform peer-to-peer communication between nodes (network byte order)."}
},
{"platformHTTPPort",
{"platformHTTPPort", RPCArg::Type::NUM, RPCArg::Optional::NO,
"TCP port of Platform HTTP/API interface (network byte order)."}
},
};
auto it = mapParamHelp.find(strParamName);
@ -177,6 +190,11 @@ static CBLSSecretKey ParseBLSSecretKey(const std::string& hexKey, const std::str
return secKey;
}
static bool ValidatePlatformPort(const int32_t port)
{
return port >= 1 && port <= std::numeric_limits<uint16_t>::max();
}
#ifdef ENABLE_WALLET
template<typename SpecialTxPayload>
@ -433,18 +451,130 @@ static void protx_register_submit_help(const JSONRPCRequest& request)
},
}.Check(request);
}
static UniValue protx_register_wrapper(const JSONRPCRequest& request,
const bool specific_legacy_bls_scheme,
const bool isExternalRegister,
const bool isFundRegister,
const bool isPrepareRegister)
static void protx_register_fund_hpmn_help(const JSONRPCRequest& request)
{
if (isFundRegister && (request.fHelp || (request.params.size() < 7 || request.params.size() > 9))) {
protx_register_fund_help(request);
} else if (isExternalRegister && (request.fHelp || (request.params.size() < 8 || request.params.size() > 10))) {
protx_register_help(request);
} else if (isPrepareRegister && (request.fHelp || (request.params.size() != 8 && request.params.size() != 9))) {
protx_register_prepare_help(request);
RPCHelpMan{
"protx register_fund_hpmn",
"\nCreates, funds and sends a ProTx to the network. The resulting transaction will move 4000 Dash\n"
"to the address specified by collateralAddress and will then function as the collateral of your\n"
"HPMN.\n"
"A few of the limitations you see in the arguments are temporary and might be lifted after DIP3\n"
"is fully deployed.\n" +
HELP_REQUIRING_PASSPHRASE,
{
GetRpcArg("collateralAddress"),
GetRpcArg("ipAndPort"),
GetRpcArg("ownerAddress"),
GetRpcArg("operatorPubKey_register"),
GetRpcArg("votingAddress_register"),
GetRpcArg("operatorReward"),
GetRpcArg("payoutAddress_register"),
GetRpcArg("platformNodeID"),
GetRpcArg("platformP2PPort"),
GetRpcArg("platformHTTPPort"),
GetRpcArg("fundAddress"),
GetRpcArg("submit"),
},
{
RPCResult{"if \"submit\" is not set or set to true",
RPCResult::Type::STR_HEX, "txid", "The transaction id"},
RPCResult{"if \"submit\" is set to false",
RPCResult::Type::STR_HEX, "hex", "The serialized signed ProTx in hex format"},
},
RPCExamples{
HelpExampleCli("protx", "register_fund_hpmn \"XrVhS9LogauRJGJu2sHuryjhpuex4RNPSb\" 1000 \"1.2.3.4:1234\" \"Xt9AMWaYSz7tR7Uo7gzXA3m4QmeWgrR3rr\" \"93746e8731c57f87f79b3620a7982924e2931717d49540a85864bd543de11c43fb868fd63e501a1db37e19ed59ae6db4\" \"Xt9AMWaYSz7tR7Uo7gzXA3m4QmeWgrR3rr\" 0 \"XrVhS9LogauRJGJu2sHuryjhpuex4RNPSb\" \"f2dbd9b0a1f541a7c44d34a58674d0262f5feca5\" 22821 22822")},
}.Check(request);
}
static void protx_register_hpmn_help(const JSONRPCRequest& request)
{
RPCHelpMan{
"protx register_hpmn",
"\nSame as \"protx register_fund_hpmn\", but with an externally referenced collateral.\n"
"The collateral is specified through \"collateralHash\" and \"collateralIndex\" and must be an unspent\n"
"transaction output spendable by this wallet. It must also not be used by any other masternode.\n" +
HELP_REQUIRING_PASSPHRASE,
{
GetRpcArg("collateralHash"),
GetRpcArg("collateralIndex"),
GetRpcArg("ipAndPort"),
GetRpcArg("ownerAddress"),
GetRpcArg("operatorPubKey_register"),
GetRpcArg("votingAddress_register"),
GetRpcArg("operatorReward"),
GetRpcArg("payoutAddress_register"),
GetRpcArg("platformNodeID"),
GetRpcArg("platformP2PPort"),
GetRpcArg("platformHTTPPort"),
GetRpcArg("feeSourceAddress"),
GetRpcArg("submit"),
},
{
RPCResult{"if \"submit\" is not set or set to true",
RPCResult::Type::STR_HEX, "txid", "The transaction id"},
RPCResult{"if \"submit\" is set to false",
RPCResult::Type::STR_HEX, "hex", "The serialized signed ProTx in hex format"},
},
RPCExamples{
HelpExampleCli("protx", "register_hpmn \"0123456701234567012345670123456701234567012345670123456701234567\" 0 \"1.2.3.4:1234\" \"Xt9AMWaYSz7tR7Uo7gzXA3m4QmeWgrR3rr\" \"93746e8731c57f87f79b3620a7982924e2931717d49540a85864bd543de11c43fb868fd63e501a1db37e19ed59ae6db4\" \"Xt9AMWaYSz7tR7Uo7gzXA3m4QmeWgrR3rr\" 0 \"XrVhS9LogauRJGJu2sHuryjhpuex4RNPSb\" \"f2dbd9b0a1f541a7c44d34a58674d0262f5feca5\" 22821 22822")},
}.Check(request);
}
static void protx_register_prepare_hpmn_help(const JSONRPCRequest& request)
{
RPCHelpMan{
"protx register_prepare_hpmn",
"\nCreates an unsigned ProTx and a message that must be signed externally\n"
"with the private key that corresponds to collateralAddress to prove collateral ownership.\n"
"The prepared transaction will also contain inputs and outputs to cover fees.\n",
{
GetRpcArg("collateralHash"),
GetRpcArg("collateralIndex"),
GetRpcArg("ipAndPort"),
GetRpcArg("ownerAddress"),
GetRpcArg("operatorPubKey_register"),
GetRpcArg("votingAddress_register"),
GetRpcArg("operatorReward"),
GetRpcArg("payoutAddress_register"),
GetRpcArg("platformNodeID"),
GetRpcArg("platformP2PPort"),
GetRpcArg("platformHTTPPort"),
GetRpcArg("feeSourceAddress"),
},
RPCResult{
RPCResult::Type::OBJ, "", "", {
{RPCResult::Type::STR_HEX, "tx", "The serialized unsigned ProTx in hex format"},
{RPCResult::Type::STR_HEX, "collateralAddress", "The collateral address"},
{RPCResult::Type::STR_HEX, "signMessage", "The string message that needs to be signed with the collateral key"},
}},
RPCExamples{HelpExampleCli("protx", "register_prepare_hpmn \"0123456701234567012345670123456701234567012345670123456701234567\" 0 \"1.2.3.4:1234\" \"Xt9AMWaYSz7tR7Uo7gzXA3m4QmeWgrR3rr\" \"93746e8731c57f87f79b3620a7982924e2931717d49540a85864bd543de11c43fb868fd63e501a1db37e19ed59ae6db4\" \"Xt9AMWaYSz7tR7Uo7gzXA3m4QmeWgrR3rr\" 0 \"XrVhS9LogauRJGJu2sHuryjhpuex4RNPSb\" \"f2dbd9b0a1f541a7c44d34a58674d0262f5feca5\" 22821 22822")},
}.Check(request);
}
static UniValue protx_register_common_wrapper(const JSONRPCRequest& request,
const bool specific_legacy_bls_scheme,
const bool isExternalRegister,
const bool isFundRegister,
const bool isPrepareRegister,
const bool isHPMNrequested)
{
if (isHPMNrequested) {
if (isFundRegister && (request.fHelp || (request.params.size() < 10 || request.params.size() > 12))) {
protx_register_fund_hpmn_help(request);
} else if (isExternalRegister && (request.fHelp || (request.params.size() < 11 || request.params.size() > 13))) {
protx_register_hpmn_help(request);
} else if (isPrepareRegister && (request.fHelp || (request.params.size() != 11 && request.params.size() != 12))) {
protx_register_prepare_hpmn_help(request);
}
} else {
if (isFundRegister && (request.fHelp || (request.params.size() < 7 || request.params.size() > 9))) {
protx_register_fund_help(request);
} else if (isExternalRegister && (request.fHelp || (request.params.size() < 8 || request.params.size() > 10))) {
protx_register_help(request);
} else if (isPrepareRegister && (request.fHelp || (request.params.size() != 8 && request.params.size() != 9))) {
protx_register_prepare_help(request);
}
}
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
@ -454,19 +584,25 @@ static UniValue protx_register_wrapper(const JSONRPCRequest& request,
EnsureWalletIsUnlocked(wallet.get());
}
size_t paramIdx = 0;
bool isV19active = llmq::utils::IsV19Active(WITH_LOCK(cs_main, return ::ChainActive().Tip();));
if (isHPMNrequested && !isV19active) {
throw JSONRPCError(RPC_INVALID_REQUEST, "HPMN aren't allowed yet");
}
CAmount collateralAmount = 1000 * COIN;
size_t paramIdx = 0;
CMutableTransaction tx;
tx.nVersion = 3;
tx.nType = TRANSACTION_PROVIDER_REGISTER;
CProRegTx ptx;
if (specific_legacy_bls_scheme)
if (specific_legacy_bls_scheme && !isHPMNrequested) {
ptx.nVersion = CProRegTx::LEGACY_BLS_VERSION;
else
ptx.nVersion = CProRegTx::GetVersion(llmq::utils::IsV19Active(::ChainActive().Tip()));
} else {
ptx.nVersion = CProRegTx::GetVersion(isV19active);
}
auto mn_type = isHPMNrequested ? MnType::HighPerformance : MnType::Regular;
ptx.nType = mn_type.index;
if (isFundRegister) {
CTxDestination collateralDest = DecodeDestination(request.params[paramIdx].get_str());
@ -475,7 +611,8 @@ static UniValue protx_register_wrapper(const JSONRPCRequest& request,
}
CScript collateralScript = GetScriptForDestination(collateralDest);
CTxOut collateralTxOut(collateralAmount, collateralScript);
CAmount fundCollateral = mn_type.collat_amount;
CTxOut collateralTxOut(fundCollateral, collateralScript);
tx.vout.emplace_back(collateralTxOut);
paramIdx++;
@ -501,7 +638,7 @@ static UniValue protx_register_wrapper(const JSONRPCRequest& request,
}
ptx.keyIDOwner = ParsePubKeyIDFromAddress(request.params[paramIdx + 1].get_str(), "owner address");
CBLSPublicKey pubKeyOperator = ParseBLSPubKey(request.params[paramIdx + 2].get_str(), "operator BLS address", specific_legacy_bls_scheme);
CBLSPublicKey pubKeyOperator = ParseBLSPubKey(request.params[paramIdx + 2].get_str(), "operator BLS address", specific_legacy_bls_scheme && !isHPMNrequested);
CKeyID keyIDVoting = ptx.keyIDOwner;
if (request.params[paramIdx + 3].get_str() != "") {
@ -522,6 +659,27 @@ static UniValue protx_register_wrapper(const JSONRPCRequest& request,
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("invalid payout address: %s", request.params[paramIdx + 5].get_str()));
}
if (isHPMNrequested) {
if (!IsHex(request.params[paramIdx + 6].get_str())) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "platformNodeID must be hexadecimal string");
}
ptx.platformNodeID.SetHex(request.params[paramIdx + 6].get_str());
int32_t requestedPlatformP2PPort = ParseInt32V(request.params[paramIdx + 7].get_str(), "platformP2PPort");
if (!ValidatePlatformPort(requestedPlatformP2PPort)) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "platformP2PPort must be a valid port [1-65535]");
}
ptx.platformP2PPort = static_cast<uint16_t>(requestedPlatformP2PPort);
int32_t requestedPlatformHTTPPort = ParseInt32V(request.params[paramIdx + 8].get_str(), "platformHTTPPort");
if (!ValidatePlatformPort(requestedPlatformHTTPPort)) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "platformHTTPPort must be a valid port [1-65535]");
}
ptx.platformHTTPPort = static_cast<uint16_t>(requestedPlatformHTTPPort);
paramIdx += 3;
}
ptx.pubKeyOperator = pubKeyOperator;
ptx.keyIDVoting = keyIDVoting;
ptx.scriptPayout = GetScriptForDestination(payoutDest);
@ -547,9 +705,10 @@ static UniValue protx_register_wrapper(const JSONRPCRequest& request,
}
if (isFundRegister) {
CAmount fundCollateral = mn_type.collat_amount;
uint32_t collateralIndex = (uint32_t) -1;
for (uint32_t i = 0; i < tx.vout.size(); i++) {
if (tx.vout[i].nValue == collateralAmount) {
if (tx.vout[i].nValue == fundCollateral) {
collateralIndex = i;
break;
}
@ -601,12 +760,20 @@ static UniValue protx_register_wrapper(const JSONRPCRequest& request,
}
}
static UniValue protx_register_hpmn(const JSONRPCRequest& request)
{
bool isExternalRegister = request.strMethod == "protxregister_hpmn";
bool isFundRegister = request.strMethod == "protxregister_fund_hpmn";
bool isPrepareRegister = request.strMethod == "protxregister_prepare_hpmn";
return protx_register_common_wrapper(request, false, isExternalRegister, isFundRegister, isPrepareRegister, true);
}
static UniValue protx_register(const JSONRPCRequest& request)
{
bool isExternalRegister = request.strMethod == "protxregister";
bool isFundRegister = request.strMethod == "protxregister_fund";
bool isPrepareRegister = request.strMethod == "protxregister_prepare";
return protx_register_wrapper(request, false, isExternalRegister, isFundRegister, isPrepareRegister);
return protx_register_common_wrapper(request, false, isExternalRegister, isFundRegister, isPrepareRegister, false);
}
static UniValue protx_register_legacy(const JSONRPCRequest& request)
@ -614,10 +781,9 @@ static UniValue protx_register_legacy(const JSONRPCRequest& request)
bool isExternalRegister = request.strMethod == "protxregister_legacy";
bool isFundRegister = request.strMethod == "protxregister_fund_legacy";
bool isPrepareRegister = request.strMethod == "protxregister_prepare_legacy";
return protx_register_wrapper(request, true, isExternalRegister, isFundRegister, isPrepareRegister);
return protx_register_common_wrapper(request, true, isExternalRegister, isFundRegister, isPrepareRegister, false);
}
// handles register, register_prepare and register_fund in one method
static UniValue protx_register_submit(const JSONRPCRequest& request)
{
protx_register_submit_help(request);
@ -671,17 +837,52 @@ static void protx_update_service_help(const JSONRPCRequest& request)
}.Check(request);
}
static UniValue protx_update_service(const JSONRPCRequest& request)
static void protx_update_service_hpmn_help(const JSONRPCRequest& request)
{
protx_update_service_help(request);
RPCHelpMan{
"protx update_service_hpmn",
"\nCreates and sends a ProUpServTx to the network. This will update the IP address and the Platform fields\n"
"of a HPMN.\n"
"If this is done for a HPMN that got PoSe-banned, the ProUpServTx will also revive this HPMN.\n" +
HELP_REQUIRING_PASSPHRASE,
{
GetRpcArg("proTxHash"),
GetRpcArg("ipAndPort"),
GetRpcArg("operatorKey"),
GetRpcArg("platformNodeID"),
GetRpcArg("platformP2PPort"),
GetRpcArg("platformHTTPPort"),
GetRpcArg("operatorPayoutAddress"),
GetRpcArg("feeSourceAddress"),
},
RPCResult{
RPCResult::Type::STR_HEX, "txid", "The transaction id"},
RPCExamples{
HelpExampleCli("protx", "update_service_hpmn \"0123456701234567012345670123456701234567012345670123456701234567\" \"1.2.3.4:1234\" \"5a2e15982e62f1e0b7cf9783c64cf7e3af3f90a52d6c40f6f95d624c0b1621cd\" \"f2dbd9b0a1f541a7c44d34a58674d0262f5feca5\" 22821 22822")},
}.Check(request);
}
static UniValue protx_update_service_common_wrapper(const JSONRPCRequest& request, const bool isHPMNrequested)
{
if (isHPMNrequested) {
protx_update_service_hpmn_help(request);
} else {
protx_update_service_help(request);
}
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
if (!wallet) return NullUniValue;
EnsureWalletIsUnlocked(wallet.get());
bool isV19active = llmq::utils::IsV19Active(WITH_LOCK(cs_main, return ::ChainActive().Tip();));
if (isHPMNrequested && !isV19active) {
throw JSONRPCError(RPC_INVALID_REQUEST, "HPMN aren't allowed yet");
}
CProUpServTx ptx;
ptx.nVersion = CProUpServTx::GetVersion(llmq::utils::IsV19Active(::ChainActive().Tip()));
ptx.nType = isHPMNrequested ? MnType::HighPerformance.index : MnType::Regular.index;
ptx.proTxHash = ParseHashV(request.params[0], "proTxHash");
if (!Lookup(request.params[1].get_str().c_str(), ptx.addr, Params().GetDefaultPort(), false)) {
@ -690,10 +891,37 @@ static UniValue protx_update_service(const JSONRPCRequest& request)
CBLSSecretKey keyOperator = ParseBLSSecretKey(request.params[2].get_str(), "operatorKey");
size_t paramIdx = 3;
if (isHPMNrequested) {
if (!IsHex(request.params[paramIdx].get_str())) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "platformNodeID must be hexadecimal string");
}
ptx.platformNodeID.SetHex(request.params[paramIdx].get_str());
int32_t requestedPlatformP2PPort = ParseInt32V(request.params[paramIdx + 1].get_str(), "platformP2PPort");
if (!ValidatePlatformPort(requestedPlatformP2PPort)) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "platformP2PPort must be a valid port [1-65535]");
}
ptx.platformP2PPort = static_cast<uint16_t>(requestedPlatformP2PPort);
int32_t requestedPlatformHTTPPort = ParseInt32V(request.params[paramIdx + 2].get_str(), "platformHTTPPort");
if (!ValidatePlatformPort(requestedPlatformHTTPPort)) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "platformHTTPPort must be a valid port [1-65535]");
}
ptx.platformHTTPPort = static_cast<uint16_t>(requestedPlatformHTTPPort);
paramIdx += 3;
}
auto dmn = deterministicMNManager->GetListAtChainTip().GetMN(ptx.proTxHash);
if (!dmn) {
throw std::runtime_error(strprintf("masternode with proTxHash %s not found", ptx.proTxHash.ToString()));
}
if (isHPMNrequested && dmn->nType != MnType::HighPerformance.index) {
throw std::runtime_error(strprintf("masternode with proTxHash %s is not a HPMN", ptx.proTxHash.ToString()));
} else if (!isHPMNrequested && dmn->nType == MnType::HighPerformance.index) {
throw std::runtime_error(strprintf("masternode with proTxHash %s is a HPMN", ptx.proTxHash.ToString()));
}
if (keyOperator.GetPublicKey() != dmn->pdmnState->pubKeyOperator.Get()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("the operator key does not belong to the registered public key"));
@ -704,13 +932,13 @@ static UniValue protx_update_service(const JSONRPCRequest& request)
tx.nType = TRANSACTION_PROVIDER_UPDATE_SERVICE;
// param operatorPayoutAddress
if (!request.params[3].isNull()) {
if (request.params[3].get_str().empty()) {
if (!request.params[paramIdx].isNull()) {
if (request.params[paramIdx].get_str().empty()) {
ptx.scriptOperatorPayout = dmn->pdmnState->scriptOperatorPayout;
} else {
CTxDestination payoutDest = DecodeDestination(request.params[3].get_str());
CTxDestination payoutDest = DecodeDestination(request.params[paramIdx].get_str());
if (!IsValidDestination(payoutDest)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("invalid operator payout address: %s", request.params[3].get_str()));
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("invalid operator payout address: %s", request.params[paramIdx].get_str()));
}
ptx.scriptOperatorPayout = GetScriptForDestination(payoutDest);
}
@ -721,10 +949,10 @@ static UniValue protx_update_service(const JSONRPCRequest& request)
CTxDestination feeSource;
// param feeSourceAddress
if (!request.params[4].isNull()) {
feeSource = DecodeDestination(request.params[4].get_str());
if (!request.params[paramIdx + 1].isNull()) {
feeSource = DecodeDestination(request.params[paramIdx + 1].get_str());
if (!IsValidDestination(feeSource))
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, std::string("Invalid Dash address: ") + request.params[4].get_str());
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, std::string("Invalid Dash address: ") + request.params[paramIdx + 1].get_str());
} else {
if (ptx.scriptOperatorPayout != CScript()) {
// use operator reward address as default source for fees
@ -1228,7 +1456,8 @@ static UniValue protx_diff(const JSONRPCRequest& request)
[[ noreturn ]] static void protx_help()
{
RPCHelpMan{"protx",
RPCHelpMan{
"protx",
"Set of commands to execute ProTx related actions.\n"
"To get help on individual commands, use \"help protx command\".\n"
"\nAvailable commands:\n"
@ -1236,6 +1465,9 @@ static UniValue protx_diff(const JSONRPCRequest& request)
" register - Create and send ProTx to network\n"
" register_fund - Fund, create and send ProTx to network\n"
" register_prepare - Create an unsigned ProTx\n"
" register_hpmn - Create and send ProTx to network for a HPMN\n"
" register_fund_hpmn - Fund, create and send ProTx to network for a HPMN\n"
" register_prepare_hpmn - Create an unsigned ProTx for a HPMN\n"
" register_legacy - Create a ProTx by parsing BLS using the legacy scheme and send it to network\n"
" register_fund_legacy - Fund and create a ProTx by parsing BLS using the legacy scheme, then send it to network\n"
" register_prepare_legacy - Create an unsigned ProTx by parsing BLS using the legacy scheme\n"
@ -1245,6 +1477,7 @@ static UniValue protx_diff(const JSONRPCRequest& request)
" info - Return information about a ProTx\n"
#ifdef ENABLE_WALLET
" update_service - Create and send ProUpServTx to network\n"
" update_service_hpmn - Create and send ProUpServTx to network for a HPMN\n"
" update_registrar - Create and send ProUpRegTx to network\n"
" revoke - Create and send ProUpRevTx to network\n"
#endif
@ -1265,12 +1498,16 @@ static UniValue protx(const JSONRPCRequest& request)
#ifdef ENABLE_WALLET
if (command == "protxregister" || command == "protxregister_fund" || command == "protxregister_prepare") {
return protx_register(new_request);
} else if (command == "protxregister_hpmn" || command == "protxregister_fund_hpmn" || command == "protxregister_prepare_hpmn") {
return protx_register_hpmn(new_request);
} else if (command == "protxregister_legacy" || command == "protxregister_fund_legacy" || command == "protxregister_prepare_legacy") {
return protx_register_legacy(new_request);
} else if (command == "protxregister_submit") {
return protx_register_submit(new_request);
} else if (command == "protxupdate_service") {
return protx_update_service(new_request);
return protx_update_service_common_wrapper(new_request, false);
} else if (command == "protxupdate_service_hpmn") {
return protx_update_service_common_wrapper(new_request, true);
} else if (command == "protxupdate_registrar") {
return protx_update_registrar(new_request);
} else if (command == "protxupdate_registrar_legacy") {

View File

@ -336,7 +336,8 @@ static UniValue masternode_winners(const JSONRPCRequest& request)
int nStartHeight = std::max(nChainTipHeight - nCount, 1);
for (int h = nStartHeight; h <= nChainTipHeight; h++) {
auto payee = deterministicMNManager->GetListForBlock(pindexTip->GetAncestor(h - 1)).GetMNPayee();
const CBlockIndex* pIndex = pindexTip->GetAncestor(h - 1);
auto payee = deterministicMNManager->GetListForBlock(pIndex).GetMNPayee(pIndex);
std::string strPayments = GetRequiredPaymentsString(h, payee);
if (strFilter != "" && strPayments.find(strFilter) == std::string::npos) continue;
obj.pushKV(strprintf("%d", h), strPayments);
@ -462,7 +463,7 @@ static UniValue masternode_payments(const JSONRPCRequest& request)
}
// NOTE: we use _previous_ block to find a payee for the current one
const auto dmnPayee = deterministicMNManager->GetListForBlock(pindex->pprev).GetMNPayee();
const auto dmnPayee = deterministicMNManager->GetListForBlock(pindex->pprev).GetMNPayee(pindex->pprev);
protxObj.pushKV("proTxHash", dmnPayee == nullptr ? "" : dmnPayee->proTxHash.ToString());
protxObj.pushKV("amount", payedPerMasternode);
protxObj.pushKV("payees", payeesArr);
@ -666,7 +667,14 @@ static UniValue masternodelist(const JSONRPCRequest& request)
objMN.pushKV("address", dmn.pdmnState->addr.ToString());
objMN.pushKV("payee", payeeStr);
objMN.pushKV("status", dmnToStatus(dmn));
objMN.pushKV("type", std::string(GetMnType(dmn.nType).description));
if (dmn.nType == MnType::HighPerformance.index) {
objMN.pushKV("platformNodeID", dmn.pdmnState->platformNodeID.ToString());
objMN.pushKV("platformP2PPort", dmn.pdmnState->platformP2PPort);
objMN.pushKV("platformHTTPPort", dmn.pdmnState->platformHTTPPort);
}
objMN.pushKV("pospenaltyscore", dmn.pdmnState->nPoSePenalty);
objMN.pushKV("consecutivePayments", dmn.pdmnState->nConsecutivePayments);
objMN.pushKV("lastpaidtime", dmnToLastPaidTime(dmn));
objMN.pushKV("lastpaidblock", dmn.pdmnState->nLastPaidHeight);
objMN.pushKV("owneraddress", EncodeDestination(PKHash(dmn.pdmnState->keyIDOwner)));

View File

@ -132,7 +132,7 @@ static CMutableTransaction CreateProRegTx(const CTxMemPool& mempool, SimpleUTXOM
CMutableTransaction tx;
tx.nVersion = 3;
tx.nType = TRANSACTION_PROVIDER_REGISTER;
FundTransaction(tx, utxos, scriptPayout, 1000 * COIN);
FundTransaction(tx, utxos, scriptPayout, MnType::Regular.collat_amount);
proTx.inputsHash = CalcTxInputsHash(CTransaction(tx));
SetTxPayload(tx, proTx);
SignTransaction(mempool, tx, coinbaseKey);

View File

@ -110,7 +110,7 @@ static CMutableTransaction CreateProRegTx(const CTxMemPool& mempool, SimpleUTXOM
CMutableTransaction tx;
tx.nVersion = 3;
tx.nType = TRANSACTION_PROVIDER_REGISTER;
FundTransaction(tx, utxos, scriptPayout, 1000 * COIN, coinbaseKey);
FundTransaction(tx, utxos, scriptPayout, MnType::Regular.collat_amount, coinbaseKey);
proTx.inputsHash = CalcTxInputsHash(CTransaction(tx));
SetTxPayload(tx, proTx);
SignTransaction(mempool, tx, coinbaseKey);
@ -322,7 +322,7 @@ void FuncDIP3Protx(TestChainSetup& setup)
// check MN reward payments
for (size_t i = 0; i < 20; i++) {
auto dmnExpectedPayee = deterministicMNManager->GetListAtChainTip().GetMNPayee();
auto dmnExpectedPayee = deterministicMNManager->GetListAtChainTip().GetMNPayee(::ChainActive().Tip());
CBlock block = setup.CreateAndProcessBlock({}, setup.coinbaseKey);
deterministicMNManager->UpdatedBlockTip(::ChainActive().Tip());
@ -380,7 +380,7 @@ void FuncDIP3Protx(TestChainSetup& setup)
// test that the revoked MN does not get paid anymore
for (size_t i = 0; i < 20; i++) {
auto dmnExpectedPayee = deterministicMNManager->GetListAtChainTip().GetMNPayee();
auto dmnExpectedPayee = deterministicMNManager->GetListAtChainTip().GetMNPayee(::ChainActive().Tip());
BOOST_ASSERT(dmnExpectedPayee->proTxHash != dmnHashes[0]);
CBlock block = setup.CreateAndProcessBlock({}, setup.coinbaseKey);
@ -428,7 +428,7 @@ void FuncDIP3Protx(TestChainSetup& setup)
// test that the revived MN gets payments again
bool foundRevived = false;
for (size_t i = 0; i < 20; i++) {
auto dmnExpectedPayee = deterministicMNManager->GetListAtChainTip().GetMNPayee();
auto dmnExpectedPayee = deterministicMNManager->GetListAtChainTip().GetMNPayee(::ChainActive().Tip());
if (dmnExpectedPayee->proTxHash == dmnHashes[0]) {
foundRevived = true;
}
@ -468,7 +468,7 @@ void FuncTestMempoolReorg(TestChainSetup& setup)
// Create a MN with an external collateral
CMutableTransaction tx_collateral;
FundTransaction(tx_collateral, utxos, scriptCollateral, 1000 * COIN, setup.coinbaseKey);
FundTransaction(tx_collateral, utxos, scriptCollateral, MnType::Regular.collat_amount, setup.coinbaseKey);
SignTransaction(*(setup.m_node.mempool), tx_collateral, setup.coinbaseKey);
auto block = std::make_shared<CBlock>(setup.CreateBlock({tx_collateral}, setup.coinbaseKey));
@ -486,7 +486,7 @@ void FuncTestMempoolReorg(TestChainSetup& setup)
payload.scriptPayout = scriptPayout;
for (size_t i = 0; i < tx_collateral.vout.size(); ++i) {
if (tx_collateral.vout[i].nValue == 1000 * COIN) {
if (tx_collateral.vout[i].nValue == MnType::Regular.collat_amount) {
payload.collateralOutpoint = COutPoint(tx_collateral.GetHash(), i);
break;
}
@ -495,7 +495,7 @@ void FuncTestMempoolReorg(TestChainSetup& setup)
CMutableTransaction tx_reg;
tx_reg.nVersion = 3;
tx_reg.nType = TRANSACTION_PROVIDER_REGISTER;
FundTransaction(tx_reg, utxos, scriptPayout, 1000 * COIN, setup.coinbaseKey);
FundTransaction(tx_reg, utxos, scriptPayout, MnType::Regular.collat_amount, setup.coinbaseKey);
payload.inputsHash = CalcTxInputsHash(CTransaction(tx_reg));
CMessageSigner::SignMessage(payload.MakeSignString(), payload.vchSig, collateralKey);
SetTxPayload(tx_reg, payload);
@ -555,7 +555,7 @@ void FuncTestMempoolDualProregtx(TestChainSetup& setup)
payload.scriptPayout = scriptPayout;
for (size_t i = 0; i < tx_reg1.vout.size(); ++i) {
if (tx_reg1.vout[i].nValue == 1000 * COIN) {
if (tx_reg1.vout[i].nValue == MnType::Regular.collat_amount) {
payload.collateralOutpoint = COutPoint(tx_reg1.GetHash(), i);
break;
}
@ -564,7 +564,7 @@ void FuncTestMempoolDualProregtx(TestChainSetup& setup)
CMutableTransaction tx_reg2;
tx_reg2.nVersion = 3;
tx_reg2.nType = TRANSACTION_PROVIDER_REGISTER;
FundTransaction(tx_reg2, utxos, scriptPayout, 1000 * COIN, setup.coinbaseKey);
FundTransaction(tx_reg2, utxos, scriptPayout, MnType::Regular.collat_amount, setup.coinbaseKey);
payload.inputsHash = CalcTxInputsHash(CTransaction(tx_reg2));
CMessageSigner::SignMessage(payload.MakeSignString(), payload.vchSig, collateralKey);
SetTxPayload(tx_reg2, payload);
@ -599,7 +599,7 @@ void FuncVerifyDB(TestChainSetup& setup)
// Create a MN with an external collateral
CMutableTransaction tx_collateral;
FundTransaction(tx_collateral, utxos, scriptCollateral, 1000 * COIN, setup.coinbaseKey);
FundTransaction(tx_collateral, utxos, scriptCollateral, MnType::Regular.collat_amount, setup.coinbaseKey);
SignTransaction(*(setup.m_node.mempool), tx_collateral, setup.coinbaseKey);
auto block = std::make_shared<CBlock>(setup.CreateBlock({tx_collateral}, setup.coinbaseKey));
@ -617,7 +617,7 @@ void FuncVerifyDB(TestChainSetup& setup)
payload.scriptPayout = scriptPayout;
for (size_t i = 0; i < tx_collateral.vout.size(); ++i) {
if (tx_collateral.vout[i].nValue == 1000 * COIN) {
if (tx_collateral.vout[i].nValue == MnType::Regular.collat_amount) {
payload.collateralOutpoint = COutPoint(tx_collateral.GetHash(), i);
break;
}
@ -626,7 +626,7 @@ void FuncVerifyDB(TestChainSetup& setup)
CMutableTransaction tx_reg;
tx_reg.nVersion = 3;
tx_reg.nType = TRANSACTION_PROVIDER_REGISTER;
FundTransaction(tx_reg, utxos, scriptPayout, 1000 * COIN, setup.coinbaseKey);
FundTransaction(tx_reg, utxos, scriptPayout, MnType::Regular.collat_amount, setup.coinbaseKey);
payload.inputsHash = CalcTxInputsHash(CTransaction(tx_reg));
CMessageSigner::SignMessage(payload.MakeSignString(), payload.vchSig, collateralKey);
SetTxPayload(tx_reg, payload);

View File

@ -11,7 +11,7 @@
*/
static const int PROTOCOL_VERSION = 70226;
static const int PROTOCOL_VERSION = 70227;
//! initial proto version, to be increased after version/verack negotiation
static const int INIT_PROTO_VERSION = 209;
@ -50,6 +50,9 @@ static const int BLS_SCHEME_PROTO_VERSION = 70225;
//! DSQ and DSTX started using protx hash in this version
static const int COINJOIN_PROTX_HASH_PROTO_VERSION = 70226;
//! Masternode type was introduced in this version
static const int DMN_TYPE_PROTO_VERSION = 70227;
// Make sure that none of the values above collide with `ADDRV2_FORMAT`.
#endif // BITCOIN_VERSION_H

View File

@ -2535,7 +2535,7 @@ void CWallet::AvailableCoins(std::vector<COutput> &vCoins, bool fOnlySafe, const
if (CCoinJoin::IsCollateralAmount(pcoin->tx->vout[i].nValue)) continue; // do not use collateral amounts
found = !CCoinJoin::IsDenominatedAmount(pcoin->tx->vout[i].nValue);
} else if(nCoinType == CoinType::ONLY_MASTERNODE_COLLATERAL) {
found = pcoin->tx->vout[i].nValue == 1000*COIN;
found = pcoin->tx->vout[i].nValue == MnType::Regular.collat_amount || pcoin->tx->vout[i].nValue == MnType::HighPerformance.collat_amount;
} else if(nCoinType == CoinType::ONLY_COINJOIN_COLLATERAL) {
found = CCoinJoin::IsCollateralAmount(pcoin->tx->vout[i].nValue);
} else {
@ -3046,7 +3046,7 @@ std::vector<CompactTallyItem> CWallet::SelectCoinsGroupedByAddresses(bool fSkipD
if(fAnonymizable) {
// ignore collaterals
if(CCoinJoin::IsCollateralAmount(wtx.tx->vout[i].nValue)) continue;
if(fMasternodeMode && wtx.tx->vout[i].nValue == 1000*COIN) continue;
if (fMasternodeMode && (wtx.tx->vout[i].nValue == MnType::Regular.collat_amount || wtx.tx->vout[i].nValue == MnType::HighPerformance.collat_amount)) continue;
// ignore outputs that are 10 times smaller then the smallest denomination
// otherwise they will just lead to higher fee / lower priority
if(wtx.tx->vout[i].nValue <= nSmallestDenom/10) continue;

View File

@ -0,0 +1,212 @@
#!/usr/bin/env python3
# Copyright (c) 2015-2022 The Dash Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
'''
feature_llmq_hpmn.py
Checks HPMNs
'''
from _decimal import Decimal
import random
from test_framework.script import hash160
from test_framework.test_framework import DashTestFramework
from test_framework.util import (
assert_equal, p2p_port
)
def extract_quorum_members(quorum_info):
return [d['proTxHash'] for d in quorum_info["members"]]
class LLMQHPMNTest(DashTestFramework):
def set_test_params(self):
self.set_dash_test_params(5, 4, fast_dip3_enforcement=True, hpmn_count=7)
self.set_dash_llmq_test_params(4, 4)
def run_test(self):
# Connect all nodes to node1 so that we always have the whole network connected
# Otherwise only masternode connections will be established between nodes, which won't propagate TXs/blocks
# Usually node0 is the one that does this, but in this test we isolate it multiple times
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()
self.mine_quorum(llmq_type_name='llmq_test', llmq_type=100)
self.log.info("Test that HPMN registration is rejected before v19")
self.test_hpmn_is_rejected_before_v19()
self.activate_v19(expected_activation_height=900)
self.log.info("Activated v19 at height:" + str(self.nodes[0].getblockcount()))
self.move_to_next_cycle()
self.log.info("Cycle H height:" + str(self.nodes[0].getblockcount()))
self.move_to_next_cycle()
self.log.info("Cycle H+C height:" + str(self.nodes[0].getblockcount()))
self.move_to_next_cycle()
self.log.info("Cycle H+2C height:" + str(self.nodes[0].getblockcount()))
(quorum_info_i_0, quorum_info_i_1) = self.mine_cycle_quorum(llmq_type_name='llmq_test_dip0024', llmq_type=103)
hpmn_protxhash_list = list()
for i in range(5):
hpmn_info = self.dynamically_add_masternode(hpmn=True)
hpmn_protxhash_list.append(hpmn_info.proTxHash)
self.nodes[0].generate(8)
self.sync_blocks(self.nodes)
self.test_hpmn_update_service(hpmn_info)
self.log.info("Test llmq_platform are formed only with HPMNs")
for i in range(3):
quorum_i_hash = self.mine_quorum(llmq_type_name='llmq_test_platform', llmq_type=106)
self.test_quorum_members_are_high_performance(quorum_i_hash, llmq_type=106)
self.log.info("Test that HPMNs are present in MN list")
self.test_hpmn_protx_are_in_mnlist(hpmn_protxhash_list)
self.log.info("Test that HPMNs are paid 4x blocks in a row")
self.test_hpmmn_payements(window_analysis=256)
self.log.info(self.nodes[0].masternodelist())
return
def test_hpmmn_payements(self, window_analysis):
current_hpmn = None
consecutive_paymments = 0
for i in range(0, window_analysis):
payee = self.get_mn_payee_for_block(self.nodes[0].getbestblockhash())
if payee is not None and payee.hpmn:
if current_hpmn is not None and payee.proTxHash == current_hpmn.proTxHash:
# same HPMN
assert consecutive_paymments > 0
consecutive_paymments += 1
consecutive_paymments_rpc = self.nodes[0].protx('info', current_hpmn.proTxHash)['state']['consecutivePayments']
assert_equal(consecutive_paymments, consecutive_paymments_rpc)
else:
# new HPMN
if current_hpmn is not None:
# make sure the old one was paid 4 times in a row
assert_equal(consecutive_paymments, 4)
consecutive_paymments_rpc = self.nodes[0].protx('info', current_hpmn.proTxHash)['state']['consecutivePayments']
# old HPMN should have its nConsecutivePayments reset to 0
assert_equal(consecutive_paymments_rpc, 0)
current_hpmn = payee
consecutive_paymments = 1
consecutive_paymments_rpc = self.nodes[0].protx('info', current_hpmn.proTxHash)['state']['consecutivePayments']
assert_equal(consecutive_paymments, consecutive_paymments_rpc)
else:
# not a HPMN
if current_hpmn is not None:
# make sure the old one was paid 4 times in a row
assert_equal(consecutive_paymments, 4)
consecutive_paymments_rpc = self.nodes[0].protx('info', current_hpmn.proTxHash)['state']['consecutivePayments']
# old HPMN should have its nConsecutivePayments reset to 0
assert_equal(consecutive_paymments_rpc, 0)
current_hpmn = None
consecutive_paymments = 0
self.nodes[0].generate(1)
if i % 8 == 0:
self.sync_blocks()
def get_mn_payee_for_block(self, block_hash):
mn_payee_info = self.nodes[0].masternode("payments", block_hash)[0]
mn_payee_protx = mn_payee_info['masternodes'][0]['proTxHash']
mninfos_online = self.mninfo.copy()
for mn_info in mninfos_online:
if mn_info.proTxHash == mn_payee_protx:
return mn_info
return None
def test_quorum_members_are_high_performance(self, quorum_hash, llmq_type):
quorum_info = self.nodes[0].quorum("info", llmq_type, quorum_hash)
quorum_members = extract_quorum_members(quorum_info)
mninfos_online = self.mninfo.copy()
for qm in quorum_members:
found = False
for mn in mninfos_online:
if mn.proTxHash == qm:
assert_equal(mn.hpmn, True)
found = True
break
assert_equal(found, True)
def test_hpmn_protx_are_in_mnlist(self, hpmn_protx_list):
mn_list = self.nodes[0].masternodelist()
for hpmn_protx in hpmn_protx_list:
found = False
for mn in mn_list:
if mn_list.get(mn)['proTxHash'] == hpmn_protx:
found = True
assert_equal(mn_list.get(mn)['type'], "HighPerformance")
assert_equal(found, True)
def test_hpmn_is_rejected_before_v19(self):
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()
collateral_amount = 4000
collateral_txid = self.nodes[0].sendtoaddress(collateral_address, collateral_amount)
# send to same address to reserve some funds for fees
self.nodes[0].sendtoaddress(funds_address, 1)
collateral_vout = 0
self.nodes[0].generate(1)
self.sync_all(self.nodes)
rawtx = self.nodes[0].getrawtransaction(collateral_txid, 1)
for txout in rawtx['vout']:
if txout['value'] == Decimal(collateral_amount):
collateral_vout = txout['n']
break
assert collateral_vout is not None
ipAndPort = '127.0.0.1:%d' % p2p_port(len(self.nodes))
operatorReward = len(self.nodes)
self.nodes[0].generate(1)
protx_success = False
try:
self.nodes[0].protx('register_hpmn', collateral_txid, collateral_vout, ipAndPort, owner_address, bls['public'], voting_address, operatorReward, reward_address, funds_address, True)
protx_success = True
except:
self.log.info("protx_hpmn rejected")
assert_equal(protx_success, False)
def test_hpmn_update_service(self, hpmn_info):
funds_address = self.nodes[0].getnewaddress()
operator_reward_address = self.nodes[0].getnewaddress()
# For the sake of the test, generate random nodeid, p2p and http platform values
rnd = random.randint(1000, 65000)
platform_node_id = hash160(b'%d' % rnd).hex()
platform_p2p_port = '%d' % (rnd + 1)
platform_http_port = '%d' % (rnd + 2)
self.nodes[0].sendtoaddress(funds_address, 1)
self.nodes[0].generate(1)
self.sync_all(self.nodes)
self.nodes[0].protx('update_service_hpmn', hpmn_info.proTxHash, hpmn_info.addr, hpmn_info.keyOperator, platform_node_id, platform_p2p_port, platform_http_port, operator_reward_address, funds_address)
self.nodes[0].generate(1)
self.sync_all(self.nodes)
self.log.info("Updated HPMN %s: platformNodeID=%s, platformP2PPort=%s, platformHTTPPort=%s" % (hpmn_info.proTxHash, platform_node_id, platform_p2p_port, platform_http_port))
if __name__ == '__main__':
LLMQHPMNTest().main()

View File

@ -24,17 +24,17 @@ class NewQuorumTypeActivationTest(BitcoinTestFramework):
self.nodes[0].generate(9)
assert_equal(get_bip9_status(self.nodes[0], 'dip0020')['status'], 'started')
ql = self.nodes[0].quorum("list")
assert_equal(len(ql), 2)
assert_equal(len(ql), 3)
assert "llmq_test_v17" not in ql
self.nodes[0].generate(10)
assert_equal(get_bip9_status(self.nodes[0], 'dip0020')['status'], 'locked_in')
ql = self.nodes[0].quorum("list")
assert_equal(len(ql), 2)
assert_equal(len(ql), 3)
assert "llmq_test_v17" not in ql
self.nodes[0].generate(10)
assert_equal(get_bip9_status(self.nodes[0], 'dip0020')['status'], 'active')
ql = self.nodes[0].quorum("list")
assert_equal(len(ql), 3)
assert_equal(len(ql), 4)
assert "llmq_test_v17" in ql

View File

@ -87,8 +87,8 @@ class HTTPBasicsTest(BitcoinTestFramework):
test_command("getblockhash", [0], rpcuser_authpair_platform, 200)
test_command("getblockcount", [], rpcuser_authpair_platform, 200)
test_command("getbestchainlock", [], rpcuser_authpair_platform, 500)
test_command("quorum", ["sign", 100], rpcuser_authpair_platform, 500)
test_command("quorum", ["sign", 100, "0000000000000000000000000000000000000000000000000000000000000000",
test_command("quorum", ["sign", 106], rpcuser_authpair_platform, 500)
test_command("quorum", ["sign", 106, "0000000000000000000000000000000000000000000000000000000000000000",
"0000000000000000000000000000000000000000000000000000000000000001"],
rpcuser_authpair_platform, 200)
test_command("quorum", ["verify"], rpcuser_authpair_platform, 500)

View File

@ -7,6 +7,7 @@
import configparser
import copy
from _decimal import Decimal
from enum import Enum
import logging
import argparse
@ -32,6 +33,7 @@ from .messages import (
ser_compact_size,
ser_string,
)
from .script import hash160
from .test_node import TestNode
from .mininode import NetworkThread
from .util import (
@ -49,7 +51,7 @@ from .util import (
set_timeout_scale,
satoshi_round,
wait_until,
get_chain_folder,
get_chain_folder, rpc_port,
)
@ -469,6 +471,75 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
use_valgrind=self.options.valgrind,
))
def add_dynamically_node(self, extra_args=None, *, rpchost=None, binary=None):
if self.bind_to_localhost_only:
extra_confs = [["bind=127.0.0.1"]]
else:
extra_confs = [[]]
if extra_args is None:
extra_args = [[]]
if binary is None:
binary = [self.options.bitcoind]
assert_equal(len(extra_confs), 1)
assert_equal(len(binary), 1)
old_num_nodes = len(self.nodes)
p0 = old_num_nodes
p1 = get_datadir_path(self.options.tmpdir, old_num_nodes)
p2 = self.extra_args_from_options
p3 = self.chain
p4 = rpchost
p5 = self.rpc_timeout
p6 = self.options.timeout_factor
p7 = binary[0]
p8 = self.options.bitcoincli
p9 = self.mocktime
p10 = self.options.coveragedir
p11 = self.options.tmpdir
p12 = extra_confs[0]
p13 = extra_args
p14 = self.options.usecli
p15 = self.options.perf
p16 = self.options.valgrind
t_node = TestNode(p0, p1, p2, chain=p3, rpchost=p4, timewait=p5, timeout_factor=p6, bitcoind=p7, bitcoin_cli=p8, mocktime=p9, coverage_dir=p10, cwd=p11, extra_conf=p12, extra_args=p13, use_cli=p14, start_perf=p15, use_valgrind=p16)
self.nodes.append(t_node)
return t_node
def dynamically_initialize_datadir(self, chain, node_p2p_port, node_rpc_port):
data_dir = get_datadir_path(self.options.tmpdir, len(self.nodes))
if not os.path.isdir(data_dir):
os.makedirs(data_dir)
# Translate chain name to config name
if chain == 'testnet3':
chain_name_conf_arg = 'testnet'
chain_name_conf_section = 'test'
chain_name_conf_arg_value = '1'
elif chain == 'devnet':
chain_name_conf_arg = 'devnet'
chain_name_conf_section = 'devnet'
chain_name_conf_arg_value = 'devnet1'
else:
chain_name_conf_arg = chain
chain_name_conf_section = chain
chain_name_conf_arg_value = '1'
with open(os.path.join(data_dir, "dash.conf"), 'w', encoding='utf8') as f:
f.write("{}={}\n".format(chain_name_conf_arg, chain_name_conf_arg_value))
f.write("[{}]\n".format(chain_name_conf_section))
f.write("port=" + str(node_p2p_port) + "\n")
f.write("rpcport=" + str(node_rpc_port) + "\n")
f.write("server=1\n")
f.write("debug=1\n")
f.write("keypool=1\n")
f.write("discover=0\n")
f.write("listenonion=0\n")
f.write("printtoconsole=0\n")
f.write("upnp=0\n")
f.write("natpmp=0\n")
f.write("shrinkdebugfile=0\n")
os.makedirs(os.path.join(data_dir, 'stderr'), exist_ok=True)
os.makedirs(os.path.join(data_dir, 'stdout'), exist_ok=True)
def start_node(self, i, *args, **kwargs):
"""Start a dashd"""
@ -826,10 +897,10 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
MASTERNODE_COLLATERAL = 1000
HIGHPERFORMANCE_MASTERNODE_COLLATERAL = 4000
class MasternodeInfo:
def __init__(self, proTxHash, ownerAddr, votingAddr, pubKeyOperator, keyOperator, collateral_address, collateral_txid, collateral_vout):
def __init__(self, proTxHash, ownerAddr, votingAddr, pubKeyOperator, keyOperator, collateral_address, collateral_txid, collateral_vout, addr, hpmn=False):
self.proTxHash = proTxHash
self.ownerAddr = ownerAddr
self.votingAddr = votingAddr
@ -838,6 +909,8 @@ class MasternodeInfo:
self.collateral_address = collateral_address
self.collateral_txid = collateral_txid
self.collateral_vout = collateral_vout
self.addr = addr
self.hpmn = hpmn
class DashTestFramework(BitcoinTestFramework):
@ -852,8 +925,9 @@ class DashTestFramework(BitcoinTestFramework):
"""Tests must override this method to define test logic"""
raise NotImplementedError
def set_dash_test_params(self, num_nodes, masterodes_count, extra_args=None, fast_dip3_enforcement=False):
def set_dash_test_params(self, num_nodes, masterodes_count, extra_args=None, fast_dip3_enforcement=False, hpmn_count=0):
self.mn_count = masterodes_count
self.hpmn_count = hpmn_count
self.num_nodes = num_nodes
self.mninfo = []
self.setup_clean_chain = True
@ -953,29 +1027,100 @@ class DashTestFramework(BitcoinTestFramework):
for i in range(0, idx):
self.connect_nodes(i, idx)
def dynamically_add_masternode(self, hpmn=False):
mn_idx = len(self.nodes)
node_p2p_port = p2p_port(mn_idx)
node_rpc_port = rpc_port(mn_idx)
created_mn_info = self.dynamically_prepare_masternode(mn_idx, node_p2p_port, hpmn)
self.dynamically_initialize_datadir(self.nodes[0].chain,node_p2p_port, node_rpc_port)
node_info = self.add_dynamically_node(self.extra_args[1])
args = ['-masternodeblsprivkey=%s' % created_mn_info.keyOperator] + node_info.extra_args
self.start_node(mn_idx, args)
for mn_info in self.mninfo:
if mn_info.proTxHash == created_mn_info.proTxHash:
mn_info.nodeIx = mn_idx
mn_info.node = self.nodes[mn_idx]
self.connect_nodes(mn_idx, 0)
self.wait_for_sporks_same()
self.sync_blocks(self.nodes)
force_finish_mnsync(self.nodes[mn_idx])
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, hpmn=False):
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()
platform_node_id = hash160(b'%d' % node_p2p_port).hex()
platform_p2p_port = '%d' % (node_p2p_port + 101) if hpmn else ''
platform_http_port = '%d' % (node_p2p_port + 102) if hpmn else ''
collateral_amount = 4000 if hpmn else 1000
collateral_txid = self.nodes[0].sendtoaddress(collateral_address, collateral_amount)
# send to same address to reserve some funds for fees
self.nodes[0].sendtoaddress(funds_address, 1)
collateral_vout = 0
self.nodes[0].generate(1)
self.sync_all(self.nodes)
rawtx = self.nodes[0].getrawtransaction(collateral_txid, 1)
for txout in rawtx['vout']:
if txout['value'] == Decimal(collateral_amount):
collateral_vout = txout['n']
break
assert collateral_vout is not None
ipAndPort = '127.0.0.1:%d' % node_p2p_port
operatorReward = idx
self.nodes[0].generate(1)
register_rpc = 'register_hpmn' if hpmn else 'register'
protx_result = self.nodes[0].protx(register_rpc, 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)
self.nodes[0].generate(1)
self.sync_all(self.nodes)
mn_info = MasternodeInfo(protx_result, owner_address, voting_address, bls['public'], bls['secret'], collateral_address, collateral_txid, collateral_vout, ipAndPort, hpmn)
self.mninfo.append(mn_info)
mn_type_str = "HPMN" if hpmn else "MN"
self.log.info("Prepared %s %d: collateral_txid=%s, collateral_vout=%d, protxHash=%s" % (mn_type_str, idx, collateral_txid, collateral_vout, protx_result))
return mn_info
def prepare_masternodes(self):
self.log.info("Preparing %d masternodes" % self.mn_count)
rewardsAddr = self.nodes[0].getnewaddress()
for idx in range(0, self.mn_count):
self.prepare_masternode(idx, rewardsAddr)
self.prepare_masternode(idx, rewardsAddr, False)
self.sync_all()
def prepare_masternode(self, idx, rewardsAddr=None):
def prepare_masternode(self, idx, rewardsAddr=None, hpmn=False):
register_fund = (idx % 2) == 0
bls = self.nodes[0].bls('generate')
address = self.nodes[0].getnewaddress()
collateral_amount = HIGHPERFORMANCE_MASTERNODE_COLLATERAL if hpmn else MASTERNODE_COLLATERAL
txid = None
txid = self.nodes[0].sendtoaddress(address, MASTERNODE_COLLATERAL)
txid = self.nodes[0].sendtoaddress(address, collateral_amount)
collateral_vout = 0
if not register_fund:
txraw = self.nodes[0].getrawtransaction(txid, True)
for vout_idx in range(0, len(txraw["vout"])):
vout = txraw["vout"][vout_idx]
if vout["value"] == MASTERNODE_COLLATERAL:
if vout["value"] == collateral_amount:
collateral_vout = vout_idx
self.nodes[0].lockunspent(False, [{'txid': txid, 'vout': collateral_vout}])
@ -1007,16 +1152,16 @@ class DashTestFramework(BitcoinTestFramework):
else:
proTxHash = self.nodes[0].sendrawtransaction(protx_result)
if operatorReward > 0:
self.nodes[0].generate(1)
operatorPayoutAddress = self.nodes[0].getnewaddress()
self.nodes[0].protx('update_service', proTxHash, ipAndPort, bls['secret'], operatorPayoutAddress, address)
self.mninfo.append(MasternodeInfo(proTxHash, ownerAddr, votingAddr, bls['public'], bls['secret'], address, txid, collateral_vout))
self.mninfo.append(MasternodeInfo(proTxHash, ownerAddr, votingAddr, bls['public'], bls['secret'], address, txid, collateral_vout, ipAndPort, hpmn))
# self.sync_all()
self.log.info("Prepared masternode %d: collateral_txid=%s, collateral_vout=%d, protxHash=%s" % (idx, txid, collateral_vout, proTxHash))
mn_type_str = "HPMN" if hpmn else "MN"
self.log.info("Prepared %s %d: collateral_txid=%s, collateral_vout=%d, protxHash=%s" % (mn_type_str, idx, txid, collateral_vout, proTxHash))
def remove_masternode(self, idx):
mn = self.mninfo[idx]
@ -1084,12 +1229,20 @@ class DashTestFramework(BitcoinTestFramework):
mninfo.node = self.nodes[mninfo.nodeIdx]
force_finish_mnsync(mninfo.node)
def dynamically_start_masternode(self, mnidx, extra_args=None):
args = []
if extra_args is not None:
args += extra_args
self.start_node(mnidx, extra_args=args)
force_finish_mnsync(self.nodes[mnidx])
def setup_network(self):
self.log.info("Creating and starting controller node")
self.add_nodes(1, extra_args=[self.extra_args[0]])
self.start_node(0)
self.import_deterministic_coinbase_privkeys()
required_balance = MASTERNODE_COLLATERAL * self.mn_count + 1
required_balance = HIGHPERFORMANCE_MASTERNODE_COLLATERAL * self.hpmn_count
required_balance += MASTERNODE_COLLATERAL * (self.mn_count - self.hpmn_count) + 1
self.log.info("Generating %d coins" % required_balance)
while self.nodes[0].getbalance() < required_balance:
self.bump_mocktime(1)
@ -1248,6 +1401,7 @@ class DashTestFramework(BitcoinTestFramework):
def wait_for_sporks_same(self, timeout=30):
def check_sporks_same():
self.bump_mocktime(1)
sporks = self.nodes[0].spork('show')
return all(node.spork('show') == sporks for node in self.nodes[1:])
wait_until(check_sporks_same, timeout=timeout, sleep=0.5)

View File

@ -111,6 +111,7 @@ BASE_SCRIPTS = [
'feature_llmq_chainlocks.py', # NOTE: needs dash_hash to pass
'feature_llmq_rotation.py', # NOTE: needs dash_hash to pass
'feature_llmq_connections.py', # NOTE: needs dash_hash to pass
'feature_llmq_hpmn.py', # NOTE: needs dash_hash to pass
'feature_llmq_simplepose.py', # NOTE: needs dash_hash to pass
'feature_llmq_is_cl_conflicts.py', # NOTE: needs dash_hash to pass
'feature_llmq_is_migration.py', # NOTE: needs dash_hash to pass