From d9b28fe1ad6e1fc07cdb185e9d41c6eab2548015 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 10 Dec 2018 06:04:33 +0100 Subject: [PATCH] Introduce dummy (ping-like) contributions for the dummy DKG (#2542) * Implement creation and propagation of dummy contributions These act as a ping which is broadcast a few blocks before the dummy commitments are created. They are meant to determine online/offline members. * Use information about received dummy contributions to determine validMembers * Fix PoSe tests * Fix dummy DKG phase progress in PoSe tests and give tests more time Mine one block at a time until we reach the mining phase. --- qa/rpc-tests/dip3-deterministicmns.py | 10 +- src/llmq/quorums_dummydkg.cpp | 200 +++++++++++++++++++++++--- src/llmq/quorums_dummydkg.h | 40 +++++- src/net_processing.cpp | 10 ++ src/protocol.cpp | 3 + src/protocol.h | 2 + 6 files changed, 235 insertions(+), 30 deletions(-) diff --git a/qa/rpc-tests/dip3-deterministicmns.py b/qa/rpc-tests/dip3-deterministicmns.py index dfcd14a56c..0ee75ae2d1 100755 --- a/qa/rpc-tests/dip3-deterministicmns.py +++ b/qa/rpc-tests/dip3-deterministicmns.py @@ -360,14 +360,14 @@ class DIP3Test(BitcoinTestFramework): punished = False banned = False t = time.time() - while (not punished or not banned) and (time.time() - t) < 60: + while (not punished or not banned) and (time.time() - t) < 120: time.sleep(1) - # 2 dummy phases - for j in range(2): - self.nodes[0].generate(2) + # 10 blocks until we can mine the dummy commitment + for j in range(10): + self.nodes[0].generate(1) self.sync_all() - time.sleep(1) + time.sleep(0.5) info = self.nodes[0].protx('info', mn.protx_hash) if not punished: diff --git a/src/llmq/quorums_dummydkg.cpp b/src/llmq/quorums_dummydkg.cpp index 384720d9ba..8967853000 100644 --- a/src/llmq/quorums_dummydkg.cpp +++ b/src/llmq/quorums_dummydkg.cpp @@ -27,7 +27,28 @@ CDummyDKG* quorumDummyDKG; void CDummyDKG::ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStream& vRecv, CConnman& connman) { - if (strCommand == NetMsgType::QDCOMMITMENT) { + if (strCommand == NetMsgType::QCONTRIB) { + if (!sporkManager.IsSporkActive(SPORK_17_QUORUM_DKG_ENABLED)) { + return; + } + + CDummyContribution qc; + try { + vRecv >> qc; + } catch (...) { + // When we switch to the real DKG, non-upgraded nodes will get to this point. Let them just ignore the + // incompatible messages + return; + } + + uint256 hash = ::SerializeHash(qc); + { + LOCK(cs_main); + connman.RemoveAskFor(hash); + } + + ProcessDummyContribution(pfrom->id, qc); + } else if (strCommand == NetMsgType::QDCOMMITMENT) { if (!Params().GetConsensus().fLLMQAllowDummyCommitments) { Misbehaving(pfrom->id, 100); return; @@ -49,6 +70,89 @@ void CDummyDKG::ProcessMessage(CNode* pfrom, const std::string& strCommand, CDat } } +void CDummyDKG::ProcessDummyContribution(NodeId from, const llmq::CDummyContribution& qc) +{ + if (!Params().GetConsensus().llmqs.count((Consensus::LLMQType)qc.llmqType)) { + LOCK(cs_main); + LogPrintf("CDummyDKG::%s -- invalid commitment type %d, peer=%d\n", __func__, + qc.llmqType, from); + if (from != -1) { + Misbehaving(from, 100); + } + return; + } + + auto type = (Consensus::LLMQType)qc.llmqType; + const auto& params = Params().GetConsensus().llmqs.at(type); + + int curQuorumHeight; + const CBlockIndex* quorumIndex; + { + LOCK(cs_main); + curQuorumHeight = chainActive.Height() - (chainActive.Height() % params.dkgInterval); + quorumIndex = chainActive[curQuorumHeight]; + } + uint256 quorumHash = quorumIndex->GetBlockHash(); + if (qc.quorumHash != quorumHash) { + LogPrintf("CDummyDKG::%s -- dummy contrinution for wrong quorum, peer=%d\n", __func__, + from); + return; + } + + auto members = CLLMQUtils::GetAllQuorumMembers(type, qc.quorumHash); + if (members.size() != params.size) { + LOCK(cs_main); + LogPrintf("CDummyDKG::%s -- invalid members count %d, peer=%d\n", __func__, + members.size(), from); + if (from != -1) { + Misbehaving(from, 100); + } + return; + } + if (qc.signer >= members.size()) { + LOCK(cs_main); + LogPrintf("CDummyDKG::%s -- invalid signer %d, peer=%d\n", __func__, + qc.signer, from); + if (from != -1) { + Misbehaving(from, 100); + } + return; + } + + auto signer = members[qc.signer]; + + { + LOCK(sessionCs); + if (curSessions[type].dummyContributionsFromMembers.count(signer->proTxHash)) { + return; + } + } + + // verify member sig + if (!qc.sig.VerifyInsecure(signer->pdmnState->pubKeyOperator, qc.GetSignHash())) { + LOCK(cs_main); + LogPrintf("CDummyDKG::%s -- invalid memberSig, peer=%d\n", __func__, + from); + if (from != -1) { + Misbehaving(from, 100); + } + return; + } + + LogPrintf("CDummyDKG::%s -- processed dummy contribution for quorum %s:%d, signer=%d, peer=%d\n", __func__, + qc.quorumHash.ToString(), qc.llmqType, qc.signer, from); + + uint256 hash = ::SerializeHash(qc); + { + LOCK(sessionCs); + curSessions[type].dummyContributions[hash] = qc; + curSessions[type].dummyContributionsFromMembers[signer->proTxHash] = hash; + } + + CInv inv(MSG_QUORUM_DUMMY_CONTRIBUTION, hash); + g_connman->RelayInv(inv, DMN_PROTO_VERSION); +} + void CDummyDKG::ProcessDummyCommitment(NodeId from, const llmq::CDummyCommitment& qc) { if (!Params().GetConsensus().llmqs.count((Consensus::LLMQType)qc.llmqType)) { @@ -174,16 +278,6 @@ void CDummyDKG::ProcessDummyCommitment(NodeId from, const llmq::CDummyCommitment uint256 hash = ::SerializeHash(qc); { LOCK(sessionCs); - - // Mark all members as bad initially and clear that state when we receive a valid dummy commitment from them - // This information is then used in the next sessions to determine which ones are valid - if (curSessions[type].dummyCommitments.empty()) { - for (const auto& dmn : members) { - curSessions[type].badMembers.emplace(dmn->proTxHash); - } - } - - curSessions[type].badMembers.erase(signer->proTxHash); curSessions[type].dummyCommitments[hash] = qc; curSessions[type].dummyCommitmentsFromMembers[commitmentHash][signer->proTxHash] = hash; } @@ -215,13 +309,52 @@ void CDummyDKG::UpdatedBlockTip(const CBlockIndex* pindex, bool fInitialDownload const auto& params = p.second; int phaseIndex = pindex->nHeight % params.dkgInterval; if (phaseIndex == 0) { - CreateDummyCommitment(params.type, pindex); + CreateDummyContribution(params.type, pindex); } else if (phaseIndex == params.dkgPhaseBlocks * 2) { + CreateDummyCommitment(params.type, pindex); + } else if (phaseIndex == params.dkgPhaseBlocks * 4) { CreateFinalCommitment(params.type, pindex); } } } +void CDummyDKG::CreateDummyContribution(Consensus::LLMQType llmqType, const CBlockIndex* pindex) +{ + const auto& params = Params().GetConsensus().llmqs.at(llmqType); + int quorumHeight = pindex->nHeight - (pindex->nHeight % params.dkgInterval); + const CBlockIndex* quorumIndex; + { + LOCK(cs_main); + quorumIndex = chainActive[quorumHeight]; + } + uint256 quorumHash = quorumIndex->GetBlockHash(); + + auto members = CLLMQUtils::GetAllQuorumMembers(llmqType, quorumHash); + if (members.size() != params.size) { + return; + } + + int myIdx = -1; + for (size_t i = 0; i < members.size(); i++) { + if (members[i]->collateralOutpoint == activeMasternodeInfo.outpoint) { + myIdx = (int)i; + break; + } + } + if (myIdx == -1) { + return; + } + auto signer = members[myIdx]; + + CDummyContribution qc; + qc.llmqType = (uint8_t)llmqType; + qc.quorumHash = quorumHash; + qc.signer = (uint16_t)myIdx; + qc.sig = activeMasternodeInfo.blsKeyOperator->Sign(qc.GetSignHash()); + + ProcessDummyContribution(-1, qc); +} + void CDummyDKG::CreateDummyCommitment(Consensus::LLMQType llmqType, const CBlockIndex* pindex) { const auto& params = Params().GetConsensus().llmqs.at(llmqType); @@ -254,6 +387,9 @@ void CDummyDKG::CreateDummyCommitment(Consensus::LLMQType llmqType, const CBlock auto vvec = BuildVvec(svec); auto vvecHash = ::SerializeHash(vvec); auto validMembers = GetValidMembers(llmqType, members); + if (std::count(validMembers.begin(), validMembers.end(), true) < params.minSize) { + return; + } auto commitmentHash = CLLMQUtils::BuildCommitmentHash((uint8_t)llmqType, quorumHash, validMembers, vvec[0], vvecHash); @@ -356,9 +492,8 @@ void CDummyDKG::CreateFinalCommitment(Consensus::LLMQType llmqType, const CBlock quorumBlockProcessor->AddMinableCommitment(fqc); } - prevSessions[llmqType].badMembers = curSessions[llmqType].badMembers; - prevSessions[llmqType].dummyCommitments = std::move(curSessions[llmqType].dummyCommitments); - prevSessions[llmqType].dummyCommitmentsFromMembers = std::move(curSessions[llmqType].dummyCommitmentsFromMembers); + // reset for next round + curSessions[llmqType] = CDummyDKGSession(); } std::vector CDummyDKG::GetValidMembers(Consensus::LLMQType llmqType, const std::vector& members) @@ -368,20 +503,14 @@ std::vector CDummyDKG::GetValidMembers(Consensus::LLMQType llmqType, const LOCK(sessionCs); - // Valid members are members that sent us a dummy commitment in the previous session + // Valid members are members that sent us a dummy contribution in this session for (size_t i = 0; i < params.size; i++) { - if (!prevSessions[llmqType].badMembers.count(members[i]->proTxHash)) { + if (curSessions[llmqType].dummyContributionsFromMembers.count(members[i]->proTxHash)) { ret[i] = true; } } - // set all to valid if last sessions failed (this reboots everything) - if (std::count(ret.begin(), ret.end(), true) < params.minSize) { - for (size_t i = 0; i < params.size; i++) { - ret[i] = true; - } - } return ret; } @@ -426,6 +555,31 @@ BLSPublicKeyVector CDummyDKG::BuildVvec(const BLSSecretKeyVector& svec) return vvec; } +bool CDummyDKG::HasDummyContribution(const uint256& hash) +{ + LOCK(sessionCs); + for (const auto& p : curSessions) { + auto it = p.second.dummyContributions.find(hash); + if (it != p.second.dummyContributions.end()) { + return true; + } + } + return false; +} + +bool CDummyDKG::GetDummyContribution(const uint256& hash, CDummyContribution& ret) +{ + LOCK(sessionCs); + for (const auto& p : curSessions) { + auto it = p.second.dummyContributions.find(hash); + if (it != p.second.dummyContributions.end()) { + ret = it->second; + return true; + } + } + return false; +} + bool CDummyDKG::HasDummyCommitment(const uint256& hash) { LOCK(sessionCs); diff --git a/src/llmq/quorums_dummydkg.h b/src/llmq/quorums_dummydkg.h index ca583f0ea3..c41cf9c1c2 100644 --- a/src/llmq/quorums_dummydkg.h +++ b/src/llmq/quorums_dummydkg.h @@ -43,6 +43,38 @@ class CConnman; namespace llmq { +// This is more like a PING than a contribution +// We will later replace this message (reusing same inv type) for the real contribution +// Deserialization will then be incompatible between peers, but that is fine (let them reject the messages) +class CDummyContribution +{ +public: + uint8_t llmqType{Consensus::LLMQ_NONE}; + uint256 quorumHash; + uint16_t signer{(uint16_t)-1}; + + CBLSSignature sig; + +public: + ADD_SERIALIZE_METHODS + + template + inline void SerializationOp(Stream& s, Operation ser_action) + { + READWRITE(llmqType); + READWRITE(quorumHash); + READWRITE(signer); + READWRITE(sig); + } + + uint256 GetSignHash() const + { + CDummyContribution tmp(*this); + tmp.sig = CBLSSignature(); + return ::SerializeHash(tmp); + } +}; + // This message is only allowed on testnet/devnet/regtest // If any peer tries to send this message on mainnet, it is banned immediately // It is used to test commitments on testnet without actually running a full-blown DKG. @@ -82,8 +114,9 @@ public: class CDummyDKGSession { public: - std::set badMembers; + std::map dummyContributions; std::map dummyCommitments; + std::map dummyContributionsFromMembers; std::map> dummyCommitmentsFromMembers; }; @@ -94,17 +127,20 @@ class CDummyDKG { private: CCriticalSection sessionCs; - std::map prevSessions; std::map curSessions; public: void ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStream& vRecv, CConnman& connman); + void ProcessDummyContribution(NodeId from, const CDummyContribution& qc); void ProcessDummyCommitment(NodeId from, const CDummyCommitment& qc); void UpdatedBlockTip(const CBlockIndex* pindex, bool fInitialDownload); + void CreateDummyContribution(Consensus::LLMQType llmqType, const CBlockIndex* pindex); void CreateDummyCommitment(Consensus::LLMQType llmqType, const CBlockIndex* pindex); void CreateFinalCommitment(Consensus::LLMQType llmqType, const CBlockIndex* pindex); + bool HasDummyContribution(const uint256& hash); + bool GetDummyContribution(const uint256& hash, CDummyContribution& ret); bool HasDummyCommitment(const uint256& hash); bool GetDummyCommitment(const uint256& hash, CDummyCommitment& ret); diff --git a/src/net_processing.cpp b/src/net_processing.cpp index bc506e8da0..641efab9f6 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -970,6 +970,8 @@ bool static AlreadyHave(const CInv& inv) EXCLUSIVE_LOCKS_REQUIRED(cs_main) return llmq::quorumBlockProcessor->HasMinableCommitment(inv.hash); case MSG_QUORUM_DUMMY_COMMITMENT: return llmq::quorumDummyDKG->HasDummyCommitment(inv.hash); + case MSG_QUORUM_DUMMY_CONTRIBUTION: + return llmq::quorumDummyDKG->HasDummyContribution(inv.hash); } // Don't know what it is, just say we already got one @@ -1291,6 +1293,14 @@ void static ProcessGetData(CNode* pfrom, const Consensus::Params& consensusParam } } + if (!push && (inv.type == MSG_QUORUM_DUMMY_CONTRIBUTION)) { + llmq::CDummyContribution o; + if (llmq::quorumDummyDKG->GetDummyContribution(inv.hash, o)) { + connman.PushMessage(pfrom, msgMaker.Make(NetMsgType::QCONTRIB, o)); + push = true; + } + } + if (!push && (inv.type == MSG_QUORUM_DUMMY_COMMITMENT)) { if (!consensusParams.fLLMQAllowDummyCommitments) { Misbehaving(pfrom->id, 100); diff --git a/src/protocol.cpp b/src/protocol.cpp index 1cde07d829..fa1ccaf9ce 100644 --- a/src/protocol.cpp +++ b/src/protocol.cpp @@ -73,6 +73,7 @@ const char *GETMNLISTDIFF="getmnlistd"; const char *MNLISTDIFF="mnlistdiff"; const char *QFCOMMITMENT="qfcommit"; const char *QDCOMMITMENT="qdcommit"; +const char *QCONTRIB="qcontrib"; }; static const char* ppszTypeName[] = @@ -102,6 +103,7 @@ static const char* ppszTypeName[] = "compact block", // Should never occur NetMsgType::QFCOMMITMENT, NetMsgType::QDCOMMITMENT, + NetMsgType::QCONTRIB, }; /** All known message types. Keep this in the same order as the list of @@ -163,6 +165,7 @@ const static std::string allNetMessageTypes[] = { NetMsgType::MNLISTDIFF, NetMsgType::QFCOMMITMENT, NetMsgType::QDCOMMITMENT, + NetMsgType::QCONTRIB, }; const static std::vector allNetMessageTypesVec(allNetMessageTypes, allNetMessageTypes+ARRAYLEN(allNetMessageTypes)); diff --git a/src/protocol.h b/src/protocol.h index 4a5326f7b1..ba1b25c456 100644 --- a/src/protocol.h +++ b/src/protocol.h @@ -272,6 +272,7 @@ extern const char *GETMNLISTDIFF; extern const char *MNLISTDIFF; extern const char *QFCOMMITMENT; extern const char *QDCOMMITMENT; +extern const char *QCONTRIB; }; /* Get a vector of all valid message types (see above) */ @@ -375,6 +376,7 @@ enum GetDataMsg { MSG_CMPCT_BLOCK = 20, //!< Defined in BIP152 MSG_QUORUM_FINAL_COMMITMENT = 21, MSG_QUORUM_DUMMY_COMMITMENT = 22, // only valid on testnet/devnet/regtest + MSG_QUORUM_DUMMY_CONTRIBUTION = 23, // not a valid contribution and only allowed on testnet/devnet/regtest. Will later be replaced with the real contribution }; /** inv message data */