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.
This commit is contained in:
Alexander Block 2018-12-10 06:04:33 +01:00 committed by GitHub
parent df0d0cce77
commit d9b28fe1ad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 235 additions and 30 deletions

View File

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

View File

@ -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<bool> CDummyDKG::GetValidMembers(Consensus::LLMQType llmqType, const std::vector<CDeterministicMNCPtr>& members)
@ -368,20 +503,14 @@ std::vector<bool> 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);

View File

@ -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<typename Stream, typename Operation>
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<uint256> badMembers;
std::map<uint256, CDummyContribution> dummyContributions;
std::map<uint256, CDummyCommitment> dummyCommitments;
std::map<uint256, uint256> dummyContributionsFromMembers;
std::map<uint256, std::map<uint256, uint256>> dummyCommitmentsFromMembers;
};
@ -94,17 +127,20 @@ class CDummyDKG
{
private:
CCriticalSection sessionCs;
std::map<Consensus::LLMQType, CDummyDKGSession> prevSessions;
std::map<Consensus::LLMQType, CDummyDKGSession> 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);

View File

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

View File

@ -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<std::string> allNetMessageTypesVec(allNetMessageTypes, allNetMessageTypes+ARRAYLEN(allNetMessageTypes));

View File

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