Skip to content

Commit 63ed462

Browse files
knstUdjinM6PastaPastaPasta
authored
feat: auto generation EHF and spork+EHF activation for MN_RR (#5597)
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 <[email protected]> Co-authored-by: PastaPastaPasta <[email protected]>
1 parent cecf63e commit 63ed462

18 files changed

+304
-35
lines changed

src/Makefile.am

+8-6
Original file line numberDiff line numberDiff line change
@@ -213,22 +213,24 @@ BITCOIN_CORE_H = \
213213
key_io.h \
214214
dbwrapper.h \
215215
limitedmap.h \
216-
llmq/quorums.h \
217216
llmq/blockprocessor.h \
218-
llmq/commitment.h \
219217
llmq/chainlocks.h \
220218
llmq/clsig.h \
219+
llmq/commitment.h \
220+
llmq/context.h \
221221
llmq/debug.h \
222+
llmq/dkgsession.h \
222223
llmq/dkgsessionhandler.h \
223224
llmq/dkgsessionmgr.h \
224-
llmq/dkgsession.h \
225-
llmq/context.h \
225+
llmq/ehf_signals.cpp \
226+
llmq/ehf_signals.h \
226227
llmq/instantsend.h \
227-
llmq/snapshot.h \
228+
llmq/params.h \
229+
llmq/quorums.h \
228230
llmq/signing.h \
229231
llmq/signing_shares.h \
232+
llmq/snapshot.h \
230233
llmq/utils.h \
231-
llmq/params.h \
232234
logging.h \
233235
logging/timer.h \
234236
mapport.h \

src/chainparams.cpp

+8-4
Original file line numberDiff line numberDiff line change
@@ -219,10 +219,11 @@ class CMainParams : public CChainParams {
219219
consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].bit = 10;
220220
consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nStartTime = 19999999999; // TODO: To be determined later
221221
consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nTimeout = Consensus::BIP9Deployment::NO_TIMEOUT;
222-
consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nWindowSize = 4032;
222+
consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nWindowSize = 4032; // TODO to be determined before v20 release: choose nWindowSize/nThresholdStart/nThresholdMin
223223
consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nThresholdStart = 3226; // 80% of 4032
224224
consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nThresholdMin = 2420; // 60% of 4032
225225
consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nFalloffCoeff = 5; // this corresponds to 10 periods
226+
consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nMNActivationHeight = 0; // requires EHF activation
226227

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

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

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

834838
// The best chain should have at least this much work.
835839
consensus.nMinimumChainWork = uint256S("0x00");

src/dsnotificationinterface.cpp

+2
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include <llmq/chainlocks.h>
2020
#include <llmq/context.h>
2121
#include <llmq/dkgsessionmgr.h>
22+
#include <llmq/ehf_signals.h>
2223
#include <llmq/instantsend.h>
2324
#include <llmq/quorums.h>
2425

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

7980
llmq_ctx->qman->UpdatedBlockTip(pindexNew, fInitialDownload);
8081
llmq_ctx->qdkgsman->UpdatedBlockTip(pindexNew, fInitialDownload);
82+
llmq_ctx->ehfSignalsHandler->UpdatedBlockTip(pindexNew);
8183

8284
if (!fDisableGovernance) govman.UpdatedBlockTip(pindexNew, connman);
8385
}

src/evo/mnhftx.cpp

+18-4
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,24 @@
1919
#include <string>
2020
#include <vector>
2121

22-
extern const std::string MNEHF_REQUESTID_PREFIX = "mnhf";
22+
static const std::string MNEHF_REQUESTID_PREFIX = "mnhf";
2323
static const std::string DB_SIGNALS = "mnhf_s";
2424

