feat: auto generation EHF and spork+EHF activation for MN_RR (#5597)

Implementation EHF mechanism, part 4. Previous changes are: 
 - https://github.com/dashpay/dash/pull/4577
 - https://github.com/dashpay/dash/pull/5505
 - https://github.com/dashpay/dash/pull/5469

## Issue being fixed or feature implemented
Currently MN_RR is activated automatically by soft-fork activation after
v20 is activated.
It is not flexible enough, because platform may not be released by that
time yet or in opposite it can be too long to wait.
Also, any signal of EHF requires manual actions from MN owners to sign
EHF signal - it is automated here.

## What was done?
New spork `SPORK_24_MN_RR_READY`; new EHF manager that sign EHF signals
semi-automatically without manual actions; and send transaction with EHF
signal when signal is signed to network.
Updated rpc `getblockchaininfo` to return information about of EHF
activated forks.
Fixed function `IsTxSafeForMining` in chainlock's handler to skip
transactions without inputs (empty `vin`).

## How Has This Been Tested?
Run unit/functional tests. Some tests have been updated due to new way
of MN_RR activation: `feature_asset_locks.py`, `feature_mnehf.py`,
`feature_llmq_evo.py` and unit test `block_reward_reallocation_tests`.


## Breaking Changes
New way of MN_RR activation.

## 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
- [ ] I have made corresponding changes to the documentation
- [x] I have assigned this pull request to a milestone _(for repository
code-owners and collaborators only)_

---------

Co-authored-by: UdjinM6 <UdjinM6@users.noreply.github.com>
Co-authored-by: PastaPastaPasta <6443210+PastaPastaPasta@users.noreply.github.com>
This commit is contained in:
Konstantin Akimov 2023-10-18 10:31:40 +07:00 committed by GitHub
parent cecf63e0b7
commit 63ed462c54
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 304 additions and 35 deletions

View File

@ -213,22 +213,24 @@ BITCOIN_CORE_H = \
key_io.h \
dbwrapper.h \
limitedmap.h \
llmq/quorums.h \
llmq/blockprocessor.h \
llmq/commitment.h \
llmq/chainlocks.h \
llmq/clsig.h \
llmq/commitment.h \
llmq/context.h \
llmq/debug.h \
llmq/dkgsession.h \
llmq/dkgsessionhandler.h \
llmq/dkgsessionmgr.h \
llmq/dkgsession.h \
llmq/context.h \
llmq/ehf_signals.cpp \
llmq/ehf_signals.h \
llmq/instantsend.h \
llmq/snapshot.h \
llmq/params.h \
llmq/quorums.h \
llmq/signing.h \
llmq/signing_shares.h \
llmq/snapshot.h \
llmq/utils.h \
llmq/params.h \
logging.h \
logging/timer.h \
mapport.h \

View File

@ -219,10 +219,11 @@ public:
consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].bit = 10;
consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nStartTime = 19999999999; // TODO: To be determined later
consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nTimeout = Consensus::BIP9Deployment::NO_TIMEOUT;
consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nWindowSize = 4032;
consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nWindowSize = 4032; // TODO to be determined before v20 release: choose nWindowSize/nThresholdStart/nThresholdMin
consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nThresholdStart = 3226; // 80% of 4032
consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nThresholdMin = 2420; // 60% of 4032
consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nFalloffCoeff = 5; // this corresponds to 10 periods
consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nMNActivationHeight = 0; // requires EHF activation
// The best chain should have at least this much work.
consensus.nMinimumChainWork = uint256S("0x000000000000000000000000000000000000000000008677827656704520eb39"); // 1889000
@ -420,6 +421,7 @@ public:
consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nThresholdStart = 80; // 80% of 100
consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nThresholdMin = 60; // 60% of 100
consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nFalloffCoeff = 5; // this corresponds to 10 periods
consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nMNActivationHeight = 0; // requires EHF activation
// The best chain should have at least this much work.
consensus.nMinimumChainWork = uint256S("0x00000000000000000000000000000000000000000000000002d68c8cc1b8e54b"); // 851000
@ -591,6 +593,7 @@ public:
consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nThresholdStart = 80; // 80% of 100
consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nThresholdMin = 60; // 60% of 100
consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nFalloffCoeff = 5; // this corresponds to 10 periods
consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nMNActivationHeight = 0; // requires EHF activation
// The best chain should have at least this much work.
consensus.nMinimumChainWork = uint256S("0x000000000000000000000000000000000000000000000000000000000000000");
@ -826,10 +829,11 @@ public:
consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].bit = 10;
consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nStartTime = 0;
consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nTimeout = Consensus::BIP9Deployment::NO_TIMEOUT;
consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nWindowSize = 1030;
consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nThresholdStart = 800; // 80% of 1000
consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nThresholdMin = 600; // 60% of 1000
consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nWindowSize = 12;
consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nThresholdStart = 9; // 80% of 12
consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nThresholdMin = 7; // 60% of 7
consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nFalloffCoeff = 5; // this corresponds to 10 periods
consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nMNActivationHeight = 0; // requires EHF activation
// The best chain should have at least this much work.
consensus.nMinimumChainWork = uint256S("0x00");

View File

@ -19,6 +19,7 @@
#include <llmq/chainlocks.h>
#include <llmq/context.h>
#include <llmq/dkgsessionmgr.h>
#include <llmq/ehf_signals.h>
#include <llmq/instantsend.h>
#include <llmq/quorums.h>
@ -78,6 +79,7 @@ void CDSNotificationInterface::UpdatedBlockTip(const CBlockIndex *pindexNew, con
llmq_ctx->qman->UpdatedBlockTip(pindexNew, fInitialDownload);
llmq_ctx->qdkgsman->UpdatedBlockTip(pindexNew, fInitialDownload);
llmq_ctx->ehfSignalsHandler->UpdatedBlockTip(pindexNew);
if (!fDisableGovernance) govman.UpdatedBlockTip(pindexNew, connman);
}

View File

@ -19,9 +19,24 @@
#include <string>
#include <vector>
extern const std::string MNEHF_REQUESTID_PREFIX = "mnhf";
static const std::string MNEHF_REQUESTID_PREFIX = "mnhf";
static const std::string DB_SIGNALS = "mnhf_s";
uint256 MNHFTxPayload::GetRequestId() const
{
return ::SerializeHash(std::make_pair(MNEHF_REQUESTID_PREFIX, int64_t{signal.versionBit}));
}
CMutableTransaction MNHFTxPayload::PrepareTx() const
{
CMutableTransaction tx;
tx.nVersion = 3;
tx.nType = SPECIALTX_TYPE;
SetTxPayload(tx, *this);
return tx;
}
CMNHFManager::Signals CMNHFManager::GetSignalsStage(const CBlockIndex* const pindexPrev)
{
Signals signals = GetFromCache(pindexPrev);
@ -53,7 +68,7 @@ CMNHFManager::Signals CMNHFManager::GetSignalsStage(const CBlockIndex* const pin
return signals;
}
bool MNHFTx::Verify(const uint256& quorumHash, const uint256& msgHash, TxValidationState& state) const
bool MNHFTx::Verify(const uint256& quorumHash, const uint256& requestId, const uint256& msgHash, TxValidationState& state) const
{
if (versionBit >= VERSIONBITS_NUM_BITS) {
return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-mnhf-nbit-out-of-bounds");
@ -62,7 +77,6 @@ bool MNHFTx::Verify(const uint256& quorumHash, const uint256& msgHash, TxValidat
const Consensus::LLMQType& llmqType = Params().GetConsensus().llmqTypeMnhf;
const auto quorum = llmq::quorumManager->GetQuorum(llmqType, quorumHash);
const uint256 requestId = ::SerializeHash(std::make_pair(MNEHF_REQUESTID_PREFIX, int64_t{versionBit}));
const uint256 signHash = llmq::utils::BuildSignHash(llmqType, quorum->qc->quorumHash, requestId, msgHash);
if (!sig.VerifyInsecure(quorum->qc->quorumPublicKey, signHash)) {
return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-mnhf-invalid");
@ -104,7 +118,7 @@ bool CheckMNHFTx(const CTransaction& tx, const CBlockIndex* pindexPrev, TxValida
uint256 msgHash = tx_copy.GetHash();
if (!mnhfTx.signal.Verify(mnhfTx.signal.quorumHash, msgHash, state)) {
if (!mnhfTx.signal.Verify(mnhfTx.signal.quorumHash, mnhfTx.GetRequestId(), msgHash, state)) {
// set up inside Verify
return false;
}

View File

@ -32,7 +32,7 @@ public:
CBLSSignature sig{};
MNHFTx() = default;
bool Verify(const uint256& quorumHash, const uint256& msgHash, TxValidationState& state) const;
bool Verify(const uint256& quorumHash, const uint256& requestId, const uint256& msgHash, TxValidationState& state) const;
SERIALIZE_METHODS(MNHFTx, obj)
{
@ -63,6 +63,17 @@ public:
uint8_t nVersion{CURRENT_VERSION};
MNHFTx signal;
public:
/**
* helper function to calculate Request ID used for signing
*/
uint256 GetRequestId() const;
/**
* helper function to prepare special transaction for signing
*/
CMutableTransaction PrepareTx() const;
SERIALIZE_METHODS(MNHFTxPayload, obj)
{
READWRITE(obj.nVersion, obj.signal);
@ -120,6 +131,7 @@ public:
* This member function is not const because it calls non-const GetFromCache()
*/
Signals GetSignalsStage(const CBlockIndex* const pindexPrev);
private:
void AddToCache(const Signals& signals, const CBlockIndex* const pindex);
@ -129,7 +141,6 @@ private:
* validate them by
*/
Signals GetFromCache(const CBlockIndex* const pindex);
};
std::optional<uint8_t> extractEHFSignal(const CTransaction& tx);

View File

@ -12,6 +12,7 @@
#include <llmq/commitment.h>
#include <llmq/debug.h>
#include <llmq/dkgsessionmgr.h>
#include <llmq/ehf_signals.h>
#include <llmq/instantsend.h>
#include <llmq/quorums.h>
#include <llmq/signing.h>
@ -45,7 +46,8 @@ LLMQContext::LLMQContext(CChainState& chainstate, CConnman& connman, CEvoDB& evo
assert(llmq::quorumInstantSendManager == nullptr);
llmq::quorumInstantSendManager = std::make_unique<llmq::CInstantSendManager>(*llmq::chainLocksHandler, chainstate, connman, *llmq::quorumManager, *sigman, *shareman, sporkman, mempool, *::masternodeSync, peerman, unit_tests, wipe);
return llmq::quorumInstantSendManager.get();
}()}
}()},
ehfSignalsHandler{std::make_unique<llmq::CEHFSignalsHandler>(chainstate, connman, *sigman, *shareman, sporkman, *llmq::quorumManager, mempool)}
{
// NOTE: we use this only to wipe the old db, do NOT use it for anything else
// TODO: remove it in some future version

View File

@ -12,25 +12,27 @@ class CChainState;
class CConnman;
class CDBWrapper;
class CEvoDB;
class CTxMemPool;
class CSporkManager;
class CTxMemPool;
class PeerManager;
namespace llmq {
class CChainLocksHandler;
class CDKGDebugManager;
class CQuorumBlockProcessor;
class CDKGSessionManager;
class CEHFSignalsHandler;
class CInstantSendManager;
class CQuorumBlockProcessor;
class CQuorumManager;
class CSigSharesManager;
class CSigningManager;
class CChainLocksHandler;
class CInstantSendManager;
}
struct LLMQContext {
LLMQContext() = delete;
LLMQContext(const LLMQContext&) = delete;
LLMQContext(CChainState& chainstate, CConnman& connman, CEvoDB& evo_db, CSporkManager& sporkman, CTxMemPool& mempool,
LLMQContext(CChainState& chainstate, CConnman& connman, CEvoDB& evo_db, CSporkManager& sporkman,
CTxMemPool& mempool,
const std::unique_ptr<PeerManager>& peerman, bool unit_tests, bool wipe);
~LLMQContext();
@ -57,6 +59,7 @@ struct LLMQContext {
const std::unique_ptr<llmq::CSigSharesManager> shareman;
llmq::CChainLocksHandler* const clhandler;
llmq::CInstantSendManager* const isman;
const std::unique_ptr<llmq::CEHFSignalsHandler> ehfSignalsHandler;
};
#endif // BITCOIN_LLMQ_CONTEXT_H

132
src/llmq/ehf_signals.cpp Normal file
View File

@ -0,0 +1,132 @@
// Copyright (c) 2023 The Dash Core developers
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <llmq/ehf_signals.h>
#include <llmq/utils.h>
#include <llmq/quorums.h>
#include <llmq/signing_shares.h>
#include <llmq/commitment.h>
#include <evo/mnhftx.h>
#include <evo/specialtx.h>
#include <index/txindex.h> // g_txindex
#include <primitives/transaction.h>
#include <spork.h>
#include <txmempool.h>
#include <validation.h>
namespace llmq {
CEHFSignalsHandler::CEHFSignalsHandler(CChainState& chainstate, CConnman& connman,
CSigningManager& sigman, CSigSharesManager& shareman,
const CSporkManager& sporkman, const CQuorumManager& qman, CTxMemPool& mempool) :
chainstate(chainstate),
connman(connman),
sigman(sigman),
shareman(shareman),
sporkman(sporkman),
qman(qman),
mempool(mempool)
{
sigman.RegisterRecoveredSigsListener(this);
}
CEHFSignalsHandler::~CEHFSignalsHandler()
{
sigman.UnregisterRecoveredSigsListener(this);
}
void CEHFSignalsHandler::UpdatedBlockTip(const CBlockIndex* const pindexNew)
{
if (!fMasternodeMode || !llmq::utils::IsV20Active(pindexNew) || !sporkman.IsSporkActive(SPORK_24_EHF)) {
return;
}
// TODO: should do this for all not-yet-signied bits
trySignEHFSignal(Params().GetConsensus().vDeployments[Consensus::DEPLOYMENT_MN_RR].bit, pindexNew);
}
void CEHFSignalsHandler::trySignEHFSignal(int bit, const CBlockIndex* const pindex)
{
MNHFTxPayload mnhfPayload;
mnhfPayload.signal.versionBit = bit;
const uint256 requestId = mnhfPayload.GetRequestId();
LogPrintf("CEHFSignalsHandler::trySignEHFSignal: bit=%d at height=%d id=%s\n", bit, pindex->nHeight, requestId.ToString());
const Consensus::LLMQType& llmqType = Params().GetConsensus().llmqTypeMnhf;
const auto& llmq_params_opt = llmq::GetLLMQParams(llmqType);
if (!llmq_params_opt.has_value()) {
return;
}
if (sigman.HasRecoveredSigForId(llmqType, requestId)) {
LOCK(cs);
ids.insert(requestId);
// no need to sign same message one more time
return;
}
const auto quorum = sigman.SelectQuorumForSigning(llmq_params_opt.value(), qman, requestId);
if (!quorum) {
LogPrintf("CEHFSignalsHandler::trySignEHFSignal no quorum for id=%s\n", requestId.ToString());
return;
}
mnhfPayload.signal.quorumHash = quorum->qc->quorumHash;
const uint256 msgHash = mnhfPayload.PrepareTx().GetHash();
{
LOCK(cs);
ids.insert(requestId);
}
sigman.AsyncSignIfMember(llmqType, shareman, requestId, msgHash);
}
void CEHFSignalsHandler::HandleNewRecoveredSig(const CRecoveredSig& recoveredSig)
{
if (g_txindex) {
g_txindex->BlockUntilSyncedToCurrentChain();
}
if (WITH_LOCK(cs, return ids.find(recoveredSig.getId()) == ids.end())) {
// Do nothing, it's not for this handler
return;
}
MNHFTxPayload mnhfPayload;
// TODO: should do this for all not-yet-signied bits
mnhfPayload.signal.versionBit = Params().GetConsensus().vDeployments[Consensus::DEPLOYMENT_MN_RR].bit;
const uint256 expectedId = mnhfPayload.GetRequestId();
LogPrintf("CEHFSignalsHandler::HandleNewRecoveredSig expecting ID=%s received=%s\n", expectedId.ToString(), recoveredSig.getId().ToString());
if (recoveredSig.getId() != mnhfPayload.GetRequestId()) {
// there's nothing interesting for CEHFSignalsHandler
LogPrintf("CEHFSignalsHandler::HandleNewRecoveredSig id is known but it's not MN_RR, expected: %s\n", mnhfPayload.GetRequestId().ToString());
return;
}
mnhfPayload.signal.quorumHash = recoveredSig.getQuorumHash();
mnhfPayload.signal.sig = recoveredSig.sig.Get();
CMutableTransaction tx = mnhfPayload.PrepareTx();
{
CTransactionRef tx_to_sent = MakeTransactionRef(std::move(tx));
LogPrintf("CEHFSignalsHandler::HandleNewRecoveredSig Special EHF TX is created hash=%s\n", tx_to_sent->GetHash().ToString());
LOCK(cs_main);
TxValidationState state;
if (AcceptToMemoryPool(chainstate, mempool, state, tx_to_sent, /* bypass_limits=*/ false, /* nAbsurdFee=*/ 0)) {
connman.RelayTransaction(*tx_to_sent);
} else {
LogPrintf("CEHFSignalsHandler::HandleNewRecoveredSig -- AcceptToMemoryPool failed: %s\n", state.ToString());
}
}
}
} // namespace llmq

61
src/llmq/ehf_signals.h Normal file
View File

@ -0,0 +1,61 @@
// Copyright (c) 2023 The Dash Core developers
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#ifndef BITCOIN_LLMQ_EHF_SIGNALS_H
#define BITCOIN_LLMQ_EHF_SIGNALS_H
#include <llmq/signing.h>
#include <set>
class CBlockIndex;
class CChainState;
class CConnman;
class CSporkManager;
class CTxMemPool;
namespace llmq
{
class CQuorumManager;
class CSigSharesManager;
class CSigningManager;
class CEHFSignalsHandler : public CRecoveredSigsListener
{
private:
CChainState& chainstate;
CConnman& connman;
CSigningManager& sigman;
CSigSharesManager& shareman;
const CSporkManager& sporkman;
const CQuorumManager& qman;
CTxMemPool& mempool;
/**
* keep freshly generated IDs for easier filter sigs in HandleNewRecoveredSig
*/
mutable Mutex cs;
std::set<uint256> ids GUARDED_BY(cs);
public:
explicit CEHFSignalsHandler(CChainState& chainstate, CConnman& connman,
CSigningManager& sigman, CSigSharesManager& shareman,
const CSporkManager& sporkman, const CQuorumManager& qman, CTxMemPool& mempool);
~CEHFSignalsHandler();
/**
* Since Tip is updated it could be a time to generate EHF Signal
*/
void UpdatedBlockTip(const CBlockIndex* const pindexNew);
void HandleNewRecoveredSig(const CRecoveredSig& recoveredSig) override LOCKS_EXCLUDED(cs);
private:
void trySignEHFSignal(int bit, const CBlockIndex* const pindex) LOCKS_EXCLUDED(cs);
};
} // namespace llmq
#endif // BITCOIN_LLMQ_EHF_SIGNALS_H

View File

@ -311,7 +311,7 @@ bool BlockAssembler::TestPackageTransactions(const CTxMemPool::setEntries& packa
const auto& txid = it->GetTx().GetHash();
if (!m_isman.RejectConflictingBlocks() || !m_isman.IsInstantSendEnabled() || m_isman.IsLocked(txid)) continue;
if (!m_clhandler.IsTxSafeForMining(txid)) {
if (!it->GetTx().vin.empty() && !m_clhandler.IsTxSafeForMining(txid)) {
return false;
}
}

View File

@ -1614,6 +1614,7 @@ static void BIP9SoftForkDescPushBack(const CBlockIndex* active_chain_tip, UniVal
}
bip9.pushKV("start_time", consensusParams.vDeployments[id].nStartTime);
bip9.pushKV("timeout", consensusParams.vDeployments[id].nTimeout);
bip9.pushKV("ehf", consensusParams.vDeployments[id].nMNActivationHeight);
int64_t since_height = VersionBitsStateSinceHeight(active_chain_tip, consensusParams, id, versionbitscache);
bip9.pushKV("since", since_height);
if (ThresholdState::STARTED == thresholdState)

View File

@ -41,6 +41,7 @@ enum SporkId : int32_t {
SPORK_19_CHAINLOCKS_ENABLED = 10018,
SPORK_21_QUORUM_ALL_CONNECTED = 10020,
SPORK_23_QUORUM_POSE = 10022,
SPORK_24_EHF = 10023,
SPORK_INVALID = -1,
};
@ -66,7 +67,7 @@ struct CSporkDef
};
#define MAKE_SPORK_DEF(name, defaultValue) CSporkDef{name, defaultValue, #name}
[[maybe_unused]] static constexpr std::array<CSporkDef, 7> sporkDefs = {
[[maybe_unused]] static constexpr std::array<CSporkDef, 8> sporkDefs = {
MAKE_SPORK_DEF(SPORK_2_INSTANTSEND_ENABLED, 4070908800ULL), // OFF
MAKE_SPORK_DEF(SPORK_3_INSTANTSEND_BLOCK_FILTERING, 4070908800ULL), // OFF
MAKE_SPORK_DEF(SPORK_9_SUPERBLOCKS_ENABLED, 4070908800ULL), // OFF
@ -74,6 +75,7 @@ struct CSporkDef
MAKE_SPORK_DEF(SPORK_19_CHAINLOCKS_ENABLED, 4070908800ULL), // OFF
MAKE_SPORK_DEF(SPORK_21_QUORUM_ALL_CONNECTED, 4070908800ULL), // OFF
MAKE_SPORK_DEF(SPORK_23_QUORUM_POSE, 4070908800ULL), // OFF
MAKE_SPORK_DEF(SPORK_24_EHF, 4070908800ULL), // OFF
};
#undef MAKE_SPORK_DEF
extern std::unique_ptr<CSporkManager> sporkManager;

View File

@ -40,7 +40,7 @@ using SimpleUTXOMap = std::map<COutPoint, std::pair<int, CAmount>>;
struct TestChainBRRBeforeActivationSetup : public TestChainSetup
{
// Force fast DIP3 activation
TestChainBRRBeforeActivationSetup() : TestChainSetup(497, {"-dip3params=30:50"}) {}
TestChainBRRBeforeActivationSetup() : TestChainSetup(497, {"-dip3params=30:50", "-vbparams=mn_rr:0:999999999999:20:16:12:5:0"}) {}
};
static SimpleUTXOMap BuildSimpleUtxoMap(const std::vector<CTransactionRef>& txs)
@ -265,6 +265,9 @@ BOOST_FIXTURE_TEST_CASE(block_reward_reallocation, TestChainBRRBeforeActivationS
}
BOOST_CHECK(!llmq::utils::IsMNRewardReallocationActive(m_node.chainman->ActiveChain().Tip()));
// Activate EHF "MN_RR"
Params().UpdateMNActivationParam(Params().GetConsensus().vDeployments[Consensus::DEPLOYMENT_MN_RR].bit, ::ChainActive().Height(), ::ChainActive().Tip()->GetMedianTimePast(), /*fJustCheck=*/ false);
// Reward split should stay ~60/40 after reallocation is done,
// check 10 next superblocks
for ([[maybe_unused]] auto i : irange::range(10)) {

View File

@ -41,6 +41,7 @@ from test_framework.util import (
assert_equal,
assert_greater_than,
assert_greater_than_or_equal,
get_bip9_details,
hex_str_to_bytes,
)
@ -83,7 +84,6 @@ class AssetLocksTest(DashTestFramework):
lock_tx.vExtraPayload = lockTx_payload.serialize()
lock_tx = node_wallet.signrawtransactionwithwallet(lock_tx.serialize().hex())
self.log.info(f"next tx: {lock_tx} payload: {lockTx_payload}")
return FromHex(CTransaction(), lock_tx["hex"])
@ -295,8 +295,8 @@ class AssetLocksTest(DashTestFramework):
self.log.info("Mine a quorum...")
self.mine_quorum()
self.validate_credit_pool_balance(locked_1)
self.validate_credit_pool_balance(locked_1)
self.log.info("Testing asset unlock...")
@ -428,8 +428,8 @@ class AssetLocksTest(DashTestFramework):
self.log.info(f"Collecting coins in pool... Collected {total}/{10_900 * COIN}")
coin = coins.pop()
to_lock = int(coin['amount'] * COIN) - tiny_amount
if to_lock > 50 * COIN:
to_lock = 50 * COIN
if to_lock > 99 * COIN:
to_lock = 99 * COIN
total += to_lock
tx = self.create_assetlock(coin, to_lock, pubkey)
self.send_tx_simple(tx)
@ -455,6 +455,7 @@ class AssetLocksTest(DashTestFramework):
self.sync_mempools()
node.generate(1)
self.sync_all()
self.log.info(f"MN_RR status: {get_bip9_details(node, 'mn_rr')}")
new_total = self.get_credit_pool_balance()
amount_actually_withdrawn = total - new_total
@ -499,14 +500,17 @@ class AssetLocksTest(DashTestFramework):
node.generate(1)
self.sync_all()
self.log.info("generate many blocks to be sure that mempool is empty afterwards...")
self.log.info("generate many blocks to be sure that mempool is empty after expiring txes...")
self.slowly_generate_batch(60)
self.log.info("Checking that credit pool is not changed...")
assert_equal(new_total, self.get_credit_pool_balance())
self.check_mempool_size()
self.activate_mn_rr(expected_activation_height=3090)
# activate MN_RR reallocation
self.activate_mn_rr(expected_activation_height=node.getblockcount() + 12 * 3)
self.log.info(f'height: {node.getblockcount()} credit: {self.get_credit_pool_balance()}')
assert_equal(new_total, self.get_credit_pool_balance())
bt = node.getblocktemplate()
platform_reward = bt['masternode'][0]['amount']
assert_equal(bt['masternode'][0]['script'], '6a') # empty OP_RETURN
@ -515,7 +519,7 @@ class AssetLocksTest(DashTestFramework):
all_mn_rewards = platform_reward + owner_reward + operator_reward
assert_equal(all_mn_rewards, bt['coinbasevalue'] * 3 // 4) # 75/25 mn/miner reward split
assert_equal(platform_reward, all_mn_rewards * 375 // 1000) # 0.375 platform share
assert_equal(platform_reward, 25553999)
assert_equal(platform_reward, 29636590)
assert_equal(new_total, self.get_credit_pool_balance())
node.generate(1)
self.sync_all()

View File

@ -114,6 +114,7 @@ class LLMQEvoNodesTest(DashTestFramework):
self.log.info("Test that EvoNodes are paid 4x blocks in a row")
self.test_evo_payments(window_analysis=256)
self.activate_v20()
self.activate_mn_rr()
self.log.info("Activated MN RewardReallocation at height:" + str(self.nodes[0].getblockcount()))

View File

@ -160,11 +160,16 @@ class MnehfTest(DashTestFramework):
assert_equal(get_bip9_details(node, 'testdummy')['status'], 'defined')
self.activate_v20()
assert_equal(get_bip9_details(node, 'testdummy')['status'], 'defined')
assert_equal(get_bip9_details(node, 'mn_rr')['status'], 'defined')
ehf_tx_sent = self.send_tx(ehf_tx)
self.log.info(f"ehf tx: {ehf_tx_sent}")
ehf_unknown_tx_sent = self.send_tx(ehf_unknown_tx)
self.log.info(f"unknown ehf tx: {ehf_unknown_tx_sent}")
self.send_tx(ehf_invalid_tx, expected_error='bad-mnhf-non-ehf')
ehf_blockhash = node.generate(1)[0]
self.sync_all()
ehf_blockhash = self.nodes[1].generate(1)[0]
self.sync_blocks()
self.sync_all()
self.log.info(f"Check MnEhfTx {ehf_tx_sent} was mined in {ehf_blockhash}")
@ -251,6 +256,12 @@ class MnehfTest(DashTestFramework):
self.mine_quorum()
ehf_tx_new_start = self.create_mnehf(28, pubkey)
self.log.info("activate MN_RR also by enabling spork 24")
assert_equal(get_bip9_details(node, 'mn_rr')['status'], 'defined')
self.nodes[0].sporkupdate("SPORK_24_EHF", 0)
self.wait_for_sporks_same()
self.check_fork('defined')
self.log.info("Mine one block and ensure EHF tx for the new deployment is mined")
@ -263,6 +274,8 @@ class MnehfTest(DashTestFramework):
self.check_fork('defined')
self.slowly_generate_batch(12 * 4)
self.check_fork('active')
self.log.info(f"bip9: {get_bip9_details(node, 'mn_rr')}")
assert_equal(get_bip9_details(node, 'mn_rr')['status'], 'active')
if __name__ == '__main__':

View File

@ -146,7 +146,8 @@ class BlockchainTest(BitcoinTestFramework):
'status': 'defined',
'start_time': 0,
'timeout': 9223372036854775807,
'since': 0
'since': 0,
'ehf': -1,
}, 'active': False},
'mn_rr': {
'type': 'bip9',
@ -154,7 +155,8 @@ class BlockchainTest(BitcoinTestFramework):
'status': 'defined',
'start_time': 0,
'timeout': 9223372036854775807,
'since': 0
'since': 0,
'ehf': 0,
},
'active': False},
'testdummy': {
@ -172,6 +174,7 @@ class BlockchainTest(BitcoinTestFramework):
'count': 57,
'possible': True,
},
'ehf': -1,
},
'active': False},
})

View File

@ -44,6 +44,7 @@ from .util import (
check_json_precision,
copy_datadir,
force_finish_mnsync,
get_bip9_details,
get_datadir_path,
hex_str_to_bytes,
initialize_datadir,
@ -1070,6 +1071,7 @@ class DashTestFramework(BitcoinTestFramework):
self.sync_blocks()
def activate_by_name(self, name, expected_activation_height=None):
assert not softfork_active(self.nodes[0], name)
self.log.info("Wait for " + name + " activation")
# disable spork17 while mining blocks to activate "name" to prevent accidental quorum formation
@ -1082,6 +1084,7 @@ class DashTestFramework(BitcoinTestFramework):
batch_size = 10
if expected_activation_height is not None:
height = self.nodes[0].getblockcount()
assert height < expected_activation_height
# NOTE: getblockchaininfo shows softforks active at block (window * 3 - 1)
# since it's returning whether a softwork is active for the _next_ block.
# Hence the last block prior to the activation is (expected_activation_height - 2).
@ -1122,6 +1125,14 @@ class DashTestFramework(BitcoinTestFramework):
self.activate_by_name('v20', expected_activation_height)
def activate_mn_rr(self, expected_activation_height=None):
self.nodes[0].sporkupdate("SPORK_24_EHF", 0)
self.wait_for_sporks_same()
mn_rr_status = 0
while mn_rr_status == 0:
time.sleep(1)
mn_rr_status = get_bip9_details(self.nodes[0], 'mn_rr')['ehf']
self.nodes[0].generate(1)
self.sync_all()
self.activate_by_name('mn_rr', expected_activation_height)
def set_dash_llmq_test_params(self, llmq_size, llmq_threshold):