Skip to content

Commit fd94e9c

Browse files
UdjinM6PastaPastaPastacodablock
committed
Streamline, refactor and unify PS checks for mixing entries and final txes (#3246)
* Move PS mixing entry verification on masternodes into `AddEntry()` Also streamline logic a bit and drop unused/excessive parts. * Unify PS checks among masternodes and clients * No need to re-check outputs over and over again * No need to count, fail early if any output is missing * No need to look any further once we found the input we expected A tx with duplicate inputs would be considered invalid anyway and we also know there are no duplicates because we just verified the final tx above. Also drop an unused variable. * Unify LogPrint-s * Drop human-readable strings for unused PoolMessage-s * Apply suggestions from code review Co-Authored-By: PastaPastaPasta <6443210+PastaPastaPasta@users.noreply.github.com> * Re-introduce zero-fee checks * fix log * Move all txin/txout verification logic shared by CPrivateSendClientSession::SignFinalTransaction() and CPrivateSendServer::AddEntry() into CPrivateSendBaseSession::IsValidInOuts() * fix nit * Add missing return * Use CCoinsViewMemPool instead of doing it manually Co-authored-by: PastaPastaPasta <6443210+PastaPastaPasta@users.noreply.github.com> Co-authored-by: Alexander Block <ablock84@gmail.com>
1 parent c42b200 commit fd94e9c

File tree

5 files changed

+184
-174
lines changed

5 files changed

+184
-174
lines changed

src/privatesend/privatesend-client.cpp

Lines changed: 58 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -541,85 +541,97 @@ bool CPrivateSendClientSession::SignFinalTransaction(const CTransaction& finalTr
541541
if (!mixingMasternode) return false;
542542

543543
finalMutableTransaction = finalTransactionNew;
544-
LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::SignFinalTransaction -- finalMutableTransaction=%s", finalMutableTransaction.ToString());
544+
LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::%s -- finalMutableTransaction=%s", __func__, finalMutableTransaction.ToString());
545+
546+
// STEP 1: check final transaction general rules
545547

546548
// Make sure it's BIP69 compliant
547549
sort(finalMutableTransaction.vin.begin(), finalMutableTransaction.vin.end(), CompareInputBIP69());
548550
sort(finalMutableTransaction.vout.begin(), finalMutableTransaction.vout.end(), CompareOutputBIP69());
549551

550552
if (finalMutableTransaction.GetHash() != finalTransactionNew.GetHash()) {
551-
LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::SignFinalTransaction -- WARNING! Masternode %s is not BIP69 compliant!\n", mixingMasternode->proTxHash.ToString());
553+
LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::%s -- ERROR! Masternode %s is not BIP69 compliant!\n", __func__, mixingMasternode->proTxHash.ToString());
554+
UnlockCoins();
555+
keyHolderStorage.ReturnAll();
556+
SetNull();
557+
return false;
558+
}
559+
560+
// Make sure all inputs/outputs are valid
561+
PoolMessage nMessageID{MSG_NOERR};
562+
if (!IsValidInOuts(finalMutableTransaction.vin, finalMutableTransaction.vout, nMessageID, nullptr)) {
563+
LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::%s -- ERROR! IsValidInOuts() failed: %s\n", __func__, CPrivateSend::GetMessageByID(nMessageID));
552564
UnlockCoins();
553565
keyHolderStorage.ReturnAll();
554566
SetNull();
555567
return false;
556568
}
557569

570+
// STEP 2: make sure our own inputs/outputs are present, otherwise refuse to sign
571+
558572
std::vector<CTxIn> sigs;
559573

560-
//make sure my inputs/outputs are present, otherwise refuse to sign
561574
for (const auto& entry : vecEntries) {
575+
// Check that the final transaction has all our outputs
576+
for (const auto& txout : entry.vecTxOut) {
577+
bool fFound = false;
578+
for (const auto& txoutFinal : finalMutableTransaction.vout) {
579+
if (txoutFinal == txout) {
580+
fFound = true;
581+
break;
582+
}
583+
}
584+
if (!fFound) {
585+
// Something went wrong and we'll refuse to sign. It's possible we'll be charged collateral. But that's
586+
// better than signing if the transaction doesn't look like what we wanted.
587+
LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::%s -- an output is missing, refusing to sign! txout=%s\n", txout.ToString());
588+
UnlockCoins();
589+
keyHolderStorage.ReturnAll();
590+
SetNull();
591+
return false;
592+
}
593+
}
594+
562595
for (const auto& txdsin : entry.vecTxDSIn) {
563596
/* Sign my transaction and all outputs */
564597
int nMyInputIndex = -1;
565598
CScript prevPubKey = CScript();
566-
CTxIn txin = CTxIn();
567599

568600
for (unsigned int i = 0; i < finalMutableTransaction.vin.size(); i++) {
569601
if (finalMutableTransaction.vin[i] == txdsin) {
570602
nMyInputIndex = i;
571603
prevPubKey = txdsin.prevPubKey;
572-
txin = txdsin;
604+
break;
573605
}
574606
}
575607

576-
if (nMyInputIndex >= 0) { //might have to do this one input at a time?
577-
int nFoundOutputsCount = 0;
578-
CAmount nValue1 = 0;
579-
CAmount nValue2 = 0;
580-
581-
for (const auto& txoutFinal : finalMutableTransaction.vout) {
582-
for (const auto& txout : entry.vecTxOut) {
583-
if (txoutFinal == txout) {
584-
nFoundOutputsCount++;
585-
nValue1 += txoutFinal.nValue;
586-
}
587-
}
588-
}
589-
590-
for (const auto& txout : entry.vecTxOut) {
591-
nValue2 += txout.nValue;
592-
}
593-
594-
int nTargetOuputsCount = entry.vecTxOut.size();
595-
if (nFoundOutputsCount < nTargetOuputsCount || nValue1 != nValue2) {
596-
// in this case, something went wrong and we'll refuse to sign. It's possible we'll be charged collateral. But that's
597-
// better then signing if the transaction doesn't look like what we wanted.
598-
LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::SignFinalTransaction -- My entries are not correct! Refusing to sign: nFoundOutputsCount: %d, nTargetOuputsCount: %d\n", nFoundOutputsCount, nTargetOuputsCount);
599-
UnlockCoins();
600-
keyHolderStorage.ReturnAll();
601-
SetNull();
602-
603-
return false;
604-
}
605-
606-
const CKeyStore& keystore = *vpwallets[0];
608+
if (nMyInputIndex == -1) {
609+
// Can't find one of my own inputs, refuse to sign. It's possible we'll be charged collateral. But that's
610+
// better than signing if the transaction doesn't look like what we wanted.
611+
LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::%s -- missing input! txdsin=%s\n", __func__, txdsin.ToString());
612+
UnlockCoins();
613+
keyHolderStorage.ReturnAll();
614+
SetNull();
615+
return false;
616+
}
607617

608-
LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::SignFinalTransaction -- Signing my input %i\n", nMyInputIndex);
609-
// TODO we're using amount=0 here but we should use the correct amount. This works because Dash ignores the amount while signing/verifying (only used in Bitcoin/Segwit)
610-
if (!SignSignature(keystore, prevPubKey, finalMutableTransaction, nMyInputIndex, 0, int(SIGHASH_ALL | SIGHASH_ANYONECANPAY))) { // changes scriptSig
611-
LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::SignFinalTransaction -- Unable to sign my own transaction!\n");
612-
// not sure what to do here, it will timeout...?
613-
}
618+
const CKeyStore& keystore = *vpwallets[0];
614619

615-
sigs.push_back(finalMutableTransaction.vin[nMyInputIndex]);
616-
LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::SignFinalTransaction -- nMyInputIndex: %d, sigs.size(): %d, scriptSig=%s\n", nMyInputIndex, (int)sigs.size(), ScriptToAsmStr(finalMutableTransaction.vin[nMyInputIndex].scriptSig));
620+
LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::%s -- Signing my input %i\n", __func__, nMyInputIndex);
621+
// TODO we're using amount=0 here but we should use the correct amount. This works because Dash ignores the amount while signing/verifying (only used in Bitcoin/Segwit)
622+
if (!SignSignature(keystore, prevPubKey, finalMutableTransaction, nMyInputIndex, 0, int(SIGHASH_ALL | SIGHASH_ANYONECANPAY))) { // changes scriptSig
623+
LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::%s -- Unable to sign my own transaction!\n", __func__);
624+
// not sure what to do here, it will timeout...?
617625
}
626+
627+
sigs.push_back(finalMutableTransaction.vin[nMyInputIndex]);
628+
LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::%s -- nMyInputIndex: %d, sigs.size(): %d, scriptSig=%s\n",
629+
__func__, nMyInputIndex, (int)sigs.size(), ScriptToAsmStr(finalMutableTransaction.vin[nMyInputIndex].scriptSig));
618630
}
619631
}
620632

621633
if (sigs.empty()) {
622-
LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::SignFinalTransaction -- can't sign anything!\n");
634+
LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::%s -- can't sign anything!\n", __func__);
623635
UnlockCoins();
624636
keyHolderStorage.ReturnAll();
625637
SetNull();
@@ -628,7 +640,7 @@ bool CPrivateSendClientSession::SignFinalTransaction(const CTransaction& finalTr
628640
}
629641

630642
// push all of our signatures to the Masternode
631-
LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::SignFinalTransaction -- pushing sigs to the masternode, finalMutableTransaction=%s", finalMutableTransaction.ToString());
643+
LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::%s -- pushing sigs to the masternode, finalMutableTransaction=%s", __func__, finalMutableTransaction.ToString());
632644
CNetMsgMaker msgMaker(pnode->GetSendVersion());
633645
connman.PushMessage(pnode, msgMaker.Make(NetMsgType::DSSIGNFINALTX, sigs));
634646
SetState(POOL_STATE_SIGNING);

src/privatesend/privatesend-server.cpp

Lines changed: 33 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -178,100 +178,10 @@ void CPrivateSendServer::ProcessMessage(CNode* pfrom, const std::string& strComm
178178

179179
LogPrint(BCLog::PRIVATESEND, "DSVIN -- txCollateral %s", entry.txCollateral->ToString());
180180

181-
if (entry.vecTxDSIn.size() != entry.vecTxOut.size()) {
182-
LogPrint(BCLog::PRIVATESEND, "DSVIN -- ERROR: inputs vs outputs size mismatch! %d vs %d\n", entry.vecTxDSIn.size(), entry.vecTxOut.size());
183-
PushStatus(pfrom, STATUS_REJECTED, ERR_SIZE_MISMATCH, connman);
184-
ConsumeCollateral(connman, entry.txCollateral);
185-
return;
186-
}
187-
188-
if (entry.vecTxDSIn.size() > PRIVATESEND_ENTRY_MAX_SIZE) {
189-
LogPrint(BCLog::PRIVATESEND, "DSVIN -- ERROR: too many inputs! %d/%d\n", entry.vecTxDSIn.size(), PRIVATESEND_ENTRY_MAX_SIZE);
190-
PushStatus(pfrom, STATUS_REJECTED, ERR_MAXIMUM, connman);
191-
ConsumeCollateral(connman, entry.txCollateral);
192-
return;
193-
}
194-
195-
// do we have the same denominations as the current session?
196-
if (!IsOutputsCompatibleWithSessionDenom(entry.vecTxOut)) {
197-
LogPrint(BCLog::PRIVATESEND, "DSVIN -- not compatible with existing transactions!\n");
198-
PushStatus(pfrom, STATUS_REJECTED, ERR_EXISTING_TX, connman);
199-
return;
200-
}
201-
202-
// check it like a transaction
203-
{
204-
CAmount nValueIn = 0;
205-
CAmount nValueOut = 0;
206-
207-
CMutableTransaction tx;
208-
209-
for (const auto& txout : entry.vecTxOut) {
210-
nValueOut += txout.nValue;
211-
tx.vout.push_back(txout);
212-
213-
if (txout.scriptPubKey.size() != 25) {
214-
LogPrint(BCLog::PRIVATESEND, "DSVIN -- non-standard pubkey detected! scriptPubKey=%s\n", ScriptToAsmStr(txout.scriptPubKey));
215-
PushStatus(pfrom, STATUS_REJECTED, ERR_NON_STANDARD_PUBKEY, connman);
216-
ConsumeCollateral(connman, entry.txCollateral);
217-
return;
218-
}
219-
if (!txout.scriptPubKey.IsPayToPublicKeyHash()) {
220-
LogPrint(BCLog::PRIVATESEND, "DSVIN -- invalid script! scriptPubKey=%s\n", ScriptToAsmStr(txout.scriptPubKey));
221-
PushStatus(pfrom, STATUS_REJECTED, ERR_INVALID_SCRIPT, connman);
222-
ConsumeCollateral(connman, entry.txCollateral);
223-
return;
224-
}
225-
}
226-
227-
for (const auto& txin : entry.vecTxDSIn) {
228-
tx.vin.push_back(txin);
229-
230-
LogPrint(BCLog::PRIVATESEND, "DSVIN -- txin=%s\n", txin.ToString());
231-
232-
Coin coin;
233-
auto mempoolTx = mempool.get(txin.prevout.hash);
234-
if (mempoolTx != nullptr) {
235-
if (mempool.isSpent(txin.prevout) || !llmq::quorumInstantSendManager->IsLocked(txin.prevout.hash)) {
236-
LogPrint(BCLog::PRIVATESEND, "DSVIN -- spent or non-locked mempool input! txin=%s\n", txin.ToString());
237-
PushStatus(pfrom, STATUS_REJECTED, ERR_MISSING_TX, connman);
238-
return;
239-
}
240-
if (!CPrivateSend::IsDenominatedAmount(mempoolTx->vout[txin.prevout.n].nValue)) {
241-
LogPrintf("DSVIN -- non-denominated mempool input! txin=%s\n", txin.ToString());
242-
PushStatus(pfrom, STATUS_REJECTED, ERR_DENOM, connman);
243-
ConsumeCollateral(connman, entry.txCollateral);
244-
return;
245-
}
246-
nValueIn += mempoolTx->vout[txin.prevout.n].nValue;
247-
} else if (GetUTXOCoin(txin.prevout, coin)) {
248-
if (!CPrivateSend::IsDenominatedAmount(coin.out.nValue)) {
249-
LogPrintf("DSVIN -- non-denominated input! txin=%s\n", txin.ToString());
250-
PushStatus(pfrom, STATUS_REJECTED, ERR_DENOM, connman);
251-
ConsumeCollateral(connman, entry.txCollateral);
252-
return;
253-
}
254-
nValueIn += coin.out.nValue;
255-
} else {
256-
LogPrint(BCLog::PRIVATESEND, "DSVIN -- missing input! txin=%s\n", txin.ToString());
257-
PushStatus(pfrom, STATUS_REJECTED, ERR_MISSING_TX, connman);
258-
return;
259-
}
260-
}
261-
262-
// There should be no fee in mixing tx
263-
CAmount nFee = nValueIn - nValueOut;
264-
if (nFee != 0) {
265-
LogPrint(BCLog::PRIVATESEND, "DSVIN -- there should be no fee in mixing tx! fees: %lld, tx=%s", nFee, tx.ToString());
266-
PushStatus(pfrom, STATUS_REJECTED, ERR_FEES, connman);
267-
return;
268-
}
269-
}
270-
271181
PoolMessage nMessageID = MSG_NOERR;
272182

273183
entry.addr = pfrom->addr;
274-
if (AddEntry(entry, nMessageID)) {
184+
if (AddEntry(connman, entry, nMessageID)) {
275185
PushStatus(pfrom, STATUS_ACCEPTED, nMessageID, connman);
276186
CheckPool(connman);
277187
RelayStatus(STATUS_ACCEPTED, connman);
@@ -628,48 +538,62 @@ bool CPrivateSendServer::IsInputScriptSigValid(const CTxIn& txin)
628538
}
629539

630540
//
631-
// Add a clients transaction to the pool
541+
// Add a client's transaction inputs/outputs to the pool
632542
//
633-
bool CPrivateSendServer::AddEntry(const CPrivateSendEntry& entryNew, PoolMessage& nMessageIDRet)
543+
bool CPrivateSendServer::AddEntry(CConnman& connman, const CPrivateSendEntry& entry, PoolMessage& nMessageIDRet)
634544
{
635545
if (!fMasternodeMode) return false;
636546

637-
for (const auto& txin : entryNew.vecTxDSIn) {
638-
if (txin.prevout.IsNull()) {
639-
LogPrint(BCLog::PRIVATESEND, "CPrivateSendServer::AddEntry -- input not valid!\n");
640-
nMessageIDRet = ERR_INVALID_INPUT;
641-
return false;
642-
}
547+
if (GetEntriesCount() >= nSessionMaxParticipants) {
548+
LogPrint(BCLog::PRIVATESEND, "CPrivateSendServer::%s -- ERROR: entries is full!\n", __func__);
549+
nMessageIDRet = ERR_ENTRIES_FULL;
550+
return false;
643551
}
644552

645-
if (!CPrivateSend::IsCollateralValid(*entryNew.txCollateral)) {
646-
LogPrint(BCLog::PRIVATESEND, "CPrivateSendServer::AddEntry -- collateral not valid!\n");
553+
if (!CPrivateSend::IsCollateralValid(*entry.txCollateral)) {
554+
LogPrint(BCLog::PRIVATESEND, "CPrivateSendServer::%s -- ERROR: collateral not valid!\n", __func__);
647555
nMessageIDRet = ERR_INVALID_COLLATERAL;
648556
return false;
649557
}
650558

651-
if (GetEntriesCount() >= nSessionMaxParticipants) {
652-
LogPrint(BCLog::PRIVATESEND, "CPrivateSendServer::AddEntry -- entries is full!\n");
653-
nMessageIDRet = ERR_ENTRIES_FULL;
559+
if (entry.vecTxDSIn.size() > PRIVATESEND_ENTRY_MAX_SIZE) {
560+
LogPrint(BCLog::PRIVATESEND, "CPrivateSendServer::%s -- ERROR: too many inputs! %d/%d\n", __func__, entry.vecTxDSIn.size(), PRIVATESEND_ENTRY_MAX_SIZE);
561+
nMessageIDRet = ERR_MAXIMUM;
562+
ConsumeCollateral(connman, entry.txCollateral);
654563
return false;
655564
}
656565

657-
for (const auto& txin : entryNew.vecTxDSIn) {
658-
LogPrint(BCLog::PRIVATESEND, "looking for txin -- %s\n", txin.ToString());
566+
std::vector<CTxIn> vin;
567+
for (const auto& txin : entry.vecTxDSIn) {
568+
LogPrint(BCLog::PRIVATESEND, "CPrivateSendServer::%s -- txin=%s\n", __func__, txin.ToString());
569+
659570
for (const auto& entry : vecEntries) {
660571
for (const auto& txdsin : entry.vecTxDSIn) {
661572
if (txdsin.prevout == txin.prevout) {
662-
LogPrint(BCLog::PRIVATESEND, "CPrivateSendServer::AddEntry -- found in txin\n");
573+
LogPrint(BCLog::PRIVATESEND, "CPrivateSendServer::%s -- ERROR: already have this txin in entries\n", __func__);
663574
nMessageIDRet = ERR_ALREADY_HAVE;
575+
// Two peers sent the same input? Can't really say who is the malicious one here,
576+
// could be that someone is picking someone else's inputs randomly trying to force
577+
// collateral consumption. Do not punish.
664578
return false;
665579
}
666580
}
667581
}
582+
vin.emplace_back(txin);
668583
}
669584

670-
vecEntries.push_back(entryNew);
585+
bool fConsumeCollateral{false};
586+
if (!IsValidInOuts(vin, entry.vecTxOut, nMessageIDRet, &fConsumeCollateral)) {
587+
LogPrint(BCLog::PRIVATESEND, "CPrivateSendServer::%s -- ERROR! IsValidInOuts() failed: %s\n", __func__, CPrivateSend::GetMessageByID(nMessageIDRet));
588+
if (fConsumeCollateral) {
589+
ConsumeCollateral(connman, entry.txCollateral);
590+
}
591+
return false;
592+
}
593+
594+
vecEntries.push_back(entry);
671595

672-
LogPrint(BCLog::PRIVATESEND, "CPrivateSendServer::AddEntry -- adding entry %d of %d required\n", GetEntriesCount(), nSessionMaxParticipants);
596+
LogPrint(BCLog::PRIVATESEND, "CPrivateSendServer::%s -- adding entry %d of %d required\n", __func__, GetEntriesCount(), nSessionMaxParticipants);
673597
nMessageIDRet = MSG_ENTRIES_ADDED;
674598

675599
return true;
@@ -724,19 +648,6 @@ bool CPrivateSendServer::IsSignaturesComplete()
724648
return true;
725649
}
726650

727-
bool CPrivateSendServer::IsOutputsCompatibleWithSessionDenom(const std::vector<CTxOut>& vecTxOut)
728-
{
729-
if (CPrivateSend::GetDenominations(vecTxOut) == 0) return false;
730-
731-
for (const auto& entry : vecEntries) {
732-
LogPrint(BCLog::PRIVATESEND, "CPrivateSendServer::IsOutputsCompatibleWithSessionDenom -- vecTxOut denom %d, entry.vecTxOut denom %d\n",
733-
CPrivateSend::GetDenominations(vecTxOut), CPrivateSend::GetDenominations(entry.vecTxOut));
734-
if (CPrivateSend::GetDenominations(vecTxOut) != CPrivateSend::GetDenominations(entry.vecTxOut)) return false;
735-
}
736-
737-
return true;
738-
}
739-
740651
bool CPrivateSendServer::IsAcceptableDSA(const CPrivateSendAccept& dsa, PoolMessage& nMessageIDRet)
741652
{
742653
if (!fMasternodeMode) return false;

src/privatesend/privatesend-server.h

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ class CPrivateSendServer : public CPrivateSendBaseSession, public CPrivateSendBa
2929
bool fUnitTest;
3030

3131
/// Add a clients entry to the pool
32-
bool AddEntry(const CPrivateSendEntry& entryNew, PoolMessage& nMessageIDRet);
32+
bool AddEntry(CConnman& connman, const CPrivateSendEntry& entry, PoolMessage& nMessageIDRet);
3333
/// Add signature to a txin
3434
bool AddScriptSig(const CTxIn& txin);
3535

@@ -57,8 +57,6 @@ class CPrivateSendServer : public CPrivateSendBaseSession, public CPrivateSendBa
5757
bool IsSignaturesComplete();
5858
/// Check to make sure a given input matches an input in the pool and its scriptSig is valid
5959
bool IsInputScriptSigValid(const CTxIn& txin);
60-
/// Are these outputs compatible with other client in the pool?
61-
bool IsOutputsCompatibleWithSessionDenom(const std::vector<CTxOut>& vecTxOut);
6260

6361
// Set the 'state' value, with some logging and capturing when the state changed
6462
void SetState(PoolState nStateNew);

0 commit comments

Comments
 (0)