25+
uint256 MNHFTxPayload::GetRequestId() const
26+
{
27+
return ::SerializeHash(std::make_pair(MNEHF_REQUESTID_PREFIX, int64_t{signal.versionBit}));
28+
}
29+
30+
CMutableTransaction MNHFTxPayload::PrepareTx() const
31+
{
32+
CMutableTransaction tx;
33+
tx.nVersion = 3;
34+
tx.nType = SPECIALTX_TYPE;
35+
SetTxPayload(tx, *this);
36+
37+
return tx;
38+
}
39+
2540
CMNHFManager::Signals CMNHFManager::GetSignalsStage(const CBlockIndex* const pindexPrev)
2641
{
2742
Signals signals = GetFromCache(pindexPrev);
@@ -53,7 +68,7 @@ CMNHFManager::Signals CMNHFManager::GetSignalsStage(const CBlockIndex* const pin
5368
return signals;
5469
}
5570

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

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

106120

107-
if (!mnhfTx.signal.Verify(mnhfTx.signal.quorumHash, msgHash, state)) {
121+
if (!mnhfTx.signal.Verify(mnhfTx.signal.quorumHash, mnhfTx.GetRequestId(), msgHash, state)) {
108122
// set up inside Verify
109123
return false;
110124
}

src/evo/mnhftx.h

+13-2
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ class MNHFTx
3232
CBLSSignature sig{};
3333

3434
MNHFTx() = default;
35-
bool Verify(const uint256& quorumHash, const uint256& msgHash, TxValidationState& state) const;
35+
bool Verify(const uint256& quorumHash, const uint256& requestId, const uint256& msgHash, TxValidationState& state) const;
3636

3737
SERIALIZE_METHODS(MNHFTx, obj)
3838
{
@@ -63,6 +63,17 @@ class MNHFTxPayload
6363
uint8_t nVersion{CURRENT_VERSION};
6464
MNHFTx signal;
6565

66+
public:
67+
/**
68+
* helper function to calculate Request ID used for signing
69+
*/
70+
uint256 GetRequestId() const;
71+
72+
/**
73+
* helper function to prepare special transaction for signing
74+
*/
75+
CMutableTransaction PrepareTx() const;
76+
6677
SERIALIZE_METHODS(MNHFTxPayload, obj)
6778
{
6879
READWRITE(obj.nVersion, obj.signal);
@@ -120,6 +131,7 @@ class CMNHFManager
120131
* This member function is not const because it calls non-const GetFromCache()
121132
*/
122133
Signals GetSignalsStage(const CBlockIndex* const pindexPrev);
134+
123135
private:
124136
void AddToCache(const Signals& signals, const CBlockIndex* const pindex);
125137

@@ -129,7 +141,6 @@ class CMNHFManager
129141
* validate them by
130142
*/
131143
Signals GetFromCache(const CBlockIndex* const pindex);
132-
133144
};
134145

135146
std::optional<uint8_t> extractEHFSignal(const CTransaction& tx);

src/llmq/context.cpp

+3-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include <llmq/commitment.h>
1313
#include <llmq/debug.h>
1414
#include <llmq/dkgsessionmgr.h>
15+
#include <llmq/ehf_signals.h>
1516
#include <llmq/instantsend.h>
1617
#include <llmq/quorums.h>
1718
#include <llmq/signing.h>
@@ -45,7 +46,8 @@ LLMQContext::LLMQContext(CChainState& chainstate, CConnman& connman, CEvoDB& evo
4546
assert(llmq::quorumInstantSendManager == nullptr);
4647
llmq::quorumInstantSendManager = std::make_unique<llmq::CInstantSendManager>(*llmq::chainLocksHandler, chainstate, connman, *llmq::quorumManager, *sigman, *shareman, sporkman, mempool, *::masternodeSync, peerman, unit_tests, wipe);
4748
return llmq::quorumInstantSendManager.get();
48-
}()}
49+
}()},
50+
ehfSignalsHandler{std::make_unique<llmq::CEHFSignalsHandler>(chainstate, connman, *sigman, *shareman, sporkman, *llmq::quorumManager, mempool)}
4951
{
5052
// NOTE: we use this only to wipe the old db, do NOT use it for anything else
5153
// TODO: remove it in some future version

src/llmq/context.h

+8-5
Original file line numberDiff line numberDiff line change
@@ -12,25 +12,27 @@ class CChainState;
1212
class CConnman;
1313
class CDBWrapper;
1414
class CEvoDB;
15-
class CTxMemPool;
1615
class CSporkManager;
16+
class CTxMemPool;
1717
class PeerManager;
1818

1919
namespace llmq {
20+
class CChainLocksHandler;
2021
class CDKGDebugManager;
21-
class CQuorumBlockProcessor;
2222
class CDKGSessionManager;
23+
class CEHFSignalsHandler;
24+
class CInstantSendManager;
25+
class CQuorumBlockProcessor;
2326
class CQuorumManager;
2427
class CSigSharesManager;
2528
class CSigningManager;
26-
class CChainLocksHandler;
27-
class CInstantSendManager;
2829
}
2930

3031
struct LLMQContext {
3132
LLMQContext() = delete;
3233
LLMQContext(const LLMQContext&) = delete;
33-
LLMQContext(CChainState& chainstate, CConnman& connman, CEvoDB& evo_db, CSporkManager& sporkman, CTxMemPool& mempool,
34+
LLMQContext(CChainState& chainstate, CConnman& connman, CEvoDB& evo_db, CSporkManager& sporkman,
35+
CTxMemPool& mempool,
3436
const std::unique_ptr<PeerManager>& peerman, bool unit_tests, bool wipe);
3537
~LLMQContext();
3638

@@ -57,6 +59,7 @@ struct LLMQContext {
5759
const std::unique_ptr<llmq::CSigSharesManager> shareman;
5860
llmq::CChainLocksHandler* const clhandler;
5961
llmq::CInstantSendManager* const isman;
62+
const std::unique_ptr<llmq::CEHFSignalsHandler> ehfSignalsHandler;
6063
};
6164

6265
#endif // BITCOIN_LLMQ_CONTEXT_H

src/llmq/ehf_signals.cpp

+132
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
// Copyright (c) 2023 The Dash Core developers
2+
// Distributed under the MIT/X11 software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#include <llmq/ehf_signals.h>
6+
#include <llmq/utils.h>
7+
#include <llmq/quorums.h>
8+
#include <llmq/signing_shares.h>
9+
#include <llmq/commitment.h>
10+
11+
12+
#include <evo/mnhftx.h>
13+
#include <evo/specialtx.h>
14+
15+
#include <index/txindex.h> // g_txindex
16+
17+
#include <primitives/transaction.h>
18+
#include <spork.h>
19+
#include <txmempool.h>
20+
#include <validation.h>
21+
22+
namespace llmq {
23+
24+
25+
CEHFSignalsHandler::CEHFSignalsHandler(CChainState& chainstate, CConnman& connman,
26+
CSigningManager& sigman, CSigSharesManager& shareman,
27+
const CSporkManager& sporkman, const CQuorumManager& qman, CTxMemPool& mempool) :
28+
chainstate(chainstate),
29+
connman(connman),
30+
sigman(sigman),
31+
shareman(shareman),
32+
sporkman(sporkman),
33+
qman(qman),
34+
mempool(mempool)
35+
{
36+
sigman.RegisterRecoveredSigsListener(this);
37+
}
38+
39+
40+
CEHFSignalsHandler::~CEHFSignalsHandler()
41+
{
42+
sigman.UnregisterRecoveredSigsListener(this);
43+
}
44+
45+
void CEHFSignalsHandler::UpdatedBlockTip(const CBlockIndex* const pindexNew)
46+
{
47+
if (!fMasternodeMode || !llmq::utils::IsV20Active(pindexNew) || !sporkman.IsSporkActive(SPORK_24_EHF)) {
48+
return;
49+
}
50+
51+
// TODO: should do this for all not-yet-signied bits
52+
trySignEHFSignal(Params().GetConsensus().vDeployments[Consensus::DEPLOYMENT_MN_RR].bit, pindexNew);
53+
}
54+
55+
void CEHFSignalsHandler::trySignEHFSignal(int bit, const CBlockIndex* const pindex)
56+
{
57+
MNHFTxPayload mnhfPayload;
58+
mnhfPayload.signal.versionBit = bit;
59+
const uint256 requestId = mnhfPayload.GetRequestId();
60+
61+
LogPrintf("CEHFSignalsHandler::trySignEHFSignal: bit=%d at height=%d id=%s\n", bit, pindex->nHeight, requestId.ToString());
62+
63+
const Consensus::LLMQType& llmqType = Params().GetConsensus().llmqTypeMnhf;
64+
const auto& llmq_params_opt = llmq::GetLLMQParams(llmqType);
65+
if (!llmq_params_opt.has_value()) {
66+
return;
67+
}
68+
if (sigman.HasRecoveredSigForId(llmqType, requestId)) {
69+
LOCK(cs);
70+
ids.insert(requestId);
71+
72+
// no need to sign same message one more time
73+
return;
74+
}
75+
76+
const auto quorum = sigman.SelectQuorumForSigning(llmq_params_opt.value(), qman, requestId);
77+
if (!quorum) {
78+
LogPrintf("CEHFSignalsHandler::trySignEHFSignal no quorum for id=%s\n", requestId.ToString());
79+
return;
80+
}
81+
82+
mnhfPayload.signal.quorumHash = quorum->qc->quorumHash;
83+
const uint256 msgHash = mnhfPayload.PrepareTx().GetHash();
84+
85+
{
86+
LOCK(cs);
87+
ids.insert(requestId);
88+
}
89+
sigman.AsyncSignIfMember(llmqType, shareman, requestId, msgHash);
90+
}
91+
92+
void CEHFSignalsHandler::HandleNewRecoveredSig(const CRecoveredSig& recoveredSig)
93+
{
94+
if (g_txindex) {
95+
g_txindex->BlockUntilSyncedToCurrentChain();
96+
}
97+
98+
if (WITH_LOCK(cs, return ids.find(recoveredSig.getId()) == ids.end())) {
99+
// Do nothing, it's not for this handler
100+
return;
101+
}
102+
103+
MNHFTxPayload mnhfPayload;
104+
// TODO: should do this for all not-yet-signied bits
105+
mnhfPayload.signal.versionBit = Params().GetConsensus().vDeployments[Consensus::DEPLOYMENT_MN_RR].bit;
106+
107+
const uint256 expectedId = mnhfPayload.GetRequestId();
108+
LogPrintf("CEHFSignalsHandler::HandleNewRecoveredSig expecting ID=%s received=%s\n", expectedId.ToString(), recoveredSig.getId().ToString());
109+
if (recoveredSig.getId() != mnhfPayload.GetRequestId()) {
110+
// there's nothing interesting for CEHFSignalsHandler
111+
LogPrintf("CEHFSignalsHandler::HandleNewRecoveredSig id is known but it's not MN_RR, expected: %s\n", mnhfPayload.GetRequestId().ToString());
112+
return;
113+
}
114+
115+
mnhfPayload.signal.quorumHash = recoveredSig.getQuorumHash();
116+
mnhfPayload.signal.sig = recoveredSig.sig.Get();
117+
118+
CMutableTransaction tx = mnhfPayload.PrepareTx();
119+
120+
{
121+
CTransactionRef tx_to_sent = MakeTransactionRef(std::move(tx));
122+
LogPrintf("CEHFSignalsHandler::HandleNewRecoveredSig Special EHF TX is created hash=%s\n", tx_to_sent->GetHash().ToString());
123+
LOCK(cs_main);
124+
TxValidationState state;
125+
if (AcceptToMemoryPool(chainstate, mempool, state, tx_to_sent, /* bypass_limits=*/ false, /* nAbsurdFee=*/ 0)) {
126+
connman.RelayTransaction(*tx_to_sent);
127+
} else {
128+
LogPrintf("CEHFSignalsHandler::HandleNewRecoveredSig -- AcceptToMemoryPool failed: %s\n", state.ToString());
129+
}
130+
}
131+
}
132+
} // namespace llmq

0 commit comments

Comments
 (0)