diff --git a/src/privatesend-client.cpp b/src/privatesend-client.cpp index 37841d67826f..0aa07f611c51 100644 --- a/src/privatesend-client.cpp +++ b/src/privatesend-client.cpp @@ -51,6 +51,7 @@ void CPrivateSendClient::ProcessMessage(CNode* pfrom, const std::string& strComm LogPrint("privatesend", "DSQUEUE -- %s new\n", dsq.ToString()); if(dsq.IsExpired()) return; + if(dsq.nInputCount < 0 || dsq.nInputCount > PRIVATESEND_ENTRY_MAX_SIZE) return; masternode_info_t infoMn; if(!mnodeman.GetMasternodeInfo(dsq.masternodeOutpoint, infoMn)) return; @@ -875,10 +876,18 @@ bool CPrivateSendClient::JoinExistingQueue(CAmount nBalanceNeedsAnonymized, CCon CAmount nValueInTmp = 0; std::vector vecTxDSInTmp; std::vector vCoinsTmp; - - // Try to match their denominations if possible, select at least 1 denominations - if(!pwalletMain->SelectCoinsByDenominations(dsq.nDenom, vecStandardDenoms[vecBits.front()], nBalanceNeedsAnonymized, vecTxDSInTmp, vCoinsTmp, nValueInTmp, 0, nPrivateSendRounds)) { - LogPrintf("CPrivateSendClient::JoinExistingQueue -- Couldn't match denominations %d %d (%s)\n", vecBits.front(), dsq.nDenom, CPrivateSend::GetDenominationsToString(dsq.nDenom)); + int nMinAmount = vecStandardDenoms[vecBits.front()]; + int nMaxAmount = nBalanceNeedsAnonymized; + // nInputCount is not covered by legacy signature, require SPORK_6_NEW_SIGS to activate to use new algo + // (to make sure nInputCount wasn't modified by some intermediary node) + bool fNewAlgo = infoMn.nProtocolVersion > 70208 && sporkManager.IsSporkActive(SPORK_6_NEW_SIGS); + + if (fNewAlgo && dsq.nInputCount != 0) { + nMinAmount = nMaxAmount = dsq.nInputCount * vecStandardDenoms[vecBits.front()]; + } + // Try to match their denominations if possible, select exact number of denominations + if(!pwalletMain->SelectCoinsByDenominations(dsq.nDenom, nMinAmount, nMaxAmount, vecTxDSInTmp, vCoinsTmp, nValueInTmp, 0, nPrivateSendRounds)) { + LogPrintf("CPrivateSendClient::JoinExistingQueue -- Couldn't match %d denominations %d %d (%s)\n", dsq.nInputCount, vecBits.front(), dsq.nDenom, CPrivateSend::GetDenominationsToString(dsq.nDenom)); continue; } @@ -890,14 +899,15 @@ bool CPrivateSendClient::JoinExistingQueue(CAmount nBalanceNeedsAnonymized, CCon } nSessionDenom = dsq.nDenom; + nSessionInputCount = fNewAlgo ? dsq.nInputCount : 0; infoMixingMasternode = infoMn; - pendingDsaRequest = CPendingDsaRequest(infoMn.addr, CDarksendAccept(nSessionDenom, txMyCollateral)); + pendingDsaRequest = CPendingDsaRequest(infoMn.addr, CDarksendAccept(nSessionDenom, nSessionInputCount, txMyCollateral)); connman.AddPendingMasternode(infoMn.addr); // TODO: add new state POOL_STATE_CONNECTING and bump MIN_PRIVATESEND_PEER_PROTO_VERSION SetState(POOL_STATE_QUEUE); nTimeLastSuccessfulStep = GetTime(); - LogPrintf("CPrivateSendClient::JoinExistingQueue -- pending connection (from queue): nSessionDenom: %d (%s), addr=%s\n", - nSessionDenom, CPrivateSend::GetDenominationsToString(nSessionDenom), infoMn.addr.ToString()); + LogPrintf("CPrivateSendClient::JoinExistingQueue -- pending connection (from queue): nSessionDenom: %d (%s), nSessionInputCount: %d, addr=%s\n", + nSessionDenom, CPrivateSend::GetDenominationsToString(nSessionDenom), nSessionInputCount, infoMn.addr.ToString()); strAutoDenomResult = _("Trying to connect..."); return true; } @@ -963,14 +973,37 @@ bool CPrivateSendClient::StartNewQueue(CAmount nValueMin, CAmount nBalanceNeedsA nSessionDenom = CPrivateSend::GetDenominationsByAmounts(vecAmounts); } + // Count available denominations. + // Should never really fail after this point, since we just selected compatible inputs ourselves. + std::vector vecBits; + if (!CPrivateSend::GetDenominationsBits(nSessionDenom, vecBits)) { + return false; + } + + CAmount nValueInTmp = 0; + std::vector vecTxDSInTmp; + std::vector vCoinsTmp; + std::vector vecStandardDenoms = CPrivateSend::GetStandardDenominations(); + + bool fSelected = pwalletMain->SelectCoinsByDenominations(nSessionDenom, vecStandardDenoms[vecBits.front()], vecStandardDenoms[vecBits.front()] * PRIVATESEND_ENTRY_MAX_SIZE, vecTxDSInTmp, vCoinsTmp, nValueInTmp, 0, nPrivateSendRounds); + if (!fSelected) { + return false; + } + + // nInputCount is not covered by legacy signature, require SPORK_6_NEW_SIGS to activate to use new algo + // (to make sure nInputCount wasn't modified by some intermediary node) + bool fNewAlgo = infoMn.nProtocolVersion > 70208 && sporkManager.IsSporkActive(SPORK_6_NEW_SIGS); + nSessionInputCount = fNewAlgo + ? std::min(vecTxDSInTmp.size(), size_t(5 + GetRand(PRIVATESEND_ENTRY_MAX_SIZE - 5 + 1))) + : 0; infoMixingMasternode = infoMn; connman.AddPendingMasternode(infoMn.addr); - pendingDsaRequest = CPendingDsaRequest(infoMn.addr, CDarksendAccept(nSessionDenom, txMyCollateral)); + pendingDsaRequest = CPendingDsaRequest(infoMn.addr, CDarksendAccept(nSessionDenom, nSessionInputCount, txMyCollateral)); // TODO: add new state POOL_STATE_CONNECTING and bump MIN_PRIVATESEND_PEER_PROTO_VERSION SetState(POOL_STATE_QUEUE); nTimeLastSuccessfulStep = GetTime(); - LogPrintf("CPrivateSendClient::StartNewQueue -- pending connection, nSessionDenom: %d (%s), addr=%s\n", - nSessionDenom, CPrivateSend::GetDenominationsToString(nSessionDenom), infoMn.addr.ToString()); + LogPrintf("CPrivateSendClient::StartNewQueue -- pending connection, nSessionDenom: %d (%s), nSessionInputCount: %d, addr=%s\n", + nSessionDenom, CPrivateSend::GetDenominationsToString(nSessionDenom), nSessionInputCount, infoMn.addr.ToString()); strAutoDenomResult = _("Trying to connect..."); return true; } @@ -1077,7 +1110,7 @@ bool CPrivateSendClient::PrepareDenominate(int nMinRounds, int nMaxRounds, std:: return false; } std::vector vecStandardDenoms = CPrivateSend::GetStandardDenominations(); - bool fSelected = pwalletMain->SelectCoinsByDenominations(nSessionDenom, vecStandardDenoms[vecBits.front()], CPrivateSend::GetMaxPoolAmount(), vecTxDSIn, vCoins, nValueIn, nMinRounds, nMaxRounds); + bool fSelected = pwalletMain->SelectCoinsByDenominations(nSessionDenom, vecStandardDenoms[vecBits.front()], vecStandardDenoms[vecBits.front()] * PRIVATESEND_ENTRY_MAX_SIZE, vecTxDSIn, vCoins, nValueIn, nMinRounds, nMaxRounds); if (nMinRounds >= 0 && !fSelected) { strErrorRet = "Can't select current denominated inputs"; return false; @@ -1098,7 +1131,7 @@ bool CPrivateSendClient::PrepareDenominate(int nMinRounds, int nMaxRounds, std:: // NOTE: No need to randomize order of inputs because they were // initially shuffled in CWallet::SelectCoinsByDenominations already. int nStep = 0; - int nStepsMax = 5 + GetRandInt(PRIVATESEND_ENTRY_MAX_SIZE-5+1); + int nStepsMax = nSessionInputCount != 0 ? nSessionInputCount : (5 + GetRandInt(PRIVATESEND_ENTRY_MAX_SIZE - 5 + 1)); while (nStep < nStepsMax) { for (const auto& nBit : vecBits) { @@ -1145,7 +1178,7 @@ bool CPrivateSendClient::PrepareDenominate(int nMinRounds, int nMaxRounds, std:: } } - if (CPrivateSend::GetDenominations(vecTxOutRet) != nSessionDenom) { + if (CPrivateSend::GetDenominations(vecTxOutRet) != nSessionDenom || (nSessionInputCount != 0 && nStep != nStepsMax)) { { // unlock used coins on failure LOCK(pwalletMain->cs_wallet); diff --git a/src/privatesend-server.cpp b/src/privatesend-server.cpp index 99b74bf66218..41794202ae7c 100644 --- a/src/privatesend-server.cpp +++ b/src/privatesend-server.cpp @@ -45,6 +45,8 @@ void CPrivateSendServer::ProcessMessage(CNode* pfrom, const std::string& strComm LogPrint("privatesend", "DSACCEPT -- nDenom %d (%s) txCollateral %s", dsa.nDenom, CPrivateSend::GetDenominationsToString(dsa.nDenom), dsa.txCollateral.ToString()); + if(dsa.nInputCount < 0 || dsa.nInputCount > PRIVATESEND_ENTRY_MAX_SIZE) return; + masternode_info_t mnInfo; if(!mnodeman.GetMasternodeInfo(activeMasternode.outpoint, mnInfo)) { PushStatus(pfrom, STATUS_REJECTED, ERR_MN_LIST, connman); @@ -98,6 +100,7 @@ void CPrivateSendServer::ProcessMessage(CNode* pfrom, const std::string& strComm LogPrint("privatesend", "DSQUEUE -- %s new\n", dsq.ToString()); if(dsq.IsExpired()) return; + if(dsq.nInputCount < 0 || dsq.nInputCount > PRIVATESEND_ENTRY_MAX_SIZE) return; masternode_info_t mnInfo; if(!mnodeman.GetMasternodeInfo(dsq.masternodeOutpoint, mnInfo)) return; @@ -165,6 +168,18 @@ void CPrivateSendServer::ProcessMessage(CNode* pfrom, const std::string& strComm return; } + if(nSessionInputCount != 0 && entry.vecTxDSIn.size() != nSessionInputCount) { + LogPrintf("DSVIN -- ERROR: incorrect number of inputs! %d/%d\n", entry.vecTxDSIn.size(), nSessionInputCount); + PushStatus(pfrom, STATUS_REJECTED, ERR_INVALID_INPUT_COUNT, connman); + return; + } + + if(nSessionInputCount != 0 && entry.vecTxOut.size() != nSessionInputCount) { + LogPrintf("DSVIN -- ERROR: incorrect number of outputs! %d/%d\n", entry.vecTxOut.size(), nSessionInputCount); + PushStatus(pfrom, STATUS_REJECTED, ERR_INVALID_INPUT_COUNT, connman); + return; + } + //do we have the same denominations as the current session? if(!IsOutputsCompatibleWithSessionDenom(entry.vecTxOut)) { LogPrintf("DSVIN -- not compatible with existing transactions!\n"); @@ -492,7 +507,7 @@ void CPrivateSendServer::CheckForCompleteQueue(CConnman& connman) if(nState == POOL_STATE_QUEUE && IsSessionReady()) { SetState(POOL_STATE_ACCEPTING_ENTRIES); - CDarksendQueue dsq(nSessionDenom, activeMasternode.outpoint, GetAdjustedTime(), true); + CDarksendQueue dsq(nSessionDenom, nSessionInputCount, activeMasternode.outpoint, GetAdjustedTime(), true); LogPrint("privatesend", "CPrivateSendServer::CheckForCompleteQueue -- queue is ready, signing and relaying (%s)\n", dsq.ToString()); dsq.Sign(); dsq.Relay(connman); @@ -670,6 +685,12 @@ bool CPrivateSendServer::IsAcceptableDSA(const CDarksendAccept& dsa, PoolMessage return false; } + if(dsa.nInputCount < 0 || dsa.nInputCount > PRIVATESEND_ENTRY_MAX_SIZE) { + LogPrint("privatesend", "CPrivateSendServer::%s -- requested count is not valid!\n", __func__); + nMessageIDRet = ERR_INVALID_INPUT_COUNT; + return false; + } + return true; } @@ -692,13 +713,16 @@ bool CPrivateSendServer::CreateNewSession(const CDarksendAccept& dsa, PoolMessag nMessageIDRet = MSG_NOERR; nSessionID = GetRandInt(999999)+1; nSessionDenom = dsa.nDenom; + // nInputCount is not covered by legacy signature, require SPORK_6_NEW_SIGS to activate to use new algo + // (to make sure nInputCount wasn't modified by some intermediary node) + nSessionInputCount = sporkManager.IsSporkActive(SPORK_6_NEW_SIGS) ? dsa.nInputCount : 0; SetState(POOL_STATE_QUEUE); nTimeLastSuccessfulStep = GetTime(); if(!fUnitTest) { //broadcast that I'm accepting entries, only if it's the first entry through - CDarksendQueue dsq(dsa.nDenom, activeMasternode.outpoint, GetAdjustedTime(), false); + CDarksendQueue dsq(dsa.nDenom, dsa.nInputCount, activeMasternode.outpoint, GetAdjustedTime(), false); LogPrint("privatesend", "CPrivateSendServer::CreateNewSession -- signing and relaying new queue: %s\n", dsq.ToString()); dsq.Sign(); dsq.Relay(connman); @@ -734,14 +758,21 @@ bool CPrivateSendServer::AddUserToExistingSession(const CDarksendAccept& dsa, Po return false; } + if(dsa.nInputCount != nSessionInputCount) { + LogPrintf("CPrivateSendServer::AddUserToExistingSession -- incompatible count %d != nSessionInputCount %d\n", + dsa.nInputCount, nSessionInputCount); + nMessageIDRet = ERR_INVALID_INPUT_COUNT; + return false; + } + // count new user as accepted to an existing session nMessageIDRet = MSG_NOERR; nTimeLastSuccessfulStep = GetTime(); vecSessionCollaterals.push_back(MakeTransactionRef(dsa.txCollateral)); - LogPrintf("CPrivateSendServer::AddUserToExistingSession -- new user accepted, nSessionID: %d nSessionDenom: %d (%s) vecSessionCollaterals.size(): %d\n", - nSessionID, nSessionDenom, CPrivateSend::GetDenominationsToString(nSessionDenom), vecSessionCollaterals.size()); + LogPrintf("CPrivateSendServer::AddUserToExistingSession -- new user accepted, nSessionID: %d nSessionDenom: %d (%s) nSessionInputCount: %d vecSessionCollaterals.size(): %d\n", + nSessionID, nSessionDenom, CPrivateSend::GetDenominationsToString(nSessionDenom), nSessionInputCount, vecSessionCollaterals.size()); return true; } diff --git a/src/privatesend.cpp b/src/privatesend.cpp index 8e5ab8c6234b..ec16dead5d98 100644 --- a/src/privatesend.cpp +++ b/src/privatesend.cpp @@ -193,6 +193,7 @@ void CPrivateSendBase::SetNull() nState = POOL_STATE_IDLE; nSessionID = 0; nSessionDenom = 0; + nSessionInputCount = 0; vecEntries.clear(); finalMutableTransaction.vin.clear(); finalMutableTransaction.vout.clear(); @@ -457,6 +458,7 @@ std::string CPrivateSend::GetMessageByID(PoolMessage nMessageID) case MSG_NOERR: return _("No errors detected."); case MSG_SUCCESS: return _("Transaction created successfully."); case MSG_ENTRIES_ADDED: return _("Your entries added successfully."); + case ERR_INVALID_INPUT_COUNT: return _("Invalid input count."); default: return _("Unknown response."); } } diff --git a/src/privatesend.h b/src/privatesend.h index 69e079e6f971..3530c0f70d06 100644 --- a/src/privatesend.h +++ b/src/privatesend.h @@ -25,7 +25,7 @@ static const int PRIVATESEND_SIGNING_TIMEOUT = 15; //! minimum peer version accepted by mixing pool static const int MIN_PRIVATESEND_PEER_PROTO_VERSION = 70208; -static const CAmount PRIVATESEND_ENTRY_MAX_SIZE = 9; +static const size_t PRIVATESEND_ENTRY_MAX_SIZE = 9; // pool responses enum PoolMessage { @@ -51,6 +51,7 @@ enum PoolMessage { MSG_NOERR, MSG_SUCCESS, MSG_ENTRIES_ADDED, + ERR_INVALID_INPUT_COUNT, MSG_POOL_MIN = ERR_ALREADY_HAVE, MSG_POOL_MAX = MSG_ENTRIES_ADDED }; @@ -99,15 +100,18 @@ class CDarksendAccept { public: int nDenom; + int nInputCount; CMutableTransaction txCollateral; CDarksendAccept() : nDenom(0), + nInputCount(0), txCollateral(CMutableTransaction()) {}; - CDarksendAccept(int nDenom, const CMutableTransaction& txCollateral) : + CDarksendAccept(int nDenom, int nInputCount, const CMutableTransaction& txCollateral) : nDenom(nDenom), + nInputCount(nInputCount), txCollateral(txCollateral) {}; @@ -116,6 +120,12 @@ class CDarksendAccept template inline void SerializationOp(Stream& s, Operation ser_action) { READWRITE(nDenom); + int nVersion = s.GetVersion(); + if (nVersion > 70208) { + READWRITE(nInputCount); + } else if (ser_action.ForRead()) { + nInputCount = 0; + } READWRITE(txCollateral); } @@ -169,6 +179,7 @@ class CDarksendQueue { public: int nDenom; + int nInputCount; COutPoint masternodeOutpoint; int64_t nTime; bool fReady; //ready for submit @@ -178,6 +189,7 @@ class CDarksendQueue CDarksendQueue() : nDenom(0), + nInputCount(0), masternodeOutpoint(COutPoint()), nTime(0), fReady(false), @@ -185,8 +197,9 @@ class CDarksendQueue fTried(false) {} - CDarksendQueue(int nDenom, COutPoint outpoint, int64_t nTime, bool fReady) : + CDarksendQueue(int nDenom, int nInputCount, COutPoint outpoint, int64_t nTime, bool fReady) : nDenom(nDenom), + nInputCount(nInputCount), masternodeOutpoint(outpoint), nTime(nTime), fReady(fReady), @@ -200,6 +213,11 @@ class CDarksendQueue inline void SerializationOp(Stream& s, Operation ser_action) { READWRITE(nDenom); int nVersion = s.GetVersion(); + if (nVersion > 70208) { + READWRITE(nInputCount); + } else if (ser_action.ForRead()) { + nInputCount = 0; + } if (nVersion == 70208 && (s.GetType() & SER_NETWORK)) { // converting from/to old format CTxIn txin{}; @@ -240,13 +258,13 @@ class CDarksendQueue std::string ToString() const { - return strprintf("nDenom=%d, nTime=%lld, fReady=%s, fTried=%s, masternode=%s", - nDenom, nTime, fReady ? "true" : "false", fTried ? "true" : "false", masternodeOutpoint.ToStringShort()); + return strprintf("nDenom=%d, nInputCount=%d, nTime=%lld, fReady=%s, fTried=%s, masternode=%s", + nDenom, nInputCount, nTime, fReady ? "true" : "false", fTried ? "true" : "false", masternodeOutpoint.ToStringShort()); } friend bool operator==(const CDarksendQueue& a, const CDarksendQueue& b) { - return a.nDenom == b.nDenom && a.masternodeOutpoint == b.masternodeOutpoint && a.nTime == b.nTime && a.fReady == b.fReady; + return a.nDenom == b.nDenom && a.nInputCount == b.nInputCount && a.masternodeOutpoint == b.masternodeOutpoint && a.nTime == b.nTime && a.fReady == b.fReady; } }; @@ -352,6 +370,7 @@ class CPrivateSendBase public: int nSessionDenom; //Users must submit an denom matching this + int nSessionInputCount; //Users must submit a count matching this CPrivateSendBase() { SetNull(); }