mirror of
https://github.com/dashpay/dash.git
synced 2024-12-25 03:52:49 +01:00
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:
parent
c8a7e69015
commit
aa8462b060
18
doc/release-notes-5039.md
Normal file
18
doc/release-notes-5039.md
Normal 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"`
|
@ -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 \
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
};
|
||||
|
@ -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);
|
||||
|
@ -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
45
src/evo/dmn_types.h
Normal 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
|
@ -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);
|
||||
|
@ -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};
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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
|
||||
|
@ -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());
|
||||
}
|
||||
|
||||
|
@ -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());
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
}
|
||||
|
305
src/rpc/evo.cpp
305
src/rpc/evo.cpp
@ -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") {
|
||||
|
@ -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)));
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
212
test/functional/feature_llmq_hpmn.py
Executable file
212
test/functional/feature_llmq_hpmn.py
Executable 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()
|
@ -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
|
||||
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user