Skip to content

Commit

Permalink
feat: auto generation EHF and spork+EHF activation for MN_RR (#5597)
Browse files Browse the repository at this point in the history
Implementation EHF mechanism, part 4. Previous changes are: 
 - #4577
 - #5505
 - #5469

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

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

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


## Breaking Changes
New way of MN_RR activation.

## Checklist:
- [x] I have performed a self-review of my own code
- [x] I have commented my code, particularly in hard-to-understand areas
- [x] I have added or updated relevant unit/integration/functional/e2e
tests
- [ ] I have made corresponding changes to the documentation
- [x] I have assigned this pull request to a milestone _(for repository
code-owners and collaborators only)_

---------

Co-authored-by: UdjinM6 <UdjinM6@users.noreply.github.com>
Co-authored-by: PastaPastaPasta <6443210+PastaPastaPasta@users.noreply.github.com>
  • Loading branch information
3 people committed Oct 18, 2023
1 parent cecf63e commit 63ed462
Show file tree
Hide file tree
Showing 18 changed files with 304 additions and 35 deletions.
14 changes: 8 additions & 6 deletions src/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -213,22 +213,24 @@ BITCOIN_CORE_H = \
key_io.h \
dbwrapper.h \
limitedmap.h \
llmq/quorums.h \
llmq/blockprocessor.h \
llmq/commitment.h \
llmq/chainlocks.h \
llmq/clsig.h \
llmq/commitment.h \
llmq/context.h \
llmq/debug.h \
llmq/dkgsession.h \
llmq/dkgsessionhandler.h \
llmq/dkgsessionmgr.h \
llmq/dkgsession.h \
llmq/context.h \
llmq/ehf_signals.cpp \
llmq/ehf_signals.h \
llmq/instantsend.h \
llmq/snapshot.h \
llmq/params.h \
llmq/quorums.h \
llmq/signing.h \
llmq/signing_shares.h \
llmq/snapshot.h \
llmq/utils.h \
llmq/params.h \
logging.h \
logging/timer.h \
mapport.h \
Expand Down
12 changes: 8 additions & 4 deletions src/chainparams.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -219,10 +219,11 @@ class CMainParams : public CChainParams {
consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].bit = 10;
consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nStartTime = 19999999999; // TODO: To be determined later
consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nTimeout = Consensus::BIP9Deployment::NO_TIMEOUT;
consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nWindowSize = 4032;
consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nWindowSize = 4032; // TODO to be determined before v20 release: choose nWindowSize/nThresholdStart/nThresholdMin
consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nThresholdStart = 3226; // 80% of 4032
consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nThresholdMin = 2420; // 60% of 4032
consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nFalloffCoeff = 5; // this corresponds to 10 periods
consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nMNActivationHeight = 0; // requires EHF activation

// The best chain should have at least this much work.
consensus.nMinimumChainWork = uint256S("0x000000000000000000000000000000000000000000008677827656704520eb39"); // 1889000
Expand Down Expand Up @@ -420,6 +421,7 @@ class CTestNetParams : public CChainParams {
consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nThresholdStart = 80; // 80% of 100
consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nThresholdMin = 60; // 60% of 100
consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nFalloffCoeff = 5; // this corresponds to 10 periods
consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nMNActivationHeight = 0; // requires EHF activation

// The best chain should have at least this much work.
consensus.nMinimumChainWork = uint256S("0x00000000000000000000000000000000000000000000000002d68c8cc1b8e54b"); // 851000
Expand Down Expand Up @@ -591,6 +593,7 @@ class CDevNetParams : public CChainParams {
consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nThresholdStart = 80; // 80% of 100
consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nThresholdMin = 60; // 60% of 100
consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nFalloffCoeff = 5; // this corresponds to 10 periods
consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nMNActivationHeight = 0; // requires EHF activation

// The best chain should have at least this much work.
consensus.nMinimumChainWork = uint256S("0x000000000000000000000000000000000000000000000000000000000000000");
Expand Down Expand Up @@ -826,10 +829,11 @@ class CRegTestParams : public CChainParams {
consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].bit = 10;
consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nStartTime = 0;
consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nTimeout = Consensus::BIP9Deployment::NO_TIMEOUT;
consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nWindowSize = 1030;
consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nThresholdStart = 800; // 80% of 1000
consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nThresholdMin = 600; // 60% of 1000
consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nWindowSize = 12;
consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nThresholdStart = 9; // 80% of 12
consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nThresholdMin = 7; // 60% of 7
consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nFalloffCoeff = 5; // this corresponds to 10 periods
consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nMNActivationHeight = 0; // requires EHF activation

// The best chain should have at least this much work.
consensus.nMinimumChainWork = uint256S("0x00");
Expand Down
2 changes: 2 additions & 0 deletions src/dsnotificationinterface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include <llmq/chainlocks.h>
#include <llmq/context.h>
#include <llmq/dkgsessionmgr.h>
#include <llmq/ehf_signals.h>
#include <llmq/instantsend.h>
#include <llmq/quorums.h>

Expand Down Expand Up @@ -78,6 +79,7 @@ void CDSNotificationInterface::UpdatedBlockTip(const CBlockIndex *pindexNew, con

llmq_ctx->qman->UpdatedBlockTip(pindexNew, fInitialDownload);
llmq_ctx->qdkgsman->UpdatedBlockTip(pindexNew, fInitialDownload);
llmq_ctx->ehfSignalsHandler->UpdatedBlockTip(pindexNew);

if (!fDisableGovernance) govman.UpdatedBlockTip(pindexNew, connman);
}
Expand Down
22 changes: 18 additions & 4 deletions src/evo/mnhftx.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,24 @@
#include <string>
#include <vector>

extern const std::string MNEHF_REQUESTID_PREFIX = "mnhf";
static const std::string MNEHF_REQUESTID_PREFIX = "mnhf";
static const std::string DB_SIGNALS = "mnhf_s";

uint256 MNHFTxPayload::GetRequestId() const
{
return ::SerializeHash(std::make_pair(MNEHF_REQUESTID_PREFIX, int64_t{signal.versionBit}));
}

CMutableTransaction MNHFTxPayload::PrepareTx() const
{
CMutableTransaction tx;
tx.nVersion = 3;
tx.nType = SPECIALTX_TYPE;
SetTxPayload(tx, *this);

return tx;
}

CMNHFManager::Signals CMNHFManager::GetSignalsStage(const CBlockIndex* const pindexPrev)
{
Signals signals = GetFromCache(pindexPrev);
Expand Down Expand Up @@ -53,7 +68,7 @@ CMNHFManager::Signals CMNHFManager::GetSignalsStage(const CBlockIndex* const pin
return signals;
}

bool MNHFTx::Verify(const uint256& quorumHash, const uint256& msgHash, TxValidationState& state) const
bool MNHFTx::Verify(const uint256& quorumHash, const uint256& requestId, const uint256& msgHash, TxValidationState& state) const
{
if (versionBit >= VERSIONBITS_NUM_BITS) {
return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-mnhf-nbit-out-of-bounds");
Expand All @@ -62,7 +77,6 @@ bool MNHFTx::Verify(const uint256& quorumHash, const uint256& msgHash, TxValidat
const Consensus::LLMQType& llmqType = Params().GetConsensus().llmqTypeMnhf;
const auto quorum = llmq::quorumManager->GetQuorum(llmqType, quorumHash);

const uint256 requestId = ::SerializeHash(std::make_pair(MNEHF_REQUESTID_PREFIX, int64_t{versionBit}));
const uint256 signHash = llmq::utils::BuildSignHash(llmqType, quorum->qc->quorumHash, requestId, msgHash);
if (!sig.VerifyInsecure(quorum->qc->quorumPublicKey, signHash)) {
return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-mnhf-invalid");
Expand Down Expand Up @@ -104,7 +118,7 @@ bool CheckMNHFTx(const CTransaction& tx, const CBlockIndex* pindexPrev, TxValida
uint256 msgHash = tx_copy.GetHash();


if (!mnhfTx.signal.Verify(mnhfTx.signal.quorumHash, msgHash, state)) {
if (!mnhfTx.signal.Verify(mnhfTx.signal.quorumHash, mnhfTx.GetRequestId(), msgHash, state)) {
// set up inside Verify
return false;
}
Expand Down
15 changes: 13 additions & 2 deletions src/evo/mnhftx.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class MNHFTx
CBLSSignature sig{};

MNHFTx() = default;
bool Verify(const uint256& quorumHash, const uint256& msgHash, TxValidationState& state) const;
bool Verify(const uint256& quorumHash, const uint256& requestId, const uint256& msgHash, TxValidationState& state) const;

SERIALIZE_METHODS(MNHFTx, obj)
{
Expand Down Expand Up @@ -63,6 +63,17 @@ class MNHFTxPayload
uint8_t nVersion{CURRENT_VERSION};
MNHFTx signal;

public:
/**
* helper function to calculate Request ID used for signing
*/
uint256 GetRequestId() const;

/**
* helper function to prepare special transaction for signing
*/
CMutableTransaction PrepareTx() const;

SERIALIZE_METHODS(MNHFTxPayload, obj)
{
READWRITE(obj.nVersion, obj.signal);
Expand Down Expand Up @@ -120,6 +131,7 @@ class CMNHFManager
* 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);

Expand All @@ -129,7 +141,6 @@ class CMNHFManager
* validate them by
*/
Signals GetFromCache(const CBlockIndex* const pindex);

};

std::optional<uint8_t> extractEHFSignal(const CTransaction& tx);
Expand Down
4 changes: 3 additions & 1 deletion src/llmq/context.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include <llmq/commitment.h>
#include <llmq/debug.h>
#include <llmq/dkgsessionmgr.h>
#include <llmq/ehf_signals.h>
#include <llmq/instantsend.h>
#include <llmq/quorums.h>
#include <llmq/signing.h>
Expand Down Expand Up @@ -45,7 +46,8 @@ LLMQContext::LLMQContext(CChainState& chainstate, CConnman& connman, CEvoDB& evo
assert(llmq::quorumInstantSendManager == nullptr);
llmq::quorumInstantSendManager = std::make_unique<llmq::CInstantSendManager>(*llmq::chainLocksHandler, chainstate, connman, *llmq::quorumManager, *sigman, *shareman, sporkman, mempool, *::masternodeSync, peerman, unit_tests, wipe);
return llmq::quorumInstantSendManager.get();
}()}
}()},
ehfSignalsHandler{std::make_unique<llmq::CEHFSignalsHandler>(chainstate, connman, *sigman, *shareman, sporkman, *llmq::quorumManager, mempool)}
{
// NOTE: we use this only to wipe the old db, do NOT use it for anything else
// TODO: remove it in some future version
Expand Down
13 changes: 8 additions & 5 deletions src/llmq/context.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,25 +12,27 @@ class CChainState;
class CConnman;
class CDBWrapper;
class CEvoDB;
class CTxMemPool;
class CSporkManager;
class CTxMemPool;
class PeerManager;

namespace llmq {
class CChainLocksHandler;
class CDKGDebugManager;
class CQuorumBlockProcessor;
class CDKGSessionManager;
class CEHFSignalsHandler;
class CInstantSendManager;
class CQuorumBlockProcessor;
class CQuorumManager;
class CSigSharesManager;
class CSigningManager;
class CChainLocksHandler;
class CInstantSendManager;
}

struct LLMQContext {
LLMQContext() = delete;
LLMQContext(const LLMQContext&) = delete;
LLMQContext(CChainState& chainstate, CConnman& connman, CEvoDB& evo_db, CSporkManager& sporkman, CTxMemPool& mempool,
LLMQContext(CChainState& chainstate, CConnman& connman, CEvoDB& evo_db, CSporkManager& sporkman,
CTxMemPool& mempool,
const std::unique_ptr<PeerManager>& peerman, bool unit_tests, bool wipe);
~LLMQContext();

Expand All @@ -57,6 +59,7 @@ struct LLMQContext {
const std::unique_ptr<llmq::CSigSharesManager> shareman;
llmq::CChainLocksHandler* const clhandler;
llmq::CInstantSendManager* const isman;
const std::unique_ptr<llmq::CEHFSignalsHandler> ehfSignalsHandler;
};

#endif // BITCOIN_LLMQ_CONTEXT_H
132 changes: 132 additions & 0 deletions src/llmq/ehf_signals.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
// Copyright (c) 2023 The Dash Core developers
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include <llmq/ehf_signals.h>
#include <llmq/utils.h>
#include <llmq/quorums.h>
#include <llmq/signing_shares.h>
#include <llmq/commitment.h>


#include <evo/mnhftx.h>
#include <evo/specialtx.h>

#include <index/txindex.h> // g_txindex

#include <primitives/transaction.h>
#include <spork.h>
#include <txmempool.h>
#include <validation.h>

namespace llmq {


CEHFSignalsHandler::CEHFSignalsHandler(CChainState& chainstate, CConnman& connman,
CSigningManager& sigman, CSigSharesManager& shareman,
const CSporkManager& sporkman, const CQuorumManager& qman, CTxMemPool& mempool) :
chainstate(chainstate),
connman(connman),
sigman(sigman),
shareman(shareman),
sporkman(sporkman),
qman(qman),
mempool(mempool)
{
sigman.RegisterRecoveredSigsListener(this);
}


CEHFSignalsHandler::~CEHFSignalsHandler()
{
sigman.UnregisterRecoveredSigsListener(this);
}

void CEHFSignalsHandler::UpdatedBlockTip(const CBlockIndex* const pindexNew)
{
if (!fMasternodeMode || !llmq::utils::IsV20Active(pindexNew) || !sporkman.IsSporkActive(SPORK_24_EHF)) {
return;
}

// TODO: should do this for all not-yet-signied bits
trySignEHFSignal(Params().GetConsensus().vDeployments[Consensus::DEPLOYMENT_MN_RR].bit, pindexNew);
}

void CEHFSignalsHandler::trySignEHFSignal(int bit, const CBlockIndex* const pindex)
{
MNHFTxPayload mnhfPayload;
mnhfPayload.signal.versionBit = bit;
const uint256 requestId = mnhfPayload.GetRequestId();

LogPrintf("CEHFSignalsHandler::trySignEHFSignal: bit=%d at height=%d id=%s\n", bit, pindex->nHeight, requestId.ToString());

const Consensus::LLMQType& llmqType = Params().GetConsensus().llmqTypeMnhf;
const auto& llmq_params_opt = llmq::GetLLMQParams(llmqType);
if (!llmq_params_opt.has_value()) {
return;
}
if (sigman.HasRecoveredSigForId(llmqType, requestId)) {
LOCK(cs);
ids.insert(requestId);

// no need to sign same message one more time
return;
}

const auto quorum = sigman.SelectQuorumForSigning(llmq_params_opt.value(), qman, requestId);
if (!quorum) {
LogPrintf("CEHFSignalsHandler::trySignEHFSignal no quorum for id=%s\n", requestId.ToString());
return;
}

mnhfPayload.signal.quorumHash = quorum->qc->quorumHash;
const uint256 msgHash = mnhfPayload.PrepareTx().GetHash();

{
LOCK(cs);
ids.insert(requestId);
}
sigman.AsyncSignIfMember(llmqType, shareman, requestId, msgHash);
}

void CEHFSignalsHandler::HandleNewRecoveredSig(const CRecoveredSig& recoveredSig)
{
if (g_txindex) {
g_txindex->BlockUntilSyncedToCurrentChain();
}

if (WITH_LOCK(cs, return ids.find(recoveredSig.getId()) == ids.end())) {
// Do nothing, it's not for this handler
return;
}

MNHFTxPayload mnhfPayload;
// TODO: should do this for all not-yet-signied bits
mnhfPayload.signal.versionBit = Params().GetConsensus().vDeployments[Consensus::DEPLOYMENT_MN_RR].bit;

const uint256 expectedId = mnhfPayload.GetRequestId();
LogPrintf("CEHFSignalsHandler::HandleNewRecoveredSig expecting ID=%s received=%s\n", expectedId.ToString(), recoveredSig.getId().ToString());
if (recoveredSig.getId() != mnhfPayload.GetRequestId()) {
// there's nothing interesting for CEHFSignalsHandler
LogPrintf("CEHFSignalsHandler::HandleNewRecoveredSig id is known but it's not MN_RR, expected: %s\n", mnhfPayload.GetRequestId().ToString());
return;
}

mnhfPayload.signal.quorumHash = recoveredSig.getQuorumHash();
mnhfPayload.signal.sig = recoveredSig.sig.Get();

CMutableTransaction tx = mnhfPayload.PrepareTx();

{
CTransactionRef tx_to_sent = MakeTransactionRef(std::move(tx));
LogPrintf("CEHFSignalsHandler::HandleNewRecoveredSig Special EHF TX is created hash=%s\n", tx_to_sent->GetHash().ToString());
LOCK(cs_main);
TxValidationState state;
if (AcceptToMemoryPool(chainstate, mempool, state, tx_to_sent, /* bypass_limits=*/ false, /* nAbsurdFee=*/ 0)) {
connman.RelayTransaction(*tx_to_sent);
} else {
LogPrintf("CEHFSignalsHandler::HandleNewRecoveredSig -- AcceptToMemoryPool failed: %s\n", state.ToString());
}
}
}
} // namespace llmq

0 comments on commit 63ed462

Please sign in to comment.