Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement MNAUTH and allow unlimited inbound MN connections #2790

Merged
merged 7 commits into from
Mar 22, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions src/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -134,12 +134,13 @@ BITCOIN_CORE_H = \
cuckoocache.h \
ctpl.h \
cxxtimer.hpp \
evo/cbtx.h \
evo/deterministicmns.h \
evo/evodb.h \
evo/specialtx.h \
evo/mnauth.h \
evo/providertx.h \
evo/deterministicmns.h \
evo/cbtx.h \
evo/simplifiedmns.h \
evo/specialtx.h \
privatesend.h \
privatesend-client.h \
privatesend-server.h \
Expand Down Expand Up @@ -267,12 +268,13 @@ libdash_server_a_SOURCES = \
chain.cpp \
checkpoints.cpp \
dsnotificationinterface.cpp \
evo/cbtx.cpp \
evo/deterministicmns.cpp \
evo/evodb.cpp \
evo/specialtx.cpp \
evo/mnauth.cpp \
evo/providertx.cpp \
evo/deterministicmns.cpp \
evo/cbtx.cpp \
evo/simplifiedmns.cpp \
evo/specialtx.cpp \
httprpc.cpp \
httpserver.cpp \
init.cpp \
Expand Down
2 changes: 2 additions & 0 deletions src/dsnotificationinterface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include "validation.h"

#include "evo/deterministicmns.h"
#include "evo/mnauth.h"

