Merge pull request #5469 from knst/mnehf

feat!: masternode node hard-fork activation DIP-0023
This commit is contained in:
PastaPastaPasta 2023-10-06 11:02:41 -05:00 committed by GitHub
commit ce9cfff0d6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 765 additions and 84 deletions

View File

@ -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;
}
}

View File

@ -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; }

View File

@ -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);
}

View File

@ -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};
};
/**

View File

@ -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)",

View File

@ -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

View File

@ -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;
}

View File

@ -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);

View File

@ -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,

View File

@ -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.

View File

@ -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 */

View File

@ -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>

View File

@ -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;

View File

@ -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)
{

View File

@ -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; }

View File

@ -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());

View File

@ -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));

View File

@ -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);

View File

@ -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));

View File

@ -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); }

View File

@ -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
)
);

View File

@ -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,

View File

@ -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

View File

@ -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;

View File

@ -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
View 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()

View File

@ -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'))

View File

@ -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

View File

@ -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