Skip to content

Commit

Permalink
feat!: masternode node hard-fork activation DIP-0023
Browse files Browse the repository at this point in the history
  • Loading branch information
ogabrielides committed Sep 4, 2023
1 parent 485a367 commit 13db37a
Show file tree
Hide file tree
Showing 27 changed files with 606 additions and 66 deletions.
46 changes: 38 additions & 8 deletions src/chainparams.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,30 @@ static CBlock FindDevNetGenesisBlock(const CBlock &prevBlock, const CAmount& rew
assert(false);
}

bool CChainParams::UpdateMNActivationParam(int nBit, int height, int64_t timePast, bool fJustCheck) const
{
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) {
LogPrintf("%s: activation by bit=%d for deployment %s timed out at height=%d\n", __func__, nBit, VersionBitsDeploymentInfo[Consensus::DeploymentPos(index)].name, height);
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;
}
if (!fJustCheck) {
LogPrintf("%s: set MnEHF height=%d for bit=%d successfuly while fJustCheck=%d\n", __func__, height, nBit, fJustCheck);
deployment.nMNActivationHeight = height;
}
return true;
}
}
LogPrintf("%s: not found MnEHF fork bit=%d\n", __func__, nBit);
return false;
}

void CChainParams::AddLLMQ(Consensus::LLMQType llmqType)
{
assert(!GetLLMQ(llmqType).has_value());
Expand Down Expand Up @@ -909,7 +933,7 @@ class CRegTestParams : public CChainParams {
/**
* 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;
Expand All @@ -925,6 +949,9 @@ class CRegTestParams : public CChainParams {
if (nFalloffCoeff != -1) {
consensus.vDeployments[d].nFalloffCoeff = nFalloffCoeff;
}
if (nMNActivationHeight != -1) {
consensus.vDeployments[d].nMNActivationHeight = nMNActivationHeight;
}
}
void UpdateActivationParametersFromArgs(const ArgsManager& args);

Expand Down Expand Up @@ -998,13 +1025,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]));
}
Expand All @@ -1019,21 +1046,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;
}
}
Expand Down
1 change: 1 addition & 0 deletions src/chainparams.h
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ class CChainParams
void UpdateDIP8Parameters(int nActivationHeight);
void UpdateBudgetParameters(int nMasternodePaymentsStartBlock, int nBudgetPaymentsStartBlock, int nSuperblockStartBlock);
void UpdateLLMQInstantSend(Consensus::LLMQType llmqType);
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; }
Expand Down
4 changes: 2 additions & 2 deletions src/chainparamsbase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);

}

Expand Down
7 changes: 7 additions & 0 deletions src/consensus/params.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,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};
};

/**
Expand Down
180 changes: 180 additions & 0 deletions src/evo/mnhftx.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,12 @@
#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
{
Expand Down Expand Up @@ -80,6 +83,183 @@ bool CheckMNHFTx(const CTransaction& tx, const CBlockIndex* pindexPrev, TxValida
return true;
}

static bool extractSignals(const CBlock& block, const CBlockIndex* const pindex, std::vector<uint8_t>& signals_to_process, 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");
}
signals_to_process.push_back(mnhfTx.signal.versionBit);
}

// Checking that there's no any duplicates...
std::sort(signals_to_process.begin(), signals_to_process.end());
const auto it = std::unique(signals_to_process.begin(), signals_to_process.end());
if (std::distance(signals_to_process.begin(), it) != signals_to_process.size()) {
return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-mnhf-duplicates");
}

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;
}
if (new_signals.empty()) {
if (!fJustCheck) {
AddToCache(GetFromCache(pindex->pprev), pindex);
}
return true;
}

Signals signals = GetFromCache(pindex->pprev);
int mined_height = pindex->nHeight;

// Extra validation of signals to be sure that it can succeed
for (const auto& versionBit : new_signals) {
LogPrintf("%s: add mnhf bit=%d block:%s number of known signals:%lld\n", __func__, 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("%s -- failed: %s\n", __func__, 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) {
assert(versionBit < VERSIONBITS_NUM_BITS);

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) {
uint8_t versionBit = signal.first;
assert(versionBit < VERSIONBITS_NUM_BITS);

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(), false);
assert(update_ret);
}

Signals signals{GetFromCache(pindex)};
for (const auto& signal: signals) {
uint8_t versionBit = signal.first;
int value = signal.second;
assert(versionBit < VERSIONBITS_NUM_BITS);

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(), 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, signals);
LogPrintf("CMNHFManager::GetFromCache: mnhf feature is disabled: return empty for block %s\n", pindex->GetBlockHash().ToString());
return signals;
}
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("%s: mnhf for block %s add to cache: %lld\n", __func__, 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)",
Expand Down
46 changes: 46 additions & 0 deletions src/evo/mnhftx.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,14 @@
#include <threadsafety.h>
#include <univalue.h>

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

Expand Down Expand Up @@ -71,6 +78,45 @@ class MNHFTxPayload
}
};

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

private:
void AddToCache(const Signals& signals, const CBlockIndex* const pindex);
Signals GetFromCache(const CBlockIndex* const pindex);
};

bool CheckMNHFTx(const CTransaction& tx, const CBlockIndex* pindexPrev, TxValidationState& state) EXCLUSIVE_LOCKS_REQUIRED(cs_main);

#endif // BITCOIN_EVO_MNHFTX_H
Loading

0 comments on commit 13db37a

Please sign in to comment.