#include "llmq/quorums.h"
#include "llmq/quorums_chainlocks.h"
Expand Down Expand Up @@ -81,6 +82,7 @@ void CDSNotificationInterface::SyncTransaction(const CTransaction &tx, const CBl

void CDSNotificationInterface::NotifyMasternodeListChanged(const CDeterministicMNList& newList)
{
CMNAuth::NotifyMasternodeListChanged(newList);
governance.CheckMasternodeOrphanObjects(connman);
governance.CheckMasternodeOrphanVotes(connman);
governance.UpdateCachesAndClean();
Expand Down
141 changes: 141 additions & 0 deletions src/evo/mnauth.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
// Copyright (c) 2019 The Dash Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include "mnauth.h"

#include "activemasternode.h"
#include "evo/deterministicmns.h"
#include "masternode-sync.h"
#include "net.h"
#include "net_processing.h"
#include "netmessagemaker.h"
#include "validation.h"

#include <unordered_set>

void CMNAuth::PushMNAUTH(CNode* pnode, CConnman& connman)
{
if (!fMasternodeMode || activeMasternodeInfo.proTxHash.IsNull()) {
return;
}

uint256 signHash;
{
LOCK(pnode->cs_mnauth);
if (pnode->receivedMNAuthChallenge.IsNull()) {
return;
}
// We include fInbound in signHash to forbid interchanging of challenges by a man in the middle. This way
// we protect ourself against MITM in this form:
// node1 <- Eve -> node2
// It does not protect against:
// node1 -> Eve -> node2
// This is ok as we only use MNAUTH as a DoS protection and not for sensitive stuff
signHash = ::SerializeHash(std::make_tuple(*activeMasternodeInfo.blsPubKeyOperator, pnode->receivedMNAuthChallenge, pnode->fInbound));
}

CMNAuth mnauth;
mnauth.proRegTxHash = activeMasternodeInfo.proTxHash;
mnauth.sig = activeMasternodeInfo.blsKeyOperator->Sign(signHash);

LogPrint("net", "CMNAuth::%s -- Sending MNAUTH, peer=%d\n", __func__, pnode->id);

connman.PushMessage(pnode, CNetMsgMaker(pnode->GetSendVersion()).Make(NetMsgType::MNAUTH, mnauth));
}

void CMNAuth::ProcessMessage(CNode* pnode, const std::string& strCommand, CDataStream& vRecv, CConnman& connman)
{
if (!fMasternodeMode) {
return;
}

if (!masternodeSync.IsBlockchainSynced()) {
// we can't really verify MNAUTH messages when we don't have the latest MN list
return;
}

if (strCommand == NetMsgType::MNAUTH) {
Copy link
Member

@PastaPastaPasta PastaPastaPasta Jun 20, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why isn't it assumed this is the case? I would think this method is only called when that check is true

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, it's called for every incoming message. This follows the convention that we use for all Dash specific message handling. For example, everything related to DKGs is handled in CDKGSessionManager::ProcessMessage and it by itself checks which message it actually is. This might look a bit strange in the cases where it's only one message that we're interested in, but I prefer to use the same style/convention for all cases.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

CMNAuth mnauth;
vRecv >> mnauth;

{
LOCK(pnode->cs_mnauth);
// only one MNAUTH allowed
if (!pnode->verifiedProRegTxHash.IsNull()) {
LOCK(cs_main);
Misbehaving(pnode->id, 100);
return;
}
}

if (mnauth.proRegTxHash.IsNull() || !mnauth.sig.IsValid()) {
LOCK(cs_main);
Misbehaving(pnode->id, 100);
return;
}

auto mnList = deterministicMNManager->GetListAtChainTip();
auto dmn = mnList.GetValidMN(mnauth.proRegTxHash);
if (!dmn) {
LOCK(cs_main);
Misbehaving(pnode->id, 10);
// in case he was unlucky and not up to date, let him retry the whole verification process
pnode->fDisconnect = true;
return;
}

uint256 signHash;
{
LOCK(pnode->cs_mnauth);
// See comment in PushMNAUTH (fInbound is negated here as we're on the other side of the connection)
signHash = ::SerializeHash(std::make_tuple(dmn->pdmnState->pubKeyOperator, pnode->sentMNAuthChallenge, !pnode->fInbound));
}

if (!mnauth.sig.VerifyInsecure(dmn->pdmnState->pubKeyOperator, signHash)) {
LOCK(cs_main);
Misbehaving(pnode->id, 10);
// in case he was unlucky and not up to date, let him retry the whole verification process
pnode->fDisconnect = true;
return;
}

connman.ForEachNode([&](CNode* pnode2) {
if (pnode2->verifiedProRegTxHash == mnauth.proRegTxHash) {
LogPrint("net", "CMNAuth::ProcessMessage -- Masternode %s has already verified as peer %d, dropping old connection. peer=%d\n",
mnauth.proRegTxHash.ToString(), pnode2->id, pnode->id);
pnode2->fDisconnect = true;
}
});

{
LOCK(pnode->cs_mnauth);
pnode->verifiedProRegTxHash = mnauth.proRegTxHash;
pnode->verifiedPubKeyHash = dmn->pdmnState->pubKeyOperator.GetHash();
}

LogPrint("net", "CMNAuth::%s -- Valid MNAUTH for %s, peer=%d\n", __func__, mnauth.proRegTxHash.ToString(), pnode->id);
}
}

void CMNAuth::NotifyMasternodeListChanged(const CDeterministicMNList& newList)
{
std::unordered_set<uint256> pubKeys;
g_connman->ForEachNode([&](const CNode* pnode) {
LOCK(pnode->cs_mnauth);
if (!pnode->verifiedProRegTxHash.IsNull()) {
pubKeys.emplace(pnode->verifiedPubKeyHash);
}
});
newList.ForEachMN(true, [&](const CDeterministicMNCPtr& dmn) {
pubKeys.erase(dmn->pdmnState->pubKeyOperator.GetHash());
});
g_connman->ForEachNode([&](CNode* pnode) {
LOCK(pnode->cs_mnauth);
if (!pnode->verifiedProRegTxHash.IsNull() && pubKeys.count(pnode->verifiedPubKeyHash)) {
LogPrint("net", "CMNAuth::NotifyMasternodeListChanged -- Disconnecting MN %s due to key changed/removed, peer=%d\n",
pnode->verifiedProRegTxHash.ToString(), pnode->id);
pnode->fDisconnect = true;
}
});
}
57 changes: 57 additions & 0 deletions src/evo/mnauth.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Copyright (c) 2019 The Dash Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#ifndef DASH_MNAUTH_H
#define DASH_MNAUTH_H

#include "bls/bls.h"
#include "serialize.h"

class CConnman;
class CDataStream;
class CDeterministicMN;
class CDeterministicMNList;
class CNode;
class UniValue;

/**
* This class handles the p2p message MNAUTH. MNAUTH is sent directly after VERACK and authenticates the sender as a
* masternode. It is only sent when the sender is actually a masternode.
*
* MNAUTH signs a challenge that was previously sent via VERSION. The challenge is signed differently depending on
* the connection being an inbound or outbound connection, which avoids MITM of this form:
* node1 <- Eve -> node2
* while still allowing:
* node1 -> Eve -> node2
*
* This is fine as we only use this mechanism for DoS protection. It allows us to keep masternode connections open for
* a very long time without evicting the connections when inbound connection limits are hit (non-MNs will then be evicted).
*
* If we ever want to add transfer of sensible data, THIS AUTHENTICATION MECHANISM IS NOT ENOUGH!! We'd need to implement
* proper encryption for these connections first.
*/

class CMNAuth
{
public:
uint256 proRegTxHash;
CBLSSignature sig;

public:
ADD_SERIALIZE_METHODS;

template <typename Stream, typename Operation>
inline void SerializationOp(Stream& s, Operation ser_action)
{
READWRITE(proRegTxHash);
READWRITE(sig);
}

static void PushMNAUTH(CNode* pnode, CConnman& connman);
static void ProcessMessage(CNode* pnode, const std::string& strCommand, CDataStream& vRecv, CConnman& connman);
static void NotifyMasternodeListChanged(const CDeterministicMNList& newList);
};


#endif //DASH_MNAUTH_H
6 changes: 3 additions & 3 deletions src/llmq/quorums.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -191,19 +191,19 @@ void CQuorumManager::EnsureQuorumConnections(Consensus::LLMQType llmqType, const
}

if (!g_connman->HasMasternodeQuorumNodes(llmqType, quorum->quorumHash)) {
std::set<CService> connections;
std::map<CService, uint256> connections;
if (quorum->IsMember(myProTxHash)) {
connections = CLLMQUtils::GetQuorumConnections(llmqType, quorum->quorumHash, myProTxHash);
} else {
auto cindexes = CLLMQUtils::CalcDeterministicWatchConnections(llmqType, quorum->quorumHash, quorum->members.size(), 1);
for (auto idx : cindexes) {
connections.emplace(quorum->members[idx]->pdmnState->addr);
connections.emplace(quorum->members[idx]->pdmnState->addr, quorum->members[idx]->proTxHash);
}
}
if (!connections.empty()) {
std::string debugMsg = strprintf("CQuorumManager::%s -- adding masternodes quorum connections for quorum %s:\n", __func__, quorum->quorumHash.ToString());
for (auto& c : connections) {
debugMsg += strprintf(" %s\n", c.ToString(false));
debugMsg += strprintf(" %s\n", c.first.ToString(false));
}
LogPrint("llmq", debugMsg);
g_connman->AddMasternodeQuorumNodes(llmqType, quorum->quorumHash, connections);
Expand Down
10 changes: 6 additions & 4 deletions src/llmq/quorums_dkgsessionhandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -476,26 +476,28 @@ void CDKGSessionHandler::HandleDKGRound()
});

if (curSession->AreWeMember() || GetBoolArg("-watchquorums", DEFAULT_WATCH_QUORUMS)) {
std::set<CService> connections;
std::map<CService, uint256> connections;
if (curSession->AreWeMember()) {
connections = CLLMQUtils::GetQuorumConnections(params.type, curQuorumHash, curSession->myProTxHash);
} else {
auto cindexes = CLLMQUtils::CalcDeterministicWatchConnections(params.type, curQuorumHash, curSession->members.size(), 1);
for (auto idx : cindexes) {
connections.emplace(curSession->members[idx]->dmn->pdmnState->addr);
connections.emplace(curSession->members[idx]->dmn->pdmnState->addr, curSession->members[idx]->dmn->proTxHash);
}
}
if (!connections.empty()) {
std::string debugMsg = strprintf("CDKGSessionManager::%s -- adding masternodes quorum connections for quorum %s:\n", __func__, curSession->quorumHash.ToString());
for (const auto& c : connections) {
debugMsg += strprintf(" %s\n", c.ToString(false));
debugMsg += strprintf(" %s\n", c.first.ToString(false));
}
LogPrint("llmq", debugMsg);
g_connman->AddMasternodeQuorumNodes(params.type, curQuorumHash, connections);

auto participatingNodesTmp = g_connman->GetMasternodeQuorumAddresses(params.type, curQuorumHash);
LOCK(curSession->invCs);
curSession->participatingNodes = std::move(participatingNodesTmp);
for (auto& p : participatingNodesTmp) {
curSession->participatingNodes.emplace(p.first);
}
}
}

Expand Down
6 changes: 3 additions & 3 deletions src/llmq/quorums_utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,12 @@ uint256 CLLMQUtils::BuildSignHash(Consensus::LLMQType llmqType, const uint256& q
return h.GetHash();
}

std::set<CService> CLLMQUtils::GetQuorumConnections(Consensus::LLMQType llmqType, const uint256& blockHash, const uint256& forMember)
std::map<CService, uint256> CLLMQUtils::GetQuorumConnections(Consensus::LLMQType llmqType, const uint256& blockHash, const uint256& forMember)
{
auto& params = Params().GetConsensus().llmqs.at(llmqType);

auto mns = GetAllQuorumMembers(llmqType, blockHash);
std::set<CService> result;
std::map<CService, uint256> result;
for (size_t i = 0; i < mns.size(); i++) {
auto& dmn = mns[i];
if (dmn->proTxHash == forMember) {
Expand All @@ -59,7 +59,7 @@ std::set<CService> CLLMQUtils::GetQuorumConnections(Consensus::LLMQType llmqType
if (otherDmn == dmn) {
continue;
}
result.emplace(otherDmn->pdmnState->addr);
result.emplace(otherDmn->pdmnState->addr, otherDmn->proTxHash);
gap <<= 1;
}
// there can be no two members with the same proTxHash, so return early
Expand Down
2 changes: 1 addition & 1 deletion src/llmq/quorums_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class CLLMQUtils
return BuildSignHash((Consensus::LLMQType)s.llmqType, s.quorumHash, s.id, s.msgHash);
}

static std::set<CService> GetQuorumConnections(Consensus::LLMQType llmqType, const uint256& blockHash, const uint256& forMember);
static std::map<CService, uint256> GetQuorumConnections(Consensus::LLMQType llmqType, const uint256& blockHash, const uint256& forMember);
static std::set<size_t> CalcDeterministicWatchConnections(Consensus::LLMQType llmqType, const uint256& blockHash, size_t memberCount, size_t connectionCount);

static bool IsQuorumActive(Consensus::LLMQType llmqType, const uint256& quorumHash);
Expand Down
Loading