mirror of
https://github.com/dashpay/dash.git
synced 2024-12-25 03:52:49 +01:00
Merge pull request #5469 from knst/mnehf
feat!: masternode node hard-fork activation DIP-0023
This commit is contained in:
commit
ce9cfff0d6
@ -12,6 +12,7 @@
|
||||
#include <util/ranges.h>
|
||||
#include <util/system.h>
|
||||
#include <util/underlying.h>
|
||||
#include <versionbits.h>
|
||||
#include <versionbitsinfo.h>
|
||||
|
||||
#include <arith_uint256.h>
|
||||
@ -105,6 +106,32 @@ static CBlock FindDevNetGenesisBlock(const CBlock &prevBlock, const CAmount& rew
|
||||
assert(false);
|
||||
}
|
||||
|
||||
bool CChainParams::UpdateMNActivationParam(int nBit, int height, int64_t timePast, bool fJustCheck) const
|
||||
{
|
||||
assert(nBit < VERSIONBITS_NUM_BITS);
|
||||
|
||||
for (int index = 0; index < Consensus::MAX_VERSION_BITS_DEPLOYMENTS; ++index) {
|
||||
if (consensus.vDeployments[index].bit == nBit) {
|
||||
auto& deployment = consensus.vDeployments[index];
|
||||
if (timePast > deployment.nTimeout || timePast < deployment.nStartTime) {
|
||||
LogPrintf("%s: activation by bit=%d height=%d deployment='%s' is out of time range start=%lld timeout=%lld\n", __func__, nBit, height, VersionBitsDeploymentInfo[Consensus::DeploymentPos(index)].name, deployment.nStartTime, deployment.nTimeout);
|
||||
continue;
|
||||
}
|
||||
if (deployment.nMNActivationHeight < 0) {
|
||||
LogPrintf("%s: trying to set MnEHF height=%d for non-masternode activation fork bit=%d\n", __func__, height, nBit);
|
||||
return false;
|
||||
}
|
||||
LogPrintf("%s: set MnEHF height=%d for bit=%d fJustCheck=%d is valid\n", __func__, height, nBit, fJustCheck);
|
||||
if (!fJustCheck) {
|
||||
deployment.nMNActivationHeight = height;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
LogPrintf("%s: WARNING: unknown MnEHF fork bit=%d\n", __func__, nBit);
|
||||
return true;
|
||||
}
|
||||
|
||||
void CChainParams::AddLLMQ(Consensus::LLMQType llmqType)
|
||||
{
|
||||
assert(!GetLLMQ(llmqType).has_value());
|
||||
@ -909,7 +936,7 @@ public:
|
||||
/**
|
||||
* Allows modifying the Version Bits regtest parameters.
|
||||
*/
|
||||
void UpdateVersionBitsParameters(Consensus::DeploymentPos d, int64_t nStartTime, int64_t nTimeout, int64_t nWindowSize, int64_t nThresholdStart, int64_t nThresholdMin, int64_t nFalloffCoeff)
|
||||
void UpdateVersionBitsParameters(Consensus::DeploymentPos d, int64_t nStartTime, int64_t nTimeout, int64_t nWindowSize, int64_t nThresholdStart, int64_t nThresholdMin, int64_t nFalloffCoeff, int64_t nMNActivationHeight)
|
||||
{
|
||||
consensus.vDeployments[d].nStartTime = nStartTime;
|
||||
consensus.vDeployments[d].nTimeout = nTimeout;
|
||||
@ -925,6 +952,9 @@ public:
|
||||
if (nFalloffCoeff != -1) {
|
||||
consensus.vDeployments[d].nFalloffCoeff = nFalloffCoeff;
|
||||
}
|
||||
if (nMNActivationHeight != -1) {
|
||||
consensus.vDeployments[d].nMNActivationHeight = nMNActivationHeight;
|
||||
}
|
||||
}
|
||||
void UpdateActivationParametersFromArgs(const ArgsManager& args);
|
||||
|
||||
@ -998,13 +1028,13 @@ void CRegTestParams::UpdateActivationParametersFromArgs(const ArgsManager& args)
|
||||
|
||||
for (const std::string& strDeployment : args.GetArgs("-vbparams")) {
|
||||
std::vector<std::string> vDeploymentParams = SplitString(strDeployment, ':');
|
||||
if (vDeploymentParams.size() != 3 && vDeploymentParams.size() != 5 && vDeploymentParams.size() != 7) {
|
||||
if (vDeploymentParams.size() != 3 && vDeploymentParams.size() != 5 && vDeploymentParams.size() != 8) {
|
||||
throw std::runtime_error("Version bits parameters malformed, expecting "
|
||||
"<deployment>:<start>:<end> or "
|
||||
"<deployment>:<start>:<end>:<window>:<threshold> or "
|
||||
"<deployment>:<start>:<end>:<window>:<thresholdstart>:<thresholdmin>:<falloffcoeff>");
|
||||
"<deployment>:<start>:<end>:<window>:<thresholdstart>:<thresholdmin>:<falloffcoeff>:<mnactivation>");
|
||||
}
|
||||
int64_t nStartTime, nTimeout, nWindowSize = -1, nThresholdStart = -1, nThresholdMin = -1, nFalloffCoeff = -1;
|
||||
int64_t nStartTime, nTimeout, nWindowSize = -1, nThresholdStart = -1, nThresholdMin = -1, nFalloffCoeff = -1, nMNActivationHeight = -1;
|
||||
if (!ParseInt64(vDeploymentParams[1], &nStartTime)) {
|
||||
throw std::runtime_error(strprintf("Invalid nStartTime (%s)", vDeploymentParams[1]));
|
||||
}
|
||||
@ -1019,21 +1049,24 @@ void CRegTestParams::UpdateActivationParametersFromArgs(const ArgsManager& args)
|
||||
throw std::runtime_error(strprintf("Invalid nThresholdStart (%s)", vDeploymentParams[4]));
|
||||
}
|
||||
}
|
||||
if (vDeploymentParams.size() == 7) {
|
||||
if (vDeploymentParams.size() == 8) {
|
||||
if (!ParseInt64(vDeploymentParams[5], &nThresholdMin)) {
|
||||
throw std::runtime_error(strprintf("Invalid nThresholdMin (%s)", vDeploymentParams[5]));
|
||||
}
|
||||
if (!ParseInt64(vDeploymentParams[6], &nFalloffCoeff)) {
|
||||
throw std::runtime_error(strprintf("Invalid nFalloffCoeff (%s)", vDeploymentParams[6]));
|
||||
}
|
||||
if (!ParseInt64(vDeploymentParams[7], &nMNActivationHeight)) {
|
||||
throw std::runtime_error(strprintf("Invalid nMNActivationHeight (%s)", vDeploymentParams[7]));
|
||||
}
|
||||
}
|
||||
bool found = false;
|
||||
for (int j=0; j < (int)Consensus::MAX_VERSION_BITS_DEPLOYMENTS; ++j) {
|
||||
if (vDeploymentParams[0] == VersionBitsDeploymentInfo[j].name) {
|
||||
UpdateVersionBitsParameters(Consensus::DeploymentPos(j), nStartTime, nTimeout, nWindowSize, nThresholdStart, nThresholdMin, nFalloffCoeff);
|
||||
UpdateVersionBitsParameters(Consensus::DeploymentPos(j), nStartTime, nTimeout, nWindowSize, nThresholdStart, nThresholdMin, nFalloffCoeff, nMNActivationHeight);
|
||||
found = true;
|
||||
LogPrintf("Setting version bits activation parameters for %s to start=%ld, timeout=%ld, window=%ld, thresholdstart=%ld, thresholdmin=%ld, falloffcoeff=%ld\n",
|
||||
vDeploymentParams[0], nStartTime, nTimeout, nWindowSize, nThresholdStart, nThresholdMin, nFalloffCoeff);
|
||||
LogPrintf("Setting version bits activation parameters for %s to start=%ld, timeout=%ld, window=%ld, thresholdstart=%ld, thresholdmin=%ld, falloffcoeff=%ld, mnactivationHeight=%ld\n",
|
||||
vDeploymentParams[0], nStartTime, nTimeout, nWindowSize, nThresholdStart, nThresholdMin, nFalloffCoeff, nMNActivationHeight);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -134,6 +134,16 @@ public:
|
||||
void UpdateDIP8Parameters(int nActivationHeight);
|
||||
void UpdateBudgetParameters(int nMasternodePaymentsStartBlock, int nBudgetPaymentsStartBlock, int nSuperblockStartBlock);
|
||||
void UpdateLLMQInstantSend(Consensus::LLMQType llmqType);
|
||||
/**
|
||||
* Update params for Masternodes EHF
|
||||
*
|
||||
* @param[in] nBit The version bit to update
|
||||
* @param[in] height The height of block where that signal is mined
|
||||
* @param[in] timePast The block time to validate if release is already time-outed
|
||||
* @param[in] fJustCheck If true do not update any internal data, only validate params
|
||||
* @return Whether params are legit and params are updated (if release is known)
|
||||
*/
|
||||
bool UpdateMNActivationParam(int nBit, int height, int64_t timePast, bool fJustCheck) const;
|
||||
int PoolMinParticipants() const { return nPoolMinParticipants; }
|
||||
int PoolMaxParticipants() const { return nPoolMaxParticipants; }
|
||||
int FulfilledRequestExpireTime() const { return nFulfilledRequestExpireTime; }
|
||||
|
@ -36,9 +36,9 @@ void SetupChainParamsBaseOptions(ArgsManager& argsman)
|
||||
argsman.AddArg("-regtest", "Enter regression test mode, which uses a special chain in which blocks can be solved instantly. "
|
||||
"This is intended for regression testing tools and app development. Equivalent to -chain=regtest", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CHAINPARAMS);
|
||||
argsman.AddArg("-testnet", "Use the test chain. Equivalent to -chain=test", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS);
|
||||
argsman.AddArg("-vbparams=<deployment>:<start>:<end>(:<window>:<threshold/thresholdstart>(:<thresholdmin>:<falloffcoeff>))",
|
||||
argsman.AddArg("-vbparams=<deployment>:<start>:<end>(:<window>:<threshold/thresholdstart>(:<thresholdmin>:<falloffcoeff>:<mnactivation>))",
|
||||
"Use given start/end times for specified version bits deployment (regtest-only). "
|
||||
"Specifying window, threshold/thresholdstart, thresholdmin and falloffcoeff is optional.", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CHAINPARAMS);
|
||||
"Specifying window, threshold/thresholdstart, thresholdmin, falloffcoeff and mnactivation is optional.", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CHAINPARAMS);
|
||||
|
||||
}
|
||||
|
||||
|
@ -48,6 +48,13 @@ struct BIP9Deployment {
|
||||
* process (which takes at least 3 BIP9 intervals). Only tests that specifically test the
|
||||
* behaviour during activation cannot use this. */
|
||||
static constexpr int64_t ALWAYS_ACTIVE = -1;
|
||||
|
||||
/** this value is used for forks activated by master nodes.
|
||||
* negative values means it is regular fork, no masternodes confirmation is needed.
|
||||
* 0 means that there's no approval from masternodes yet.
|
||||
* Otherwise it shows minimum height when miner's signals for this block can be assumed
|
||||
*/
|
||||
mutable int64_t nMNActivationHeight{-1};
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -15,28 +15,59 @@
|
||||
#include <validation.h>
|
||||
#include <versionbits.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
extern const std::string MNEHF_REQUESTID_PREFIX = "mnhf";
|
||||
static const std::string DB_SIGNALS = "mnhf_s";
|
||||
|
||||
bool MNHFTx::Verify(const CBlockIndex* pQuorumIndex, const uint256& msgHash, TxValidationState& state) const
|
||||
CMNHFManager::Signals CMNHFManager::GetSignalsStage(const CBlockIndex* const pindexPrev)
|
||||
{
|
||||
Signals signals = GetFromCache(pindexPrev);
|
||||
const int height = pindexPrev->nHeight + 1;
|
||||
for (auto it = signals.begin(); it != signals.end(); ) {
|
||||
bool found{false};
|
||||
const auto signal_pindex = pindexPrev->GetAncestor(it->second);
|
||||
assert(signal_pindex != nullptr);
|
||||
const int64_t signal_time = signal_pindex->GetMedianTimePast();
|
||||
for (int index = 0; index < Consensus::MAX_VERSION_BITS_DEPLOYMENTS; ++index) {
|
||||
const auto& deployment = Params().GetConsensus().vDeployments[index];
|
||||
if (deployment.bit != it->first) continue;
|
||||
if (signal_time < deployment.nStartTime) {
|
||||
// new deployment is using the same bit as the old one
|
||||
LogPrintf("CMNHFManager::GetSignalsStage: mnhf signal bit=%d height:%d is expired at height=%d\n", it->first, it->second, height);
|
||||
it = signals.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
if (!found) {
|
||||
// no deployment means we buried it and aren't using the same bit (yet)
|
||||
LogPrintf("CMNHFManager::GetSignalsStage: mnhf signal bit=%d height:%d is not known at height=%d\n", it->first, it->second, height);
|
||||
it = signals.erase(it);
|
||||
}
|
||||
}
|
||||
return signals;
|
||||
}
|
||||
|
||||
bool MNHFTx::Verify(const uint256& quorumHash, const uint256& msgHash, TxValidationState& state) const
|
||||
{
|
||||
if (versionBit >= VERSIONBITS_NUM_BITS) {
|
||||
return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-mnhf-nbit-out-of-bounds");
|
||||
}
|
||||
|
||||
Consensus::LLMQType llmqType = Params().GetConsensus().llmqTypeMnhf;
|
||||
const auto& llmq_params_opt = llmq::GetLLMQParams(llmqType);
|
||||
if (!llmq_params_opt.has_value()) {
|
||||
return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-mnhf-quorum-type");
|
||||
}
|
||||
int signOffset{llmq_params_opt->dkgInterval};
|
||||
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}));
|
||||
|
||||
if (!llmq::CSigningManager::VerifyRecoveredSig(llmqType, *llmq::quorumManager, pQuorumIndex->nHeight + signOffset, requestId, msgHash, sig)) {
|
||||
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");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -72,14 +103,203 @@ bool CheckMNHFTx(const CTransaction& tx, const CBlockIndex* pindexPrev, TxValida
|
||||
SetTxPayload(tx_copy, payload_copy);
|
||||
uint256 msgHash = tx_copy.GetHash();
|
||||
|
||||
if (!mnhfTx.signal.Verify(pindexQuorum, msgHash, state)) {
|
||||
|
||||
if (!mnhfTx.signal.Verify(mnhfTx.signal.quorumHash, msgHash, state)) {
|
||||
// set up inside Verify
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Params().UpdateMNActivationParam(mnhfTx.signal.versionBit, pindexPrev->nHeight, pindexPrev->GetMedianTimePast(), /* fJustCheck= */ true )) {
|
||||
return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-mnhf-non-ehf");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::optional<uint8_t> extractEHFSignal(const CTransaction& tx)
|
||||
{
|
||||
if (tx.nVersion != 3 || tx.nType != TRANSACTION_MNHF_SIGNAL) {
|
||||
// only interested in special TXs 'TRANSACTION_MNHF_SIGNAL'
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
MNHFTxPayload mnhfTx;
|
||||
if (!GetTxPayload(tx, mnhfTx)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return mnhfTx.signal.versionBit;
|
||||
}
|
||||
|
||||
static bool extractSignals(const CBlock& block, const CBlockIndex* const pindex, std::vector<uint8_t>& new_signals, BlockValidationState& state)
|
||||
{
|
||||
AssertLockHeld(cs_main);
|
||||
|
||||
// we skip the coinbase
|
||||
for (size_t i = 1; i < block.vtx.size(); ++i) {
|
||||
const CTransaction& tx = *block.vtx[i];
|
||||
|
||||
if (tx.nVersion != 3 || tx.nType != TRANSACTION_MNHF_SIGNAL) {
|
||||
// only interested in special TXs 'TRANSACTION_MNHF_SIGNAL'
|
||||
continue;
|
||||
}
|
||||
|
||||
TxValidationState tx_state;
|
||||
if (!CheckMNHFTx(tx, pindex, tx_state)) {
|
||||
return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, tx_state.GetRejectReason(), tx_state.GetDebugMessage());
|
||||
}
|
||||
|
||||
MNHFTxPayload mnhfTx;
|
||||
if (!GetTxPayload(tx, mnhfTx)) {
|
||||
return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-mnhf-tx-payload");
|
||||
}
|
||||
const uint8_t bit = mnhfTx.signal.versionBit;
|
||||
if (std::find(new_signals.begin(), new_signals.end(), bit) != new_signals.end()) {
|
||||
return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-mnhf-duplicates-in-block");
|
||||
}
|
||||
new_signals.push_back(bit);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CMNHFManager::ProcessBlock(const CBlock& block, const CBlockIndex* const pindex, bool fJustCheck, BlockValidationState& state)
|
||||
{
|
||||
try {
|
||||
std::vector<uint8_t> new_signals;
|
||||
if (!extractSignals(block, pindex, new_signals, state)) {
|
||||
// state is set inside extractSignals
|
||||
return false;
|
||||
}
|
||||
Signals signals = GetSignalsStage(pindex->pprev);
|
||||
if (new_signals.empty()) {
|
||||
if (!fJustCheck) {
|
||||
AddToCache(signals, pindex);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int mined_height = pindex->nHeight;
|
||||
|
||||
// Extra validation of signals to be sure that it can succeed
|
||||
for (const auto& versionBit : new_signals) {
|
||||
LogPrintf("CMNHFManager::ProcessBlock: add mnhf bit=%d block:%s number of known signals:%lld\n", versionBit, pindex->GetBlockHash().ToString(), signals.size());
|
||||
if (signals.find(versionBit) != signals.end()) {
|
||||
return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-mnhf-duplicate");
|
||||
}
|
||||
|
||||
if (!Params().UpdateMNActivationParam(versionBit, mined_height, pindex->GetMedianTimePast(), true /* fJustCheck */)) {
|
||||
return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-mnhf-non-mn-fork");
|
||||
}
|
||||
}
|
||||
if (fJustCheck) {
|
||||
// We are done, no need actually update any params
|
||||
return true;
|
||||
}
|
||||
for (const auto& versionBit : new_signals) {
|
||||
signals.insert({versionBit, mined_height});
|
||||
|
||||
if (!Params().UpdateMNActivationParam(versionBit, mined_height, pindex->GetMedianTimePast(), false /* fJustCheck */)) {
|
||||
// it should not ever fail - all checks are done above
|
||||
assert(false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
AddToCache(signals, pindex);
|
||||
return true;
|
||||
} catch (const std::exception& e) {
|
||||
LogPrintf("CMNHFManager::ProcessBlock -- failed: %s\n", e.what());
|
||||
return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "failed-proc-mnhf-inblock");
|
||||
}
|
||||
}
|
||||
|
||||
bool CMNHFManager::UndoBlock(const CBlock& block, const CBlockIndex* const pindex)
|
||||
{
|
||||
std::vector<uint8_t> excluded_signals;
|
||||
BlockValidationState state;
|
||||
if (!extractSignals(block, pindex, excluded_signals, state)) {
|
||||
LogPrintf("%s: failed to extract signals\n", __func__);
|
||||
return false;
|
||||
}
|
||||
if (excluded_signals.empty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const Signals signals = GetFromCache(pindex);
|
||||
for (const auto& versionBit : excluded_signals) {
|
||||
LogPrintf("%s: exclude mnhf bit=%d block:%s number of known signals:%lld\n", __func__, versionBit, pindex->GetBlockHash().ToString(), signals.size());
|
||||
assert(signals.find(versionBit) != signals.end());
|
||||
|
||||
bool update_ret = Params().UpdateMNActivationParam(versionBit, 0, pindex->GetMedianTimePast(), false /* fJustCheck */);
|
||||
assert(update_ret);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CMNHFManager::UpdateChainParams(const CBlockIndex* const pindex, const CBlockIndex* const pindexOld)
|
||||
{
|
||||
LogPrintf("%s: update chain params %s -> %s\n", __func__, pindexOld ? pindexOld->GetBlockHash().ToString() : "", pindex ? pindex->GetBlockHash().ToString() : "");
|
||||
Signals signals_old{GetFromCache(pindexOld)};
|
||||
for (const auto& signal: signals_old) {
|
||||
const uint8_t versionBit = signal.first;
|
||||
|
||||
LogPrintf("%s: unload mnhf bit=%d block:%s number of known signals:%lld\n", __func__, versionBit, pindex->GetBlockHash().ToString(), signals_old.size());
|
||||
|
||||
bool update_ret = Params().UpdateMNActivationParam(versionBit, 0, pindex->GetMedianTimePast(), /* fJustCheck= */ false);
|
||||
assert(update_ret);
|
||||
}
|
||||
|
||||
Signals signals{GetFromCache(pindex)};
|
||||
for (const auto& signal: signals) {
|
||||
const uint8_t versionBit = signal.first;
|
||||
const int value = signal.second;
|
||||
|
||||
LogPrintf("%s: load mnhf bit=%d block:%s number of known signals:%lld\n", __func__, versionBit, pindex->GetBlockHash().ToString(), signals.size());
|
||||
|
||||
bool update_ret = Params().UpdateMNActivationParam(versionBit, value, pindex->GetMedianTimePast(), /* fJustCheck= */ false);
|
||||
assert(update_ret);
|
||||
}
|
||||
}
|
||||
|
||||
CMNHFManager::Signals CMNHFManager::GetFromCache(const CBlockIndex* const pindex)
|
||||
{
|
||||
if (pindex == nullptr) return {};
|
||||
const uint256& blockHash = pindex->GetBlockHash();
|
||||
Signals signals{};
|
||||
{
|
||||
LOCK(cs_cache);
|
||||
if (mnhfCache.get(blockHash, signals)) {
|
||||
LogPrintf("CMNHFManager::GetFromCache: mnhf get for block %s from cache: %lld signals\n", pindex->GetBlockHash().ToString(), signals.size());
|
||||
return signals;
|
||||
}
|
||||
}
|
||||
if (VersionBitsState(pindex->pprev, Params().GetConsensus(), Consensus::DEPLOYMENT_V20, versionbitscache) != ThresholdState::ACTIVE) {
|
||||
LOCK(cs_cache);
|
||||
mnhfCache.insert(blockHash, {});
|
||||
LogPrintf("CMNHFManager::GetFromCache: mnhf feature is disabled: return empty for block %s\n", pindex->GetBlockHash().ToString());
|
||||
return {};
|
||||
}
|
||||
if (!m_evoDb.Read(std::make_pair(DB_SIGNALS, blockHash), signals)) {
|
||||
LogPrintf("CMNHFManager::GetFromCache: failure: can't read MnEHF signals from db for %s\n", pindex->GetBlockHash().ToString());
|
||||
}
|
||||
LogPrintf("CMNHFManager::GetFromCache: mnhf for block %s read from evo: %lld\n", pindex->GetBlockHash().ToString(), signals.size());
|
||||
LOCK(cs_cache);
|
||||
mnhfCache.insert(blockHash, signals);
|
||||
return signals;
|
||||
}
|
||||
|
||||
void CMNHFManager::AddToCache(const Signals& signals, const CBlockIndex* const pindex)
|
||||
{
|
||||
const uint256& blockHash = pindex->GetBlockHash();
|
||||
{
|
||||
LOCK(cs_cache);
|
||||
LogPrintf("CMNHFManager::AddToCache: mnhf for block %s add to cache: %lld\n", pindex->GetBlockHash().ToString(), signals.size());
|
||||
mnhfCache.insert(blockHash, signals);
|
||||
}
|
||||
m_evoDb.Write(std::make_pair(DB_SIGNALS, blockHash), signals);
|
||||
}
|
||||
|
||||
std::string MNHFTx::ToString() const
|
||||
{
|
||||
return strprintf("MNHFTx(versionBit=%d, quorumHash=%s, sig=%s)",
|
||||
|
@ -11,7 +11,15 @@
|
||||
#include <threadsafety.h>
|
||||
#include <univalue.h>
|
||||
|
||||
#include <optional>
|
||||
#include <saltedhasher.h>
|
||||
#include <unordered_map>
|
||||
#include <unordered_lru_cache.h>
|
||||
|
||||
class BlockValidationState;
|
||||
class CBlock;
|
||||
class CBlockIndex;
|
||||
class CEvoDB;
|
||||
class TxValidationState;
|
||||
extern RecursiveMutex cs_main;
|
||||
|
||||
@ -20,11 +28,11 @@ class MNHFTx
|
||||
{
|
||||
public:
|
||||
uint8_t versionBit{0};
|
||||
uint256 quorumHash;
|
||||
CBLSSignature sig;
|
||||
uint256 quorumHash{0};
|
||||
CBLSSignature sig{};
|
||||
|
||||
MNHFTx() = default;
|
||||
bool Verify(const CBlockIndex* pQuorumIndex, const uint256& msgHash, TxValidationState& state) const;
|
||||
bool Verify(const uint256& quorumHash, const uint256& msgHash, TxValidationState& state) const;
|
||||
|
||||
SERIALIZE_METHODS(MNHFTx, obj)
|
||||
{
|
||||
@ -72,6 +80,59 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
class CMNHFManager
|
||||
{
|
||||
public:
|
||||
using Signals = std::unordered_map<uint8_t, int>;
|
||||
|
||||
private:
|
||||
CEvoDB& m_evoDb;
|
||||
|
||||
static constexpr size_t MNHFCacheSize = 1000;
|
||||
Mutex cs_cache;
|
||||
// versionBit <-> height
|
||||
unordered_lru_cache<uint256, Signals, StaticSaltedHasher> mnhfCache GUARDED_BY(cs_cache) {MNHFCacheSize};
|
||||
|
||||
public:
|
||||
explicit CMNHFManager(CEvoDB& evoDb) :
|
||||
m_evoDb(evoDb) {}
|
||||
~CMNHFManager() = default;
|
||||
|
||||
/**
|
||||
* Every new block should be processed when Tip() is updated by calling of CMNHFManager::ProcessBlock
|
||||
*/
|
||||
bool ProcessBlock(const CBlock& block, const CBlockIndex* const pindex, bool fJustCheck, BlockValidationState& state) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
|
||||
|
||||
/**
|
||||
* Every undo block should be processed when Tip() is updated by calling of CMNHFManager::UndoBlock
|
||||
*/
|
||||
bool UndoBlock(const CBlock& block, const CBlockIndex* const pindex) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
|
||||
|
||||
/**
|
||||
* Once app is started, need to initialize dictionary will all known signals at the current Tip()
|
||||
* by calling UpdateChainParams()
|
||||
*/
|
||||
void UpdateChainParams(const CBlockIndex* const pindex, const CBlockIndex* const pindexOld) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
|
||||
|
||||
/**
|
||||
* This function prepares signals for new block.
|
||||
* This data is filterd expired signals from previous blocks.
|
||||
* 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);
|
||||
|
||||
/**
|
||||
* This function returns list of signals available on previous block.
|
||||
* NOTE: that some signals could expired between blocks.
|
||||
* validate them by
|
||||
*/
|
||||
Signals GetFromCache(const CBlockIndex* const pindex);
|
||||
|
||||
};
|
||||
|
||||
std::optional<uint8_t> extractEHFSignal(const CTransaction& tx);
|
||||
bool CheckMNHFTx(const CTransaction& tx, const CBlockIndex* pindexPrev, TxValidationState& state) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
|
||||
|
||||
#endif // BITCOIN_EVO_MNHFTX_H
|
||||
|
@ -122,7 +122,8 @@ static bool UndoSpecialTx(const CTransaction& tx, const CBlockIndex* pindex)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ProcessSpecialTxsInBlock(const CBlock& block, const CBlockIndex* pindex, llmq::CQuorumBlockProcessor& quorum_block_processor, const llmq::CChainLocksHandler& chainlock_handler,
|
||||
bool ProcessSpecialTxsInBlock(const CBlock& block, const CBlockIndex* pindex, CMNHFManager& mnhfManager,
|
||||
llmq::CQuorumBlockProcessor& quorum_block_processor, const llmq::CChainLocksHandler& chainlock_handler,
|
||||
const Consensus::Params& consensusParams, const CCoinsViewCache& view, bool fJustCheck, bool fCheckCbTxMerleRoots,
|
||||
BlockValidationState& state)
|
||||
{
|
||||
@ -134,6 +135,7 @@ bool ProcessSpecialTxsInBlock(const CBlock& block, const CBlockIndex* pindex, ll
|
||||
static int64_t nTimeDMN = 0;
|
||||
static int64_t nTimeMerkle = 0;
|
||||
static int64_t nTimeCbTxCL = 0;
|
||||
static int64_t nTimeMnehf = 0;
|
||||
|
||||
int64_t nTime1 = GetTimeMicros();
|
||||
|
||||
@ -198,6 +200,15 @@ bool ProcessSpecialTxsInBlock(const CBlock& block, const CBlockIndex* pindex, ll
|
||||
nTimeCbTxCL += nTime6 - nTime5;
|
||||
LogPrint(BCLog::BENCHMARK, " - CheckCbTxBestChainlock: %.2fms [%.2fs]\n", 0.001 * (nTime6 - nTime5), nTimeCbTxCL * 0.000001);
|
||||
|
||||
if (!mnhfManager.ProcessBlock(block, pindex, fJustCheck, state)) {
|
||||
// pass the state returned by the function above
|
||||
return false;
|
||||
}
|
||||
|
||||
int64_t nTime7 = GetTimeMicros();
|
||||
nTimeMnehf += nTime7 - nTime6;
|
||||
LogPrint(BCLog::BENCHMARK, " - mnhfManager: %.2fms [%.2fs]\n", 0.001 * (nTime7 - nTime6), nTimeMnehf * 0.000001);
|
||||
|
||||
if (Params().GetConsensus().V19Height == pindex->nHeight + 1) {
|
||||
// NOTE: The block next to the activation is the one that is using new rules.
|
||||
// V19 activated just activated, so we must switch to the new rules here.
|
||||
@ -212,7 +223,7 @@ bool ProcessSpecialTxsInBlock(const CBlock& block, const CBlockIndex* pindex, ll
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UndoSpecialTxsInBlock(const CBlock& block, const CBlockIndex* pindex, llmq::CQuorumBlockProcessor& quorum_block_processor)
|
||||
bool UndoSpecialTxsInBlock(const CBlock& block, const CBlockIndex* pindex, CMNHFManager& mnhfManager, llmq::CQuorumBlockProcessor& quorum_block_processor)
|
||||
{
|
||||
AssertLockHeld(cs_main);
|
||||
|
||||
@ -233,6 +244,10 @@ bool UndoSpecialTxsInBlock(const CBlock& block, const CBlockIndex* pindex, llmq:
|
||||
}
|
||||
}
|
||||
|
||||
if (!mnhfManager.UndoBlock(block, pindex)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!deterministicMNManager->UndoBlock(pindex)) {
|
||||
return false;
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ class BlockValidationState;
|
||||
class CBlock;
|
||||
class CBlockIndex;
|
||||
class CCoinsViewCache;
|
||||
class CMNHFManager;
|
||||
class TxValidationState;
|
||||
namespace llmq {
|
||||
class CQuorumBlockProcessor;
|
||||
@ -26,10 +27,12 @@ extern RecursiveMutex cs_main;
|
||||
|
||||
bool CheckSpecialTx(const CTransaction& tx, const CBlockIndex* pindexPrev, const CCoinsViewCache& view, bool check_sigs,
|
||||
TxValidationState& state) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
|
||||
bool ProcessSpecialTxsInBlock(const CBlock& block, const CBlockIndex* pindex, llmq::CQuorumBlockProcessor& quorum_block_processor, const llmq::CChainLocksHandler& chainlock_handler,
|
||||
const Consensus::Params& consensusParams, const CCoinsViewCache& view, bool fJustCheck, bool fCheckCbTxMerleRoots,
|
||||
BlockValidationState& state) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
|
||||
bool UndoSpecialTxsInBlock(const CBlock& block, const CBlockIndex* pindex, llmq::CQuorumBlockProcessor& quorum_block_processor) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
|
||||
bool ProcessSpecialTxsInBlock(const CBlock& block, const CBlockIndex* pindex, CMNHFManager& mnhfManager,
|
||||
llmq::CQuorumBlockProcessor& quorum_block_processor, const llmq::CChainLocksHandler& chainlock_handler,
|
||||
const Consensus::Params& consensusParams, const CCoinsViewCache& view, bool fJustCheck, bool fCheckCbTxMerleRoots,
|
||||
BlockValidationState& state) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
|
||||
bool UndoSpecialTxsInBlock(const CBlock& block, const CBlockIndex* pindex, CMNHFManager& mnhfManager,
|
||||
llmq::CQuorumBlockProcessor& quorum_block_processor) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
|
||||
bool CheckCreditPoolDiffForBlock(const CBlock& block, const CBlockIndex* pindex, const Consensus::Params& consensusParams,
|
||||
const CAmount blockReward, BlockValidationState& state) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
|
||||
|
||||
|
@ -87,6 +87,7 @@
|
||||
|
||||
#include <evo/creditpool.h>
|
||||
#include <evo/deterministicmns.h>
|
||||
#include <evo/mnhftx.h>
|
||||
#include <llmq/blockprocessor.h>
|
||||
#include <llmq/chainlocks.h>
|
||||
#include <llmq/context.h>
|
||||
@ -341,6 +342,7 @@ void PrepareShutdown(NodeContext& node)
|
||||
llmq::quorumSnapshotManager.reset();
|
||||
deterministicMNManager.reset();
|
||||
creditPoolManager.reset();
|
||||
node.mnhf_manager.reset();
|
||||
node.evodb.reset();
|
||||
}
|
||||
for (const auto& client : node.chain_clients) {
|
||||
@ -1914,9 +1916,10 @@ bool AppInitMain(const CoreContext& context, NodeContext& node, interfaces::Bloc
|
||||
LOCK(cs_main);
|
||||
node.evodb.reset();
|
||||
node.evodb = std::make_unique<CEvoDB>(nEvoDbCache, false, fReset || fReindexChainState);
|
||||
node.mnhf_manager = std::make_unique<CMNHFManager>(*node.evodb);
|
||||
|
||||
chainman.Reset();
|
||||
chainman.InitializeChainstate(Assert(node.mempool.get()), *node.evodb, llmq::chainLocksHandler, llmq::quorumInstantSendManager, llmq::quorumBlockProcessor);
|
||||
chainman.InitializeChainstate(Assert(node.mempool.get()), *node.mnhf_manager, *node.evodb, llmq::chainLocksHandler, llmq::quorumInstantSendManager, llmq::quorumBlockProcessor);
|
||||
chainman.m_total_coinstip_cache = nCoinCacheUsage;
|
||||
chainman.m_total_coinsdb_cache = nCoinDBCache;
|
||||
|
||||
@ -2104,6 +2107,9 @@ bool AppInitMain(const CoreContext& context, NodeContext& node, interfaces::Bloc
|
||||
LogPrintf("%s: bls_legacy_scheme=%d\n", __func__, bls::bls_legacy_scheme.load());
|
||||
}
|
||||
|
||||
LogPrintf("init.cpp: update chain params right after bls\n");
|
||||
node.mnhf_manager->UpdateChainParams(::ChainActive().Tip(), nullptr);
|
||||
|
||||
if (!CVerifyDB().VerifyDB(
|
||||
*chainstate, chainparams, chainstate->CoinsDB(),
|
||||
*node.evodb,
|
||||
|
@ -24,6 +24,7 @@
|
||||
#include <evo/specialtx.h>
|
||||
#include <evo/cbtx.h>
|
||||
#include <evo/creditpool.h>
|
||||
#include <evo/mnhftx.h>
|
||||
#include <evo/simplifiedmns.h>
|
||||
#include <governance/governance.h>
|
||||
#include <llmq/blockprocessor.h>
|
||||
@ -174,7 +175,8 @@ std::unique_ptr<CBlockTemplate> BlockAssembler::CreateNewBlock(const CScript& sc
|
||||
LogPrintf("%s: CCreditPool is %s\n", __func__, creditPool.ToString());
|
||||
creditPoolDiff.emplace(std::move(creditPool), pindexPrev, chainparams.GetConsensus());
|
||||
}
|
||||
addPackageTxs(nPackagesSelected, nDescendantsUpdated, creditPoolDiff);
|
||||
std::unordered_map<uint8_t, int> signals = m_chainstate.GetMNHFSignalsStage();
|
||||
addPackageTxs(nPackagesSelected, nDescendantsUpdated, creditPoolDiff, signals);
|
||||
|
||||
int64_t nTime1 = GetTimeMicros();
|
||||
|
||||
@ -400,7 +402,7 @@ void BlockAssembler::SortForBlock(const CTxMemPool::setEntries& package, std::ve
|
||||
// Each time through the loop, we compare the best transaction in
|
||||
// mapModifiedTxs with the next transaction in the mempool to decide what
|
||||
// transaction package to work on next.
|
||||
void BlockAssembler::addPackageTxs(int &nPackagesSelected, int &nDescendantsUpdated, std::optional<CCreditPoolDiff>& creditPoolDiff)
|
||||
void BlockAssembler::addPackageTxs(int &nPackagesSelected, int &nDescendantsUpdated, std::optional<CCreditPoolDiff>& creditPoolDiff, std::unordered_map<uint8_t, int>& signals)
|
||||
{
|
||||
AssertLockHeld(m_mempool.cs);
|
||||
|
||||
@ -460,7 +462,7 @@ void BlockAssembler::addPackageTxs(int &nPackagesSelected, int &nDescendantsUpda
|
||||
if (creditPoolDiff != std::nullopt) {
|
||||
// If one transaction is skipped due to limits, it is not a reason to interrupt
|
||||
// whole process of adding transactions.
|
||||
// `state` is local here because used to log info about this specific tx
|
||||
// `state` is local here because used only to log info about this specific tx
|
||||
TxValidationState state;
|
||||
|
||||
if (!creditPoolDiff->ProcessLockUnlockTransaction(iter->GetTx(), state)) {
|
||||
@ -473,6 +475,18 @@ void BlockAssembler::addPackageTxs(int &nPackagesSelected, int &nDescendantsUpda
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (std::optional<uint8_t> signal = extractEHFSignal(iter->GetTx()); signal != std::nullopt) {
|
||||
if (signals.find(*signal) != signals.end()) {
|
||||
if (fUsingModified) {
|
||||
mapModifiedTx.get<ancestor_score>().erase(modit);
|
||||
failedTx.insert(iter);
|
||||
}
|
||||
LogPrintf("%s: ehf signal tx %s skipped due to duplicate %d\n",
|
||||
__func__, iter->GetTx().GetHash().ToString(), *signal);
|
||||
continue;
|
||||
}
|
||||
signals.insert({*signal, 0});
|
||||
}
|
||||
|
||||
// We skip mapTx entries that are inBlock, and mapModifiedTx shouldn't
|
||||
// contain anything that is inBlock.
|
||||
|
@ -194,7 +194,8 @@ private:
|
||||
/** Add transactions based on feerate including unconfirmed ancestors
|
||||
* Increments nPackagesSelected / nDescendantsUpdated with corresponding
|
||||
* statistics from the package selection (for logging statistics). */
|
||||
void addPackageTxs(int& nPackagesSelected, int& nDescendantsUpdated, std::optional<CCreditPoolDiff>& creditPoolDiff) EXCLUSIVE_LOCKS_REQUIRED(m_mempool.cs);
|
||||
void addPackageTxs(int& nPackagesSelected, int& nDescendantsUpdated,
|
||||
std::optional<CCreditPoolDiff>& creditPoolDiff, std::unordered_map<uint8_t, int>& signals) EXCLUSIVE_LOCKS_REQUIRED(m_mempool.cs);
|
||||
|
||||
// helper functions for addPackageTxs()
|
||||
/** Remove confirmed (inBlock) entries from given set */
|
||||
|
@ -11,6 +11,7 @@
|
||||
#include <interfaces/chain.h>
|
||||
#include <llmq/context.h>
|
||||
#include <evo/evodb.h>
|
||||
#include <evo/mnhftx.h>
|
||||
#include <net.h>
|
||||
#include <net_processing.h>
|
||||
#include <policy/fees.h>
|
||||
|
@ -20,6 +20,7 @@ class ChainstateManager;
|
||||
class CEvoDB;
|
||||
class CScheduler;
|
||||
class CTxMemPool;
|
||||
class CMNHFManager;
|
||||
class PeerManager;
|
||||
struct CJContext;
|
||||
struct LLMQContext;
|
||||
@ -60,6 +61,7 @@ struct NodeContext {
|
||||
//! Dash
|
||||
std::unique_ptr<LLMQContext> llmq_ctx;
|
||||
std::unique_ptr<CCreditPoolManager> creditPoolManager;
|
||||
std::unique_ptr<CMNHFManager> mnhf_manager;
|
||||
std::unique_ptr<CJContext> cj_ctx;
|
||||
|
||||
std::unique_ptr<CEvoDB> evodb;
|
||||
|
@ -34,7 +34,7 @@ static constexpr int threshold(int attempt)
|
||||
|
||||
struct TestChainDATSetup : public TestChainSetup
|
||||
{
|
||||
TestChainDATSetup() : TestChainSetup(window - 2, {"-vbparams=testdummy:0:999999999999:100:80:60:5"}) {}
|
||||
TestChainDATSetup() : TestChainSetup(window - 2, {"-vbparams=testdummy:0:999999999999:100:80:60:5:-1"}) {}
|
||||
|
||||
void signal(int num_blocks, bool expected_lockin)
|
||||
{
|
||||
|
@ -42,6 +42,7 @@ public:
|
||||
|
||||
bool Condition(const CBlockIndex* pindex, const Consensus::Params& params) const override { return Condition(pindex->nVersion); }
|
||||
int64_t BeginTime(const Consensus::Params& params) const override { return m_begin; }
|
||||
int MasternodeBeginHeight(const Consensus::Params& params) const override { return 0; }
|
||||
int64_t EndTime(const Consensus::Params& params) const override { return m_end; }
|
||||
int Period(const Consensus::Params& params) const override { return m_period; }
|
||||
int Threshold(const Consensus::Params& params, int nAttempt) const override { return m_threshold; }
|
||||
|
@ -67,6 +67,7 @@
|
||||
#include <evo/creditpool.h>
|
||||
#include <evo/deterministicmns.h>
|
||||
#include <evo/evodb.h>
|
||||
#include <evo/mnhftx.h>
|
||||
#include <evo/specialtx.h>
|
||||
|
||||
#include <memory>
|
||||
@ -164,6 +165,7 @@ BasicTestingSetup::BasicTestingSetup(const std::string& chainName, const std::ve
|
||||
g_wallet_init_interface.Construct(m_node);
|
||||
fCheckBlockIndex = true;
|
||||
m_node.evodb = std::make_unique<CEvoDB>(1 << 20, true, true);
|
||||
m_node.mnhf_manager = std::make_unique<CMNHFManager>(*m_node.evodb);
|
||||
connman = std::make_unique<CConnman>(0x1337, 0x1337, *m_node.addrman);
|
||||
llmq::quorumSnapshotManager.reset(new llmq::CQuorumSnapshotManager(*m_node.evodb));
|
||||
creditPoolManager = std::make_unique<CCreditPoolManager>(*m_node.evodb);
|
||||
@ -180,6 +182,7 @@ BasicTestingSetup::~BasicTestingSetup()
|
||||
connman.reset();
|
||||
llmq::quorumSnapshotManager.reset();
|
||||
creditPoolManager.reset();
|
||||
m_node.mnhf_manager.reset();
|
||||
m_node.evodb.reset();
|
||||
|
||||
LogInstance().DisconnectTestLogger();
|
||||
@ -251,7 +254,7 @@ TestingSetup::TestingSetup(const std::string& chainName, const std::vector<const
|
||||
// instead of unit tests, but for now we need these here.
|
||||
RegisterAllCoreRPCCommands(tableRPC);
|
||||
|
||||
m_node.chainman->InitializeChainstate(m_node.mempool.get(), *m_node.evodb, llmq::chainLocksHandler, llmq::quorumInstantSendManager, llmq::quorumBlockProcessor);
|
||||
m_node.chainman->InitializeChainstate(m_node.mempool.get(), *m_node.mnhf_manager, *m_node.evodb, llmq::chainLocksHandler, llmq::quorumInstantSendManager, llmq::quorumBlockProcessor);
|
||||
::ChainstateActive().InitCoinsDB(
|
||||
/* cache_size_bytes */ 1 << 23, /* in_memory */ true, /* should_wipe */ false);
|
||||
assert(!::ChainstateActive().CanFlushToDisk());
|
||||
|
@ -40,7 +40,7 @@ BOOST_AUTO_TEST_CASE(validation_chainstate_resize_caches)
|
||||
return outp;
|
||||
};
|
||||
|
||||
CChainState& c1 = *WITH_LOCK(cs_main, return &manager.InitializeChainstate(&mempool, *m_node.evodb, llmq::chainLocksHandler, llmq::quorumInstantSendManager, llmq::quorumBlockProcessor));
|
||||
CChainState& c1 = *WITH_LOCK(cs_main, return &manager.InitializeChainstate(&mempool, *m_node.mnhf_manager, *m_node.evodb, llmq::chainLocksHandler, llmq::quorumInstantSendManager, llmq::quorumBlockProcessor));
|
||||
c1.InitCoinsDB(
|
||||
/* cache_size_bytes */ 1 << 23, /* in_memory */ true, /* should_wipe */ false);
|
||||
WITH_LOCK(::cs_main, c1.InitCoinsCache(1 << 23));
|
||||
|
@ -42,7 +42,7 @@ BOOST_AUTO_TEST_CASE(chainstatemanager)
|
||||
|
||||
// Create a legacy (IBD) chainstate.
|
||||
//
|
||||
CChainState& c1 = *WITH_LOCK(::cs_main, return &manager.InitializeChainstate(&mempool, evodb, llmq::chainLocksHandler, llmq::quorumInstantSendManager, llmq::quorumBlockProcessor));
|
||||
CChainState& c1 = *WITH_LOCK(::cs_main, return &manager.InitializeChainstate(&mempool, *m_node.mnhf_manager, evodb, llmq::chainLocksHandler, llmq::quorumInstantSendManager, llmq::quorumBlockProcessor));
|
||||
chainstates.push_back(&c1);
|
||||
c1.InitCoinsDB(
|
||||
/* cache_size_bytes */ 1 << 23, /* in_memory */ true, /* should_wipe */ false);
|
||||
@ -76,7 +76,7 @@ BOOST_AUTO_TEST_CASE(chainstatemanager)
|
||||
//
|
||||
const uint256 snapshot_blockhash = GetRandHash();
|
||||
CChainState& c2 = *WITH_LOCK(::cs_main, return &manager.InitializeChainstate(
|
||||
&mempool, evodb, llmq::chainLocksHandler, llmq::quorumInstantSendManager, llmq::quorumBlockProcessor,
|
||||
&mempool, *m_node.mnhf_manager, evodb, llmq::chainLocksHandler, llmq::quorumInstantSendManager, llmq::quorumBlockProcessor,
|
||||
snapshot_blockhash)
|
||||
);
|
||||
chainstates.push_back(&c2);
|
||||
@ -145,7 +145,7 @@ BOOST_AUTO_TEST_CASE(chainstatemanager_rebalance_caches)
|
||||
|
||||
// Create a legacy (IBD) chainstate.
|
||||
//
|
||||
CChainState& c1 = *WITH_LOCK(cs_main, return &manager.InitializeChainstate(&mempool, evodb, llmq::chainLocksHandler, llmq::quorumInstantSendManager, llmq::quorumBlockProcessor));
|
||||
CChainState& c1 = *WITH_LOCK(cs_main, return &manager.InitializeChainstate(&mempool, *m_node.mnhf_manager, evodb, llmq::chainLocksHandler, llmq::quorumInstantSendManager, llmq::quorumBlockProcessor));
|
||||
chainstates.push_back(&c1);
|
||||
c1.InitCoinsDB(
|
||||
/* cache_size_bytes */ 1 << 23, /* in_memory */ true, /* should_wipe */ false);
|
||||
@ -163,7 +163,7 @@ BOOST_AUTO_TEST_CASE(chainstatemanager_rebalance_caches)
|
||||
|
||||
// Create a snapshot-based chainstate.
|
||||
//
|
||||
CChainState& c2 = *WITH_LOCK(cs_main, return &manager.InitializeChainstate(&mempool, evodb, llmq::chainLocksHandler, llmq::quorumInstantSendManager, llmq::quorumBlockProcessor, GetRandHash()));
|
||||
CChainState& c2 = *WITH_LOCK(cs_main, return &manager.InitializeChainstate(&mempool, *m_node.mnhf_manager, evodb, llmq::chainLocksHandler, llmq::quorumInstantSendManager, llmq::quorumBlockProcessor, GetRandHash()));
|
||||
chainstates.push_back(&c2);
|
||||
c2.InitCoinsDB(
|
||||
/* cache_size_bytes */ 1 << 23, /* in_memory */ true, /* should_wipe */ false);
|
||||
|
@ -24,7 +24,7 @@ BOOST_AUTO_TEST_CASE(getcoinscachesizestate)
|
||||
{
|
||||
CTxMemPool mempool;
|
||||
BlockManager blockman{};
|
||||
CChainState chainstate(&mempool, blockman, *m_node.evodb, llmq::chainLocksHandler, llmq::quorumInstantSendManager, llmq::quorumBlockProcessor);
|
||||
CChainState chainstate(&mempool, blockman, *m_node.mnhf_manager, *m_node.evodb, llmq::chainLocksHandler, llmq::quorumInstantSendManager, llmq::quorumBlockProcessor);
|
||||
chainstate.InitCoinsDB(/*cache_size_bytes*/ 1 << 10, /*in_memory*/ true, /*should_wipe*/ false);
|
||||
WITH_LOCK(::cs_main, chainstate.InitCoinsCache(1 << 10));
|
||||
|
||||
|
@ -24,6 +24,7 @@ private:
|
||||
public:
|
||||
int64_t BeginTime(const Consensus::Params& params) const override { return TestTime(10000); }
|
||||
int64_t EndTime(const Consensus::Params& params) const override { return TestTime(20000); }
|
||||
int MasternodeBeginHeight(const Consensus::Params& params) const override { return 0; }
|
||||
int Period(const Consensus::Params& params) const override { return 1000; }
|
||||
int Threshold(const Consensus::Params& params, int nAttempt) const override { return 900; }
|
||||
bool Condition(const CBlockIndex* pindex, const Consensus::Params& params) const override { return (pindex->nVersion & 0x100); }
|
||||
|
@ -53,6 +53,7 @@
|
||||
#include <masternode/sync.h>
|
||||
|
||||
#include <evo/evodb.h>
|
||||
#include <evo/mnhftx.h>
|
||||
#include <evo/specialtx.h>
|
||||
#include <evo/specialtxman.h>
|
||||
#include <governance/governance.h>
|
||||
@ -796,7 +797,11 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
|
||||
|
||||
// No transactions are allowed below minRelayTxFee except from disconnected
|
||||
// blocks
|
||||
if (!bypass_limits && !CheckFeeRate(nSize, nModifiedFees, state)) return false;
|
||||
// Checking of fee for MNHF_SIGNAL should be skipped: mnhf does not have
|
||||
// inputs, outputs, or fee
|
||||
if (tx.nVersion != 3 || tx.nType != TRANSACTION_MNHF_SIGNAL) {
|
||||
if (!bypass_limits && !CheckFeeRate(nSize, nModifiedFees, state)) return false;
|
||||
}
|
||||
|
||||
if (nAbsurdFee && nFees > nAbsurdFee)
|
||||
return state.Invalid(TxValidationResult::TX_NOT_STANDARD,
|
||||
@ -1256,6 +1261,7 @@ void CoinsViews::InitCache()
|
||||
|
||||
CChainState::CChainState(CTxMemPool* mempool,
|
||||
BlockManager& blockman,
|
||||
CMNHFManager& mnhfManager,
|
||||
CEvoDB& evoDb,
|
||||
const std::unique_ptr<llmq::CChainLocksHandler>& clhandler,
|
||||
const std::unique_ptr<llmq::CInstantSendManager>& isman,
|
||||
@ -1266,6 +1272,7 @@ CChainState::CChainState(CTxMemPool* mempool,
|
||||
m_clhandler(clhandler),
|
||||
m_isman(isman),
|
||||
m_quorum_block_processor(quorum_block_processor),
|
||||
m_mnhfManager(mnhfManager),
|
||||
m_evoDb(evoDb),
|
||||
m_blockman(blockman),
|
||||
m_from_snapshot_blockhash(from_snapshot_blockhash) {}
|
||||
@ -1702,7 +1709,7 @@ DisconnectResult CChainState::DisconnectBlock(const CBlock& block, const CBlockI
|
||||
std::vector<std::pair<CAddressUnspentKey, CAddressUnspentValue> > addressUnspentIndex;
|
||||
std::vector<std::pair<CSpentIndexKey, CSpentIndexValue> > spentIndex;
|
||||
|
||||
if (!UndoSpecialTxsInBlock(block, pindex, *m_quorum_block_processor)) {
|
||||
if (!UndoSpecialTxsInBlock(block, pindex, m_mnhfManager, *m_quorum_block_processor)) {
|
||||
error("DisconnectBlock(): UndoSpecialTxsInBlock failed");
|
||||
return DISCONNECT_FAILED;
|
||||
}
|
||||
@ -1927,6 +1934,7 @@ public:
|
||||
|
||||
int64_t BeginTime(const Consensus::Params& params) const override { return 0; }
|
||||
int64_t EndTime(const Consensus::Params& params) const override { return std::numeric_limits<int64_t>::max(); }
|
||||
int MasternodeBeginHeight(const Consensus::Params& params) const override { return 0; }
|
||||
int Period(const Consensus::Params& params) const override { return params.nMinerConfirmationWindow; }
|
||||
int Threshold(const Consensus::Params& params, int nAttempt) const override { return params.nRuleChangeActivationThreshold; }
|
||||
|
||||
@ -2181,7 +2189,7 @@ bool CChainState::ConnectBlock(const CBlock& block, BlockValidationState& state,
|
||||
bool fDIP0001Active_context = pindex->nHeight >= Params().GetConsensus().DIP0001Height;
|
||||
|
||||
// MUST process special txes before updating UTXO to ensure consistency between mempool and block processing
|
||||
if (!ProcessSpecialTxsInBlock(block, pindex, *m_quorum_block_processor, *m_clhandler, m_params.GetConsensus(), view, fJustCheck, fScriptChecks, state)) {
|
||||
if (!ProcessSpecialTxsInBlock(block, pindex, m_mnhfManager, *m_quorum_block_processor, *m_clhandler, m_params.GetConsensus(), view, fJustCheck, fScriptChecks, state)) {
|
||||
return error("ConnectBlock(DASH): ProcessSpecialTxsInBlock for block %s failed with %s",
|
||||
pindex->GetBlockHash().ToString(), state.ToString());
|
||||
}
|
||||
@ -2485,6 +2493,13 @@ CoinsCacheSizeState CChainState::GetCoinsCacheSizeState(
|
||||
return CoinsCacheSizeState::OK;
|
||||
}
|
||||
|
||||
std::unordered_map<uint8_t, int> CChainState::GetMNHFSignalsStage()
|
||||
{
|
||||
const CBlockIndex* const tip = m_chain.Tip();
|
||||
if (tip == nullptr) return {};
|
||||
return this->m_mnhfManager.GetSignalsStage(tip);
|
||||
}
|
||||
|
||||
bool CChainState::FlushStateToDisk(
|
||||
BlockValidationState &state,
|
||||
FlushStateMode mode,
|
||||
@ -4804,7 +4819,7 @@ bool CChainState::RollforwardBlock(const CBlockIndex* pindex, CCoinsViewCache& i
|
||||
|
||||
// MUST process special txes before updating UTXO to ensure consistency between mempool and block processing
|
||||
BlockValidationState state;
|
||||
if (!ProcessSpecialTxsInBlock(block, pindex, *m_quorum_block_processor, *m_clhandler, m_params.GetConsensus(), inputs, false /*fJustCheck*/, false /*fScriptChecks*/, state)) {
|
||||
if (!ProcessSpecialTxsInBlock(block, pindex, m_mnhfManager, *m_quorum_block_processor, *m_clhandler, m_params.GetConsensus(), inputs, false /*fJustCheck*/, false /*fScriptChecks*/, state)) {
|
||||
return error("RollforwardBlock(DASH): ProcessSpecialTxsInBlock for block %s failed with %s",
|
||||
pindex->GetBlockHash().ToString(), state.ToString());
|
||||
}
|
||||
@ -5646,6 +5661,7 @@ std::vector<CChainState*> ChainstateManager::GetAll()
|
||||
}
|
||||
|
||||
CChainState& ChainstateManager::InitializeChainstate(CTxMemPool* mempool,
|
||||
CMNHFManager& mnhfManager,
|
||||
CEvoDB& evoDb,
|
||||
const std::unique_ptr<llmq::CChainLocksHandler>& clhandler,
|
||||
const std::unique_ptr<llmq::CInstantSendManager>& isman,
|
||||
@ -5660,7 +5676,7 @@ CChainState& ChainstateManager::InitializeChainstate(CTxMemPool* mempool,
|
||||
throw std::logic_error("should not be overwriting a chainstate");
|
||||
}
|
||||
|
||||
to_modify.reset(new CChainState(mempool, m_blockman, evoDb, clhandler, isman, quorum_block_processor, snapshot_blockhash));
|
||||
to_modify.reset(new CChainState(mempool, m_blockman, mnhfManager, evoDb, clhandler, isman, quorum_block_processor, snapshot_blockhash));
|
||||
|
||||
// Snapshot chainstates and initial IBD chaintates always become active.
|
||||
if (is_snapshot || (!is_snapshot && !m_active_chainstate)) {
|
||||
@ -5730,9 +5746,13 @@ bool ChainstateManager::ActivateSnapshot(
|
||||
}
|
||||
|
||||
auto snapshot_chainstate = WITH_LOCK(::cs_main, return std::make_unique<CChainState>(
|
||||
/* mempool */ nullptr, m_blockman, this->ActiveChainstate().m_evoDb,
|
||||
this->ActiveChainstate().m_clhandler, this->ActiveChainstate().m_isman,
|
||||
this->ActiveChainstate().m_quorum_block_processor, base_blockhash
|
||||
/* mempool */ nullptr, m_blockman,
|
||||
this->ActiveChainstate().m_mnhfManager,
|
||||
this->ActiveChainstate().m_evoDb,
|
||||
this->ActiveChainstate().m_clhandler,
|
||||
this->ActiveChainstate().m_isman,
|
||||
this->ActiveChainstate().m_quorum_block_processor,
|
||||
base_blockhash
|
||||
)
|
||||
);
|
||||
|
||||
|
@ -56,6 +56,7 @@ class CChainParams;
|
||||
struct CCheckpointData;
|
||||
class CInv;
|
||||
class CConnman;
|
||||
class CMNHFManager;
|
||||
class CScriptCheck;
|
||||
class CTxMemPool;
|
||||
class TxValidationState;
|
||||
@ -584,6 +585,7 @@ private:
|
||||
const std::unique_ptr<llmq::CChainLocksHandler>& m_clhandler;
|
||||
const std::unique_ptr<llmq::CInstantSendManager>& m_isman;
|
||||
const std::unique_ptr<llmq::CQuorumBlockProcessor>& m_quorum_block_processor;
|
||||
CMNHFManager& m_mnhfManager;
|
||||
CEvoDB& m_evoDb;
|
||||
|
||||
public:
|
||||
@ -593,6 +595,7 @@ public:
|
||||
|
||||
explicit CChainState(CTxMemPool* mempool,
|
||||
BlockManager& blockman,
|
||||
CMNHFManager& mnhfManager,
|
||||
CEvoDB& evoDb,
|
||||
const std::unique_ptr<llmq::CChainLocksHandler>& clhandler,
|
||||
const std::unique_ptr<llmq::CInstantSendManager>& isman,
|
||||
@ -775,8 +778,10 @@ public:
|
||||
size_t max_coins_cache_size_bytes,
|
||||
size_t max_mempool_size_bytes) EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
|
||||
|
||||
std::string ToString() EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
|
||||
/** Return list of MN EHF signals for current Tip() */
|
||||
std::unordered_map<uint8_t, int> GetMNHFSignalsStage() EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
|
||||
|
||||
std::string ToString() EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
|
||||
private:
|
||||
bool ActivateBestChainStep(BlockValidationState& state, CBlockIndex* pindexMostWork, const std::shared_ptr<const CBlock>& pblock, bool& fInvalidFound, ConnectTrace& connectTrace) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_mempool->cs);
|
||||
bool ConnectTip(BlockValidationState& state, CBlockIndex* pindexNew, const std::shared_ptr<const CBlock>& pblock, ConnectTrace& connectTrace, DisconnectedBlockTransactions& disconnectpool) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_mempool->cs);
|
||||
@ -940,6 +945,7 @@ public:
|
||||
//! @param[in] snapshot_blockhash If given, signify that this chainstate
|
||||
//! is based on a snapshot.
|
||||
CChainState& InitializeChainstate(CTxMemPool* mempool,
|
||||
CMNHFManager& mnhfManager,
|
||||
CEvoDB& evoDb,
|
||||
const std::unique_ptr<llmq::CChainLocksHandler>& clhandler,
|
||||
const std::unique_ptr<llmq::CInstantSendManager>& isman,
|
||||
|
@ -5,10 +5,33 @@
|
||||
#include <versionbits.h>
|
||||
#include <consensus/params.h>
|
||||
|
||||
static int calculateStartHeight(const CBlockIndex* pindexPrev, ThresholdState state, const int nPeriod, const ThresholdConditionCache& cache) {
|
||||
int nStartHeight{std::numeric_limits<int>::max()};
|
||||
|
||||
// we are interested only in state STARTED
|
||||
// For state DEFINED: it is not started yet, nothing to do
|
||||
// For states LOCKED_IN, FAILED, ACTIVE: it is too late, nothing to do
|
||||
while (state == ThresholdState::STARTED) {
|
||||
nStartHeight = std::min(pindexPrev->nHeight + 1, nStartHeight);
|
||||
|
||||
// we can walk back here because the only way for STARTED state to exist
|
||||
// in cache already is to be calculated in previous runs via "walk forward"
|
||||
// loop below starting from DEFINED state.
|
||||
pindexPrev = pindexPrev->GetAncestor(pindexPrev->nHeight - nPeriod);
|
||||
auto cache_it = cache.find(pindexPrev);
|
||||
assert(cache_it != cache.end());
|
||||
|
||||
state = cache_it->second;
|
||||
}
|
||||
|
||||
return nStartHeight;
|
||||
}
|
||||
|
||||
ThresholdState AbstractThresholdConditionChecker::GetStateFor(const CBlockIndex* pindexPrev, const Consensus::Params& params, ThresholdConditionCache& cache) const
|
||||
{
|
||||
int nPeriod = Period(params);
|
||||
int64_t nTimeStart = BeginTime(params);
|
||||
int masternodeStartHeight = MasternodeBeginHeight(params);
|
||||
int64_t nTimeTimeout = EndTime(params);
|
||||
|
||||
// Check if this deployment is always active.
|
||||
@ -29,7 +52,7 @@ ThresholdState AbstractThresholdConditionChecker::GetStateFor(const CBlockIndex*
|
||||
cache[pindexPrev] = ThresholdState::DEFINED;
|
||||
break;
|
||||
}
|
||||
if (pindexPrev->GetMedianTimePast() < nTimeStart) {
|
||||
if (pindexPrev->GetMedianTimePast() < nTimeStart || pindexPrev->nHeight < masternodeStartHeight) {
|
||||
// Optimization: don't recompute down further, as we know every earlier block will be before the start time
|
||||
cache[pindexPrev] = ThresholdState::DEFINED;
|
||||
break;
|
||||
@ -42,35 +65,7 @@ ThresholdState AbstractThresholdConditionChecker::GetStateFor(const CBlockIndex*
|
||||
assert(cache.count(pindexPrev));
|
||||
ThresholdState state = cache[pindexPrev];
|
||||
|
||||
auto pindex_search = pindexPrev;
|
||||
auto state_search = state;
|
||||
bool do_search{true};
|
||||
int nStartHeight{std::numeric_limits<int>::max()};
|
||||
while (do_search) {
|
||||
switch (state_search) {
|
||||
case ThresholdState::DEFINED: {
|
||||
// not started yet, nothinig to do
|
||||
do_search = false;
|
||||
break;
|
||||
}
|
||||
case ThresholdState::STARTED: {
|
||||
nStartHeight = std::min(nStartHeight, pindex_search->nHeight + 1);
|
||||
// we can walk back here because the only way for STARTED state to exist
|
||||
// in cache already is to be calculated in previous runs via "walk forward"
|
||||
// loop below starting from DEFINED state.
|
||||
pindex_search = pindex_search->GetAncestor(pindex_search->nHeight - nPeriod);
|
||||
state_search = cache[pindex_search];
|
||||
break;
|
||||
}
|
||||
case ThresholdState::LOCKED_IN:
|
||||
case ThresholdState::FAILED:
|
||||
case ThresholdState::ACTIVE: {
|
||||
// too late, nothing to do
|
||||
do_search = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
int nStartHeight = calculateStartHeight(pindexPrev, state, nPeriod, cache);
|
||||
|
||||
// Now walk forward and compute the state of descendants of pindexPrev
|
||||
while (!vToCompute.empty()) {
|
||||
@ -82,7 +77,7 @@ ThresholdState AbstractThresholdConditionChecker::GetStateFor(const CBlockIndex*
|
||||
case ThresholdState::DEFINED: {
|
||||
if (pindexPrev->GetMedianTimePast() >= nTimeTimeout) {
|
||||
stateNext = ThresholdState::FAILED;
|
||||
} else if (pindexPrev->GetMedianTimePast() >= nTimeStart) {
|
||||
} else if (pindexPrev->GetMedianTimePast() >= nTimeStart && pindexPrev->nHeight >= masternodeStartHeight) {
|
||||
stateNext = ThresholdState::STARTED;
|
||||
nStartHeight = pindexPrev->nHeight + 1;
|
||||
}
|
||||
@ -210,6 +205,16 @@ private:
|
||||
|
||||
protected:
|
||||
int64_t BeginTime(const Consensus::Params& params) const override { return params.vDeployments[id].nStartTime; }
|
||||
int MasternodeBeginHeight(const Consensus::Params& params) const override {
|
||||
const auto& deployment = params.vDeployments[id];
|
||||
if (deployment.nMNActivationHeight == 0) {
|
||||
return std::numeric_limits<int>::max();
|
||||
}
|
||||
if (deployment.nMNActivationHeight < 0) {
|
||||
return 0;
|
||||
}
|
||||
return deployment.nMNActivationHeight;
|
||||
}
|
||||
int64_t EndTime(const Consensus::Params& params) const override { return params.vDeployments[id].nTimeout; }
|
||||
int Period(const Consensus::Params& params) const override { return params.vDeployments[id].nWindowSize ? params.vDeployments[id].nWindowSize : params.nMinerConfirmationWindow; }
|
||||
int Threshold(const Consensus::Params& params, int nAttempt) const override
|
||||
|
@ -56,6 +56,7 @@ class AbstractThresholdConditionChecker {
|
||||
protected:
|
||||
virtual bool Condition(const CBlockIndex* pindex, const Consensus::Params& params) const =0;
|
||||
virtual int64_t BeginTime(const Consensus::Params& params) const =0;
|
||||
virtual int MasternodeBeginHeight(const Consensus::Params& params) const = 0;
|
||||
virtual int64_t EndTime(const Consensus::Params& params) const =0;
|
||||
virtual int Period(const Consensus::Params& params) const =0;
|
||||
virtual int Threshold(const Consensus::Params& params, int nAttempt) const =0;
|
||||
|
@ -24,7 +24,7 @@ llmq_type_strings = {llmq_test: 'llmq_test', llmq_test_v17: 'llmq_test_v17'}
|
||||
|
||||
class QuorumDataRecoveryTest(DashTestFramework):
|
||||
def set_test_params(self):
|
||||
extra_args = [["-vbparams=testdummy:0:999999999999:10:8:6:5"] for _ in range(9)]
|
||||
extra_args = [["-vbparams=testdummy:0:999999999999:10:8:6:5:-1"] for _ in range(9)]
|
||||
self.set_dash_test_params(9, 7, fast_dip3_enforcement=True, extra_args=extra_args)
|
||||
self.set_dash_llmq_test_params(4, 3)
|
||||
|
||||
|
269
test/functional/feature_mnehf.py
Executable file
269
test/functional/feature_mnehf.py
Executable file
@ -0,0 +1,269 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# 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.
|
||||
|
||||
import struct
|
||||
from io import BytesIO
|
||||
|
||||
from test_framework.authproxy import JSONRPCException
|
||||
from test_framework.key import ECKey
|
||||
from test_framework.messages import (
|
||||
CMnEhf,
|
||||
CTransaction,
|
||||
hash256,
|
||||
ser_string,
|
||||
)
|
||||
|
||||
from test_framework.test_framework import DashTestFramework
|
||||
from test_framework.util import (
|
||||
assert_equal,
|
||||
assert_greater_than,
|
||||
get_bip9_details,
|
||||
)
|
||||
|
||||
class MnehfTest(DashTestFramework):
|
||||
def set_test_params(self):
|
||||
extra_args = [["-vbparams=testdummy:0:999999999999:12:12:12:5:0", "-persistmempool=0"] for _ in range(4)]
|
||||
self.set_dash_test_params(4, 3, fast_dip3_enforcement=True, extra_args=extra_args)
|
||||
|
||||
def skip_test_if_missing_module(self):
|
||||
self.skip_if_no_wallet()
|
||||
|
||||
def restart_all_nodes(self, params=None):
|
||||
for inode in range(self.num_nodes):
|
||||
self.log.info(f"Restart node {inode} with {self.extra_args[inode]}")
|
||||
if params is not None:
|
||||
self.extra_args[inode][0] = f"-vbparams=testdummy:{params[0]}:{params[1]}:12:12:12:5:0"
|
||||
self.log.info(f"Actual restart options: {self.extra_args[inode]}")
|
||||
|
||||
self.restart_node(0)
|
||||
for mn in self.mninfo:
|
||||
index = mn.node.index
|
||||
self.stop_node(index)
|
||||
self.start_masternode(mn)
|
||||
for i in range(1, self.num_nodes):
|
||||
self.connect_nodes(i, 0)
|
||||
|
||||
def slowly_generate_batch(self, amount):
|
||||
self.log.info(f"Slowly generate {amount} blocks")
|
||||
while amount > 0:
|
||||
self.log.info(f"Generating batch of blocks {amount} left")
|
||||
next = min(10, amount)
|
||||
amount -= next
|
||||
self.bump_mocktime(next)
|
||||
self.nodes[1].generate(next)
|
||||
self.sync_all()
|
||||
|
||||
def create_mnehf(self, versionBit, pubkey=None):
|
||||
# request ID = sha256("mnhf", versionBit)
|
||||
request_id_buf = ser_string(b"mnhf") + struct.pack("<Q", versionBit)
|
||||
request_id = hash256(request_id_buf)[::-1].hex()
|
||||
|
||||
quorumHash = self.mninfo[0].node.quorum("selectquorum", 100, request_id)["quorumHash"]
|
||||
mnehf_payload = CMnEhf(
|
||||
version = 1,
|
||||
versionBit = versionBit,
|
||||
quorumHash = int(quorumHash, 16),
|
||||
quorumSig = b'\00' * 96)
|
||||
|
||||
mnehf_tx = CTransaction()
|
||||
mnehf_tx.vin = []
|
||||
mnehf_tx.vout = []
|
||||
mnehf_tx.nVersion = 3
|
||||
mnehf_tx.nType = 7 # mnehf signal
|
||||
mnehf_tx.vExtraPayload = mnehf_payload.serialize()
|
||||
|
||||
mnehf_tx.calc_sha256()
|
||||
msgHash = format(mnehf_tx.sha256, '064x')
|
||||
|
||||
self.log.info(f"Signing request_id: {request_id} msgHash: {msgHash} quorum: {quorumHash}")
|
||||
recsig = self.get_recovered_sig(request_id, msgHash)
|
||||
|
||||
mnehf_payload.quorumSig = bytearray.fromhex(recsig["sig"])
|
||||
mnehf_tx.vExtraPayload = mnehf_payload.serialize()
|
||||
return mnehf_tx
|
||||
|
||||
|
||||
def set_sporks(self):
|
||||
spork_enabled = 0
|
||||
spork_disabled = 4070908800
|
||||
|
||||
self.nodes[0].sporkupdate("SPORK_17_QUORUM_DKG_ENABLED", spork_enabled)
|
||||
self.nodes[0].sporkupdate("SPORK_19_CHAINLOCKS_ENABLED", spork_disabled)
|
||||
self.nodes[0].sporkupdate("SPORK_3_INSTANTSEND_BLOCK_FILTERING", spork_disabled)
|
||||
self.nodes[0].sporkupdate("SPORK_2_INSTANTSEND_ENABLED", spork_disabled)
|
||||
self.wait_for_sporks_same()
|
||||
|
||||
def check_fork(self, expected):
|
||||
status = get_bip9_details(self.nodes[0], 'testdummy')['status']
|
||||
self.log.info(f"height: {self.nodes[0].getblockcount()} status: {status}")
|
||||
assert_equal(status, expected)
|
||||
|
||||
def ensure_tx_is_not_mined(self, tx_id):
|
||||
try:
|
||||
assert_equal(self.nodes[0].getrawtransaction(tx_id, 1)['height'], -1)
|
||||
raise AssertionError("Transaction should not be mined")
|
||||
except KeyError:
|
||||
# KeyError is expected
|
||||
pass
|
||||
|
||||
def send_tx(self, tx, expected_error = None, reason = None):
|
||||
try:
|
||||
self.log.info(f"Send tx with expected_error:'{expected_error}'...")
|
||||
tx = self.nodes[0].sendrawtransaction(hexstring=tx.serialize().hex(), maxfeerate=0)
|
||||
if expected_error is None:
|
||||
return tx
|
||||
|
||||
# failure didn't happen, but expected:
|
||||
message = "Transaction should not be accepted"
|
||||
if reason is not None:
|
||||
message += ": " + reason
|
||||
|
||||
raise AssertionError(message)
|
||||
except JSONRPCException as e:
|
||||
self.log.info(f"Send tx triggered an error: {e.error}")
|
||||
assert expected_error in e.error['message']
|
||||
|
||||
|
||||
def run_test(self):
|
||||
node = self.nodes[0]
|
||||
|
||||
self.set_sporks()
|
||||
self.activate_v19()
|
||||
self.log.info(f"After v19 activation should be plenty of blocks: {node.getblockcount()}")
|
||||
assert_greater_than(node.getblockcount(), 900)
|
||||
assert_equal(get_bip9_details(node, 'testdummy')['status'], 'defined')
|
||||
|
||||
self.log.info("Mine a quorum...")
|
||||
self.mine_quorum()
|
||||
assert_equal(get_bip9_details(node, 'testdummy')['status'], 'defined')
|
||||
|
||||
key = ECKey()
|
||||
key.generate()
|
||||
pubkey = key.get_pubkey().get_bytes()
|
||||
ehf_tx = self.create_mnehf(28, pubkey)
|
||||
ehf_unknown_tx = self.create_mnehf(27, pubkey)
|
||||
ehf_invalid_tx = self.create_mnehf(9, pubkey) # deployment that is known as non-EHF
|
||||
|
||||
self.log.info("Checking deserialization of CMnEhf by python's code")
|
||||
mnehf_payload = CMnEhf()
|
||||
mnehf_payload.deserialize(BytesIO(ehf_tx.vExtraPayload))
|
||||
assert_equal(mnehf_payload.version, 1)
|
||||
assert_equal(mnehf_payload.versionBit, 28)
|
||||
self.log.info("Checking correctness of requestId and quorumHash")
|
||||
assert_equal(mnehf_payload.quorumHash, int(self.mninfo[0].node.quorum("selectquorum", 100, 'a0eee872d7d3170dd20d5c5e8380c92b3aa887da5f63d8033289fafa35a90691')["quorumHash"], 16))
|
||||
|
||||
self.send_tx(ehf_tx, expected_error='mnhf-before-v20')
|
||||
|
||||
assert_equal(get_bip9_details(node, 'testdummy')['status'], 'defined')
|
||||
self.activate_v20()
|
||||
assert_equal(get_bip9_details(node, 'testdummy')['status'], 'defined')
|
||||
|
||||
ehf_tx_sent = self.send_tx(ehf_tx)
|
||||
ehf_unknown_tx_sent = self.send_tx(ehf_unknown_tx)
|
||||
self.send_tx(ehf_invalid_tx, expected_error='bad-mnhf-non-ehf')
|
||||
ehf_blockhash = node.generate(1)[0]
|
||||
self.sync_all()
|
||||
|
||||
self.log.info(f"Check MnEhfTx {ehf_tx_sent} was mined in {ehf_blockhash}")
|
||||
assert ehf_tx_sent in node.getblock(ehf_blockhash)['tx']
|
||||
assert ehf_unknown_tx_sent in node.getblock(ehf_blockhash)['tx']
|
||||
|
||||
self.log.info(f"MnEhf tx: '{ehf_tx}' is sent: {ehf_tx_sent}")
|
||||
self.log.info(f"MnEhf 'unknown' tx: '{ehf_unknown_tx}' is sent: {ehf_unknown_tx_sent}")
|
||||
self.log.info(f"mempool: {node.getmempoolinfo()}")
|
||||
assert_equal(node.getmempoolinfo()['size'], 0)
|
||||
|
||||
while (node.getblockcount() + 1) % 12 != 0:
|
||||
self.check_fork('defined')
|
||||
node.generate(1)
|
||||
self.sync_all()
|
||||
|
||||
|
||||
self.restart_all_nodes()
|
||||
|
||||
for i in range(12):
|
||||
self.check_fork('started')
|
||||
node.generate(1)
|
||||
self.sync_all()
|
||||
|
||||
|
||||
for i in range(12):
|
||||
self.check_fork('locked_in')
|
||||
node.generate(1)
|
||||
self.sync_all()
|
||||
if i == 7:
|
||||
self.restart_all_nodes()
|
||||
|
||||
self.check_fork('active')
|
||||
|
||||
fork_active_blockhash = node.getbestblockhash()
|
||||
self.log.info(f"Invalidate block: {ehf_blockhash} with tip {fork_active_blockhash}")
|
||||
for inode in self.nodes:
|
||||
inode.invalidateblock(ehf_blockhash)
|
||||
|
||||
self.log.info("Expecting for fork to be defined in next blocks because no MnEHF tx here")
|
||||
for i in range(12):
|
||||
self.check_fork('defined')
|
||||
node.generate(1)
|
||||
self.sync_all()
|
||||
|
||||
|
||||
self.log.info("Re-sending MnEHF for new fork")
|
||||
tx_sent_2 = self.send_tx(ehf_tx)
|
||||
ehf_blockhash_2 = node.generate(1)[0]
|
||||
self.sync_all()
|
||||
|
||||
self.log.info(f"Check MnEhfTx again {tx_sent_2} was mined in {ehf_blockhash_2}")
|
||||
assert tx_sent_2 in node.getblock(ehf_blockhash_2)['tx']
|
||||
|
||||
self.log.info(f"Generate some more block to jump to `started` status")
|
||||
for i in range(12):
|
||||
node.generate(1)
|
||||
self.check_fork('started')
|
||||
self.restart_all_nodes()
|
||||
self.check_fork('started')
|
||||
|
||||
|
||||
self.log.info(f"Re-consider block {ehf_blockhash} to the old MnEHF and forget new fork")
|
||||
for inode in self.nodes:
|
||||
inode.reconsiderblock(ehf_blockhash)
|
||||
assert_equal(node.getbestblockhash(), fork_active_blockhash)
|
||||
|
||||
self.check_fork('active')
|
||||
self.restart_all_nodes()
|
||||
self.check_fork('active')
|
||||
|
||||
self.log.info("Testing duplicate EHF signal with same bit")
|
||||
ehf_tx_duplicate = self.send_tx(self.create_mnehf(28, pubkey))
|
||||
tip_blockhash = node.generate(1)[0]
|
||||
self.sync_blocks()
|
||||
block = node.getblock(tip_blockhash)
|
||||
assert ehf_tx_duplicate in node.getrawmempool() and ehf_tx_duplicate not in block['tx']
|
||||
|
||||
self.log.info("Testing EHF signal with same bit but with newer start time")
|
||||
self.bump_mocktime(int(60 * 60 * 24 * 14))
|
||||
node.generate(1)
|
||||
self.sync_blocks()
|
||||
self.restart_all_nodes(params=[self.mocktime, self.mocktime + 1000000])
|
||||
self.mine_quorum()
|
||||
|
||||
ehf_tx_new_start = self.create_mnehf(28, pubkey)
|
||||
self.check_fork('defined')
|
||||
|
||||
self.log.info("Mine one block and ensure EHF tx for the new deployment is mined")
|
||||
ehf_tx_sent = self.send_tx(ehf_tx_new_start)
|
||||
tip_blockhash = node.generate(1)[0]
|
||||
self.sync_all()
|
||||
block = node.getblock(tip_blockhash)
|
||||
assert ehf_tx_sent in block['tx']
|
||||
|
||||
self.check_fork('defined')
|
||||
self.slowly_generate_batch(12 * 4)
|
||||
self.check_fork('active')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
MnehfTest().main()
|
@ -17,7 +17,7 @@ class NewQuorumTypeActivationTest(BitcoinTestFramework):
|
||||
def set_test_params(self):
|
||||
self.setup_clean_chain = True
|
||||
self.num_nodes = 1
|
||||
self.extra_args = [["-vbparams=testdummy:0:999999999999:10:8:6:5"]]
|
||||
self.extra_args = [["-vbparams=testdummy:0:999999999999:10:8:6:5:-1"]]
|
||||
|
||||
def run_test(self):
|
||||
self.log.info(get_bip9_details(self.nodes[0], 'testdummy'))
|
||||
|
@ -121,6 +121,7 @@ BASE_SCRIPTS = [
|
||||
'feature_llmq_dkgerrors.py', # NOTE: needs dash_hash to pass
|
||||
'feature_dip4_coinbasemerkleroots.py', # NOTE: needs dash_hash to pass
|
||||
'feature_asset_locks.py', # NOTE: needs dash_hash to pass
|
||||
'feature_mnehf.py', # NOTE: needs dash_hash to pass
|
||||
# vv Tests less than 60s vv
|
||||
'p2p_sendheaders.py', # NOTE: needs dash_hash to pass
|
||||
'p2p_sendheaders_compressed.py', # NOTE: needs dash_hash to pass
|
||||
|
@ -104,6 +104,7 @@ EXPECTED_CIRCULAR_DEPENDENCIES=(
|
||||
"llmq/debug -> llmq/dkgsessionhandler -> llmq/debug"
|
||||
"llmq/debug -> llmq/dkgsessionhandler -> llmq/dkgsession -> llmq/debug"
|
||||
"llmq/utils -> validation -> llmq/utils"
|
||||
"evo/mnhftx -> validation -> evo/mnhftx"
|
||||
)
|
||||
|
||||
EXIT_CODE=0
|
||||
|
Loading…
Reference in New Issue
Block